• springboot自定义starter


    自定义starter

    使用自定义starter

    自动装配源代码跟踪

    从springmvc到springboot最大的特点就是配置少,甚至不需要配置.这其中自动装配起了很大作用.这篇博客会带你了解下自动装配的源码以及怎么自己自定义starter

    自定义starter

    首先创建一个springboot工程.pom只需要导入spring-boot-starter依赖无需其他.

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.5.6</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.datang</groupId>
        <artifactId>test1</artifactId>
        <version>1</version>
        <name>test1</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>
    
    
            
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    这是整体的目录结构.

    启动类不能少因为我们自定义的功能依然需要使用springboot的注解.启动类的@SpringBootApplication是一个组合注解它会帮我们自动扫描其他包中的注解.

    package com.datang;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Test1Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Test1Application.class, args);
        }
    
    }
    View Code

    AddLog类是我们最终的功能类,很简单接收两个参数show方法打印出来.

    package com.datang.app;
    
    public class AddLog {
        private String log1;
        private String log2;
        public AddLog(String log1,String log2) {
            this.log1 = log1;
            this.log2 = log2;
        }
        
        public void show() {
            System.out.println(log1+"-----"+log2);
        }
    }
    View Code

    LogAutoConfiguration最最核心的类没有之一.类上的四个注解@Configurable它声明当前类是一个配置类,一般和方法注解@Bean配合使用.@ConditionalOnClass(AddLog.class)这是一个条件注解,如果你没有看过springboot的源码会很陌生.这个注解表示要只有类路径下找到AddLog.class才会配置这个bean.那什么情况下类路径会有AddLog.class呢,当然是你的项目引用了当前这个自定义starter的依赖时就会有.@EnableConfigurationProperties(LogProperties.class)这这个注解必须要和@Configurable用在一块,它表示需要将某个类作为配置类.稍后我们将看到怎么使用它.@AutoConfigureAfter(LogProperties.class)这也是一个条件注解,它表示当前类注册成bean需要在LogProperties之后,为什么呢?因为我们的成员变量使用了LogProperties.addLogBean()方法体创建了一个AddLog对象,使用LogProperties这个Bean的两个方法拿到参数构造自己.上有两个注解@Bean就是注册AddLog这个bean,另外一个也是条件注解,它表示当前Spring容器中没有AddLog这个类型的Bean时才需要将方法体内的对象注册到bean中,是为了防止bean冲突.

    package com.datang.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Configurable;
    import org.springframework.boot.autoconfigure.AutoConfigureAfter;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    
    import com.datang.app.AddLog;
    
    @Configurable
    @ConditionalOnClass(AddLog.class)
    @EnableConfigurationProperties(LogProperties.class)
    @AutoConfigureAfter(LogProperties.class)
    public class LogAutoConfiguration {
        @Autowired
        private LogProperties logProperties;
    
        @Bean
        @ConditionalOnMissingBean
        public AddLog addLogBean() {
            return new AddLog(logProperties.getLog1(), logProperties.getLog2());
        }
    }
    View Code

    最后一个配置类,@ConfigurationProperties(prefix = "dfsn.log")它可以从yml或者properties读取属性然后封装到LogProperties对象中.要开启这个注解必须使用@EnableConfigurationProperties

    package com.datang.config;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @ConfigurationProperties(prefix = "dfsn.log")
    public class LogProperties {
        private String log1;
        private String log2;
        public String getLog1() {
            return log1;
        }
        public void setLog1(String log1) {
            this.log1 = log1;
        }
        public String getLog2() {
            return log2;
        }
        public void setLog2(String log2) {
            this.log2 = log2;
        }
        
        
    }
    View Code

    现在思路很清晰了,我们自定义的starter功能类是AddLog它需要实例参数,它的参数由LogProperties类从配置文件中读取,LogAutoConfiguration用于在指定条件满足情况下将AddLog这个类装到Spring容器中.最后我们还需要最后一步,按照springboot的规则配置一个文件这个文件名是固定的需要在META-INF文件夹下名称为spring.factories,文件内容是一个key-value.它的key也是固定的value是LogAutoConfiguration配置类.

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.datang.config.LogAutoConfiguration
    View Code

    使用自定义starter

    pom文件中指定自定义starter的依赖.

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.9.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>demo</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <!--springBoot-start SpringBoot启动项  -->
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
    <dependency>
        <groupId>com.datang</groupId>
        <artifactId>test1</artifactId>
        <version>1</version>
        </dependency>
    
            <!--  <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-data-redis</artifactId>
           </dependency> -->
    
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    使用@Autowired注入自定义starter中的应用类调用方法.

    package com.example.demo;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.datang.app.AddLog;
    
    
    @RestController
    @SpringBootApplication
    public class DemoApplication {
        
        @Autowired
        private AddLog addLog;
        
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
        @GetMapping("test")
        public String test() {
            addLog.show();
            return "success";
        }
        
    }
    View Code

    yml文件中配置两个参数就是LogProperties需要读取的两个参数.

    # Spring配置
    #spring:
      # redis配置  
      #redis: 
         # 地址
         #host: 127.0.0.1
         # 端口,默认为6379
         #port: 6379 
         
    dfsn:
      log:
        log1: 我是log1
        log2: 我是log2
         
    View Code

    自动装配源代码跟踪

    看源代码首先需要明确,自己要找的功能是什么,不要盲目进入源码.首先我们思考,springboot自动装配都能干啥,我们平时整合Redis,mongoDB等中间件时,一般只需要引入依赖,yml配置,然后就可以使用bean了.思考一下,springboot需要整合那么多中间件,是不是要有一个文件描述了那些中间件是支持的.spring-boot-autoconfigure这个项目它就是spring自动装配的核心项目.在MATE-INF下有一个spring.factories文件它里边就有org.springframework.boot.autoconfigure.EnableAutoConfiguration属性,它的value是集合.这就像是我们自定义的starter一样.

    我们找一个熟悉的org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration那这个类就是Redis的自动装配配置类了.

    这个Redis的配置类和我们写的核心步骤是没有区别的.它多了一个@Import注解,这个注解就是spring中的<import>标签表示该类还有其他的配置类.

    现在我们大概知道了,springboot自动装配的步骤就是,现在spring.factories文件中配置需要整合的中间件的配置类,如果是我们自定义的也需要在META-INF下创建同名的文件,并且使用相同的key.然后spring会根据value去找配置类.从配置类上读取条件注解,判断是否需要注册bean.条件注解中两个相当重要的是@ConditionalOnClass和@EnableConfigurationProperties它们分别对应pom文件中必须要有依赖,yml中必须有配置.我们自定义的starter也需要这样.

    接下来我们看springboot的核心注解.SpringBootApplication除了元注解外,它有@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解.@SpringBootConfiguration其实就是封装了@Configuration表示当前类是一个配置类.@ComponentScan是一个包扫描标识,它和xml中的<context:component-scan>标签作用一致.告诉springboot需要扫描某个包中的文件看看它里边有没有注解需要解析.springboot2后这个注解默认会扫描启动类同包以及其子包.

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    View Code

    @EnableAutoConfiguration这个注解是自动装配的核心注解.它导入了AutoConfigurationImportSelector类.

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    View Code

    AutoConfigurationImportSelector的getCandidateConfigurations返回了一个集合,这个集合中包含了很多类全路径,在这个集合中的都是符合条件被自动装配的bean

    现在我们反着推导,看看从哪里筛选出这些符合条件的类的.

    进入这个方法SpringFactoriesLoader.loadFactoryNames这个方法通过classLoader去public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";路径下找配置.springboot自动装配和我们自定义starter的都是这个路径.这样就把所有可能需要装配的类的配置类给找到了.

    再次回到AutoConfigurationImportSelector现在已经找到了这些配置类,要看看如何筛选符合条件的类.找到谁调用了getCandidateConfigurations()

     

    接下来我们就看OnClassCondition是怎么判断的.

  • 相关阅读:
    WinForm容器内控件批量效验是否同意为空?设置是否仅仅读?设置是否可用等方法分享
    EF的CRUD
    SICP 习题 (1.41)解题总结
    陈光标挽救纽约穷人背后有何玄机?
    poj 1276 Cash Machine(多重背包)
    vue的生命周期
    vue mounted组件的使用
    babel-polyfill的几种使用方式
    可拖拽排序的vue组件
    import、export 和 export default
  • 原文地址:https://www.cnblogs.com/zumengjie/p/15487197.html
Copyright © 2020-2023  润新知