自定义 Spring-Boot-Starter

自定义 spring-boot-starter

spring-boot-starter 的作用

xxx-spring-boot-starter 本质上也是一个 Spring 项目。将其作为一个 pom 依赖引入后,当前项目中可以直接使用在 xxx-spring-boot-starter 项目里面配置好的设置,达到一个配置重用的效果,即自动配置。下面以自定义的一个 hello-spring-boot-starter 为例,分别介绍如何使用和如何创建这样的一个 starter。

spring-boot-starter 的使用

hello-spring-boot-starter 是自定义的一个 springboot 项目,没有主启动类,也不需要主启动类。其功能是读取配置文件的值,利用这些值配置一个 HelloService 对象,从而达到其它项目在引入 hello-spring-boot-starter的 pom 依赖后可以直接利用 @Autowired 使用该 HelloService 对象。

  1. 引入 pom 依赖

            <!--hello-spring-boot-starter是自定义的一个springboot项目-->
            <dependency>
                <groupId>org.example</groupId>
                <artifactId>hello-spring-boot-starter</artifactId>
                <version>1.0</version>
            </dependency>
  2. 配置文件(可以省略,不省略则表示覆盖默认值)

    # 覆盖设置的默认值(root和root)
    hello:
        username: admin
        password: admin
  3. @SpringBootTest 中使用 HelloService 对象

    import com.example.service.HelloService;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    class HeimaSecurityApplicationTests {
    
        @Autowired
        private HelloService helloService;
    
        @Test
        public void getHelloSpringBootStarterTest(){
            System.out.println(helloService);
        }
    
    }

    输出测试结果

