自定义Spring Boot Starter

1、SpringBoot starter机制

  SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,
SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。
SpringBoot会自动通过classpath路径下的类发现需要的Bean,并注册进IOC容器。SpringBoot提供了针对日常企业应用研发各种场景的spring-boot-starter依赖模块。
所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。

2、为什么要自定义starter

  在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,
重新集成一遍,麻烦至极。如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配,简直不要太爽。

3、自定义starter的命名规则

  SpringBoot提供的starter以spring-boot-starter-xxx的方式命名的。官方建议自定义的starter使用xxx-spring-boot-starter命名规则。以区分SpringBoot生态提供的starter。

4、代码地址

https://gitee.com/tenic/demo-spring-boot-starter.git

5、代码具体实现

  • 目录结构如下图所示,其中demo-spring-boot-starter为父工程,properties-spring-boot-starter,interceptor-spring-boot-starter是我们今天要实现的2个自定义starter,
    test-spring-boot-starter是我们的测试工程
    自定义Spring Boot Starter

  • demo-spring-boot-starter 创建
    • 我们创建一个空的springboot项目,我们依赖的版本是2.3.7.RELEASE, 具体的POM文件依赖内容如下代码所示:

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <packaging>pom</packaging>
          <!--子模块工程-->
          <modules>
              <module>properties-spring-boot-starter</module>
              <module>test-spring-boot-starter</module>
              <module>interceptor-spring-boot-starter</module>
          </modules>
      
          <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>2.3.7.RELEASE</version>
          </parent>
          <groupId>com.demo</groupId>
          <artifactId>demo-spring-boot-starter</artifactId>
          <version>0.0.1-RELEASE</version>
      
          <name>demo-spring-boot-starter</name>
          <description>Demo project for Spring Boot</description>
      
          <properties>
              <java.version>1.8</java.version>
          </properties>
      
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-autoconfigure</artifactId>
              </dependency>
          </dependencies>
      
      </project>
      
  • properties-spring-boot-starter创建,
    • 我们这里创建一个父工程的子模块,具体模块划分如下图所示

      自定义Spring Boot Starter

    • 因为我们依赖父工程,且父工程已经依赖了我们需要的基本的jar包,所以我们的POM文件相对简单一点

        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <parent>
                <artifactId>demo-spring-boot-starter</artifactId>
                <groupId>com.demo</groupId>
                <version>0.0.1-RELEASE</version>
            </parent>
            <modelVersion>4.0.0</modelVersion>
      
            <artifactId>properties-spring-boot-starter</artifactId>
            <packaging>jar</packaging>
        </project>
      
    • 创建一个Properties相关的类,用来使用配置相关的信息

        package com.tenic.springboot.properties;
      
        import org.springframework.boot.context.properties.ConfigurationProperties;
      
        @ConfigurationProperties(prefix = "tenic")
        public class CustomerProperties {
      
            private String username;
      
            private String password;
      
            private String driver;
      
            private String url;
            // ... 省略 有参,无参 构造函数 getter/setter方法 ...
        }
      
    • 创建一个使用到Properties信息的一个类,比如我们创建一个工厂类ServiceFactory

        package com.tenic.springboot.service;
      
        import com.tenic.springboot.properties.CustomerProperties;
      
        public class ServiceFactory {
      
            public ServiceFactory(CustomerProperties customerProperties) {
                System.out.println(customerProperties.toString());
            }
        }
      
    • 创建一个自动装配的类,将我们上边的ServiceFactory生成Bean 放入到我们的Spring 容器里面来

        package com.tenic.springboot.config;
      
        import com.tenic.springboot.properties.CustomerProperties;
        import com.tenic.springboot.service.ServiceFactory;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.context.properties.EnableConfigurationProperties;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
      
        @Configuration
        @EnableConfigurationProperties({CustomerProperties.class})
        public class CustomerPropertiesAutoConfiguration {
      
           @Autowired
           CustomerProperties properties;
      
           @Bean
           public ServiceFactory getServiceFactory(){
               return new ServiceFactory(properties);
           }
        }
      
    • 在resource目录下创建一个META-INF/spring.factories的文件,文件的内容就是我们要自动装配的具体的信息

        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
          com.tenic.springboot.config.CustomerPropertiesAutoConfiguration
      
    • 在resource目录下创建一个application.yml的文件,配置上我们需要的配置信息

        server:
          port: 9091
      
        tenic:
          username: tenic
          password: 123456
          driver: com.tenic.springboot
          url: www.cnblogs.com/tenic
      
    • 启动模块的启动类, 可以看到控制台上打印出来了我们配置的具体信息,说明已经将具体的配置信息自动装配进去了
      自定义Spring Boot Starter

  • interceptor-spring-boot-starter 创建
    • 我们这里来实现一个对接口调用统计时间的一个starter, 具体我们是使用到了拦截器的机制,在执行前确定是啥时候开始,执行后确定啥时候结束,统计花费时长。
      我们也同样创建一个父工程的子模块,具体的目录结构如下图所示
      自定义Spring Boot Starter
    • 具体的POM文件内容
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <parent>
                <artifactId>demo-spring-boot-starter</artifactId>
                <groupId>com.demo</groupId>
                <version>0.0.1-RELEASE</version>
            </parent>
            <modelVersion>4.0.0</modelVersion>
            <packaging>jar</packaging>
      
            <artifactId>interceptor-spring-boot-starter</artifactId>
        </project>
      
    • 我们创建一个注解,帮我们在相应的方法上打上标记
        package com.tenic.springboot.annotion;
      
        import java.lang.annotation.*;
      
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Inherited
        public @interface TenicLog {
      
            String desc() default "";
        }
      
    • 创建自定义的拦截器,在拦截器中记录上我们请求的地址,接口,花费时长等信息
        package com.tenic.springboot.interceptor;
      
        import com.tenic.springboot.annotion.TenicLog;
        import org.springframework.web.method.HandlerMethod;
        import org.springframework.web.servlet.ModelAndView;
        import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
      
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
      
        public class CustomerInterceptor extends HandlerInterceptorAdapter {
      
            private final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
      
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                TenicLog tenicLog = handlerMethod.getMethodAnnotation(TenicLog.class);
                if(tenicLog!=null) {
                    threadLocal.set(System.currentTimeMillis());
                }
                return true;
            }
      
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                TenicLog tenicLog = handlerMethod.getMethodAnnotation(TenicLog.class);
                if(tenicLog!=null){
                    Long costTime = System.currentTimeMillis()-threadLocal.get();
                    String desc = tenicLog.desc();
                    String name = handlerMethod.getMethod().getDeclaringClass()+" " +handlerMethod.getMethod().getName();
                    StringBuffer requestURL = request.getRequestURL();
                    System.out.println("请求地址为:"+ requestURL +" 方法是:"+ name +" 描述信息是:"+ desc +" 总耗时:" + costTime + "ms");
                }
            }
        }
      
    • 创建自动装配类,并将拦截器注册到容器中
        package com.tenic.springboot.config;
      
        import com.tenic.springboot.interceptor.CustomerInterceptor;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
        import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      
        @Configuration
        public class InterceptorAutoConfiguration implements WebMvcConfigurer {
      
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new CustomerInterceptor());
            }
        }
      
    • 在resource目录下创建一个application.yml的文件,配置上我们需要的配置信息
        server:
          port: 9092
          servlet:
            context-path: /test
      
    • 启动模块的启动类,在浏览器中调用我们的接口 http://localhost:9092/test/hello ,可以在控制台中查看到具体的日志信息
        package com.tenic.springboot;
      
        import com.tenic.springboot.annotion.TenicLog;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.ResponseBody;
        import org.springframework.web.bind.annotation.RestController;
      
        @SpringBootApplication
        //@RestController
        public class SpringBootInterceptorApplication {
      
            public static void main(String[] args) {
                SpringApplication.run(SpringBootInterceptorApplication.class);
            }
      
            /**
             * 供测试使用
             */
        //    @GetMapping("/hello")
        //    @TenicLog(desc = "测试接口类")
        //    public String hello() {
        //        return "hello world!";
        //    }
        }
      
      自定义Spring Boot Starter
  • test-spring-boot-starter 创建
    • 我们这里只是验证上边2个starter,创建一个父工程的子模块,验证模块的目录结构比较简单,如下图所示
      自定义Spring Boot Starter
    • 我们依赖了父工程,验证上边2个模块,所以也要添加上边的模块到maven中,具体的POM文件内容如下
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <parent>
                <artifactId>demo-spring-boot-starter</artifactId>
                <groupId>com.demo</groupId>
                <version>0.0.1-RELEASE</version>
            </parent>
            <modelVersion>4.0.0</modelVersion>
      
            <artifactId>test-spring-boot-starter</artifactId>
      
            <dependencies>
                <dependency>
                    <groupId>com.demo</groupId>
                    <artifactId>properties-spring-boot-starter</artifactId>
                    <version>0.0.1-RELEASE</version>
                </dependency>
                <dependency>
                    <groupId>com.demo</groupId>
                    <artifactId>interceptor-spring-boot-starter</artifactId>
                    <version>0.0.1-RELEASE</version>
                </dependency>
            </dependencies>
        </project>
      
    • 在resource目录下创建一个application.yml的文件,配置上我们需要的配置信息
      server:
        port: 9090
        servlet:
          context-path: /tsla
      
      tenic:
        username: zzz
        password: abc
        driver: com.cnblog.tenic
        url: https://gitee.com/tenic/
      
    • 创建一个启动类和一个controller类,创建好之后,启动一下,访问http://localhost:9090/tsla/test, 看以在日志上看到我们的接口调用信息
        package com.tenic.springboot.controller;
      
        import com.tenic.springboot.annotion.TenicLog;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.RestController;
      
        @RestController
        public class TestController {
      
            @GetMapping("/test")
            @TenicLog(desc = "测试接口类")
            public String hello(){
                return "hello world!";
            }
        }
      
      启动类
        package com.tenic.springboot;
      
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
      
        @SpringBootApplication
        public class SpringBootTestApplicaiton {
      
            public static void main(String[] args) {
                SpringApplication.run(SpringBootTestApplicaiton.class);
            }
        }
      
      自定义Spring Boot Starter
上一篇:springboot log4j2


下一篇:spring-boot-starter-test