spring-boot-starter 的创建

  1. hello-spring-boot-starter 项目的结构

    • HelloServiceAutoConfiguration:(自动)配置类
    • HelloServiceProperties:配置文件读取类
    • HelloService:(自动)配置的目标类
    • META-INF/spring.factories:在引入 hello-spring-boot-starter 的其它 SpringBoot 项目中,会扫描该文件路径来获取自动配置类(Spring自动配置源码硬编码)

    项目结构图

  2. 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>
        <!--引入spring-boot-starter-parent来进行版本管理, 减少需要手动管理的依赖-->    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
        </parent>
    
        <!--本项目的相关信息-->        
        <groupId>org.example</groupId>
        <artifactId>hello-spring-boot-starter</artifactId>
        <version>1.0</version>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
            <!--xxx-spring-boot-starter项目的核心依赖, 其它的都是次要的-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
        </dependencies>
    </project>
  3. HelloService 自动配置的目标类(可以不是 hello-spring-boot-starter 项目中的类,或者说往往不是,这里为了简化问题的处理)

    public class HelloService {
        private String username;
        private String password;
    
        public HelloService() {
        }
    
        public HelloService(String username, String password) {
            this.username = username;
            this.password = password;
        }
    
        @Override
        public String toString() {
            return "Hello{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
  1. HelloServiceAutoConfiguration 自动配置类

    自动配置是相对于使用 hello-spring-boot-starter 作为其 pom 依赖的 SpringBoot 项目而言的,在 hello-spring-boot-starter 项目本身中,所谓的自动配置类就是一个配置类。(对于 hello-spring-boot-starter 项目的维护者来说,这是手动配置类,我配置好了,别人再去使用就是自动配置了)

    import com.example.service.HelloService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * 自动配置类, 用于自动配置HelloService
     */
    @Configuration
    @EnableConfigurationProperties(HelloServiceProperties.class)
    public class HelloServiceAutoConfiguration {
    
        @Autowired
        private HelloServiceProperties helloServiceProperties;
    
        // // 另外一种注入方式
        // public HelloServiceAutoConfiguration(HelloServiceProperties helloServiceProperties) {
        //     this.helloServiceProperties = helloServiceProperties;
        // }
    
        /**
         * 添加 @Bean 注解才会自动调用该方法
         * <p>
         * 添加 @ConditionalOnMissingBean 注解, 用户手动注入, 则默认的该设置则失效
         *
         * @return
         */
        @Bean
        @ConditionalOnMissingBean
        public HelloService helloService() {
            return new HelloService(
                    helloServiceProperties.getUsername(),
                    helloServiceProperties.getPassword());
        }
    }
  2. HelloServiceProperties 配置文件读取类

    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @ConfigurationProperties(prefix = "hello")
    @Data
    public class HelloServiceProperties {
        private String username;
        private String password;
    }
  3. application.yml 配置文件

    配置文件对于 hello-spring-boot-starter 项目而言不是必须的,因为 hello-spring-boot-starter 项目本身并不执行,项目的配置文件是一种设置默认值的体现。

    hello:
      username: root
      password: root
  4. spring.factories 自动配置类的地址指示文件

    # 告诉使用hello-spring-boot-starter的SpringBoot项目去哪里加载 HelloService 的自动配置类
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.config.HelloServiceAutoConfiguration
    

自定义 spring-boot-starter 进阶(AOP编程)

两个关键点:

  • 自定义注解 + 拦截器(拦截器体现的是 AOP 编程)
  • 在配置类中注入拦截器的方式(和一般的 Bean 对象不同)

在上面 hello-spring-boot-starter 的基础上,进一步拓展

  1. 自定义注解

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 自定义注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyLog {
        /**
         * 方法描述
         *
         * @return
         */
        String desc() default "";
    }
  2. 自定义拦截器(即AOP编程,对所有标注 @MyLog 注解的原始方法进行增强)

    这里决定了 @MyLog 的生效范围是 web 中的 controller 方法?为什么在 @SpringBootTest 中进行测试无法正常输出信息

    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;
    import java.lang.reflect.Method;
    
    public class MyLogInterceptor extends HandlerInterceptorAdapter {
    
        private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
    
        /**
         * 原始方法之前执行
         *
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            MyLog annotation = method.getAnnotation(MyLog.class);
            if (annotation != null) {
                // 说明当前拦截的方法上添加了自定义的 @MyLog 注解
    
                // 1. 用 ThreadLocal 来记录该方法的开始执行时间
                // TODO 为什么要使用 ThreadLocal 呢?
                startTimeThreadLocal.set(System.currentTimeMillis());
            }
            // 该拦截器为添加 @MyLog 注解的方法进行功能增强, 而不是真正意义上的拦截某个请求, 因此所有方法都会放行(返回true)
            return true;
        }
    
        /**
         * 原始方法之后执行
         *
         * @param request
         * @param response
         * @param handler
         * @param modelAndView
         * @throws Exception
         */
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            MyLog annotation = method.getAnnotation(MyLog.class);
            if (annotation != null) {
                // 自定义一些日志的输出信息
                System.out.println(method.getName() + " 的执行时间: " + (System.currentTimeMillis() - startTimeThreadLocal.get()));
            }
            super.postHandle(request, response, handler, modelAndView);
        }
    }
  3. MyLogAutoConfiguration 配置类中注入拦截器的 Bean 对象(和一般的 Bean 对象有所不同)

    import com.example.mylog.MyLogInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * 创建 web 层相关的配置类需要实现 WebMvcConfigurer
     */
    @Configuration
    public class MyLogAutoConfiguration implements WebMvcConfigurer {
        /**
         * 对于拦截器对象(AOP编程)的专门处理, 不使用@Bean注入, 而是使用registry.addInterceptor()的方式进行添加
         *
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MyLogInterceptor());
        }
    }
  4. 在 spring.factories 文件中设置配置类的全类名(多个配置类之间使用逗号 , 进行分隔,换行需要添加 \

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.example.config.HelloServiceAutoConfiguration,\
      com.example.config.MyLogAutoConfiguration
  5. 通过 controller 来进行测试(必须通过 controller 层)

    import com.example.mylog.MyLog;
    import com.example.service.HelloService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/hello")
    public class HelloSpringBootStarterController {
        @Autowired
        private HelloService helloService;
    
        @GetMapping("test")
        public String testHello() {
            return helloService.toString();
        }
    
        @GetMapping("mylog")
        @MyLog
        public String testMyLog() {
            return helloService.toString();
        }
    }
  6. 测试结果

    自定义拦截器的测试结果


   转载规则


《自定义 Spring-Boot-Starter》 熊水斌 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
权限管理系统 权限管理系统
权限管理系统项目概述业务架构 技术架构 SpringBoot Starter 原理SpringBoot 简化 Spring 的原因就是提供了许多 starter,而 starter 本质上就是引入了一些相关依赖和进行一些初始化的自动配置。
2023-04-06
下一篇 
Redis集群 Redis集群
Redis 集群Redis 集群的安装配置 解压缩tar -zxvf /opt/module/redis-6.2.7.tar.gz -C /opt/module 编译make -C /opt/module/redis-6.2.7 安装m
2023-04-05
  目录