美文网首页
Spring Boot 从配置文件注入到自动配置(二)

Spring Boot 从配置文件注入到自动配置(二)

作者: 牧童US | 来源:发表于2020-08-10 15:31 被阅读0次

先回顾一下自定义注解

1.创建自定义注解
mport java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author lus E-mail:***@163.com
* @date 2020年8月7日 上午10:36:03
* @Description: TODO 类说明
* @version V1.0
* @since JDK 1.8
*/
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name() default "旁白";
    int id() default 5;
    Class gid();
}
2.创建注解使用实体
import java.util.HashMap;
import java.util.Map;

/**
 * @author lus E-mail:***@163.com
 * @date 2020年8月7日 上午10:38:29
 * @Description: TODO 类说明
 * @version V1.0
 * @since JDK 1.8
 */

@TestAnnotation(name = "type", gid = Long.class)
public class UserAnnotation {
    // 私有属性不会被输出其注解
    @TestAnnotation(name = "param", id = 1, gid = Long.class)
    private Integer age;

    // 注解中没有对相关的属性赋值的话,使用默认值
    @TestAnnotation(id = 2, gid = Long.class)
    public Integer age2;

    // 在age3中没有注释所以不会再控制台输出相关的注释信息
    public Integer age3;

    @TestAnnotation(name = "param", id = 2, gid = Long.class)

    public Integer age4;

    @TestAnnotation(name = "construct", id = 2, gid = Long.class)
    public UserAnnotation() {
    }

    @TestAnnotation(name = "public method", id = 3, gid = Long.class)
    public void a() {
        Map m = new HashMap(0);
    }

    @TestAnnotation(name = "private method", id = 5, gid = Long.class)
    private void c() {
        Map m = new HashMap(0);
    }

    public void b(Integer a) {

    }
}

3.创建注解解释器


import java.lang.annotation.Annotation;
import java.lang.reflect.*;

/**
 * @author lus E-mail:***@163.com
 * @date 2020年8月7日 上午10:42:00
 * @Description: TODO 类说明:解析注解的类
 * @version V1.0
 * @since JDK 1.8
 */
public class ParseAnnotation {
    public static void paraseTypeAnnotation() throws ClassNotFoundException {
        Class clazz = Class.forName("com.lus.annotation.UserAnnotation");
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            TestAnnotation testA = (TestAnnotation) annotation;
            System.out.println("id= " + testA.id() + ";name=" + testA.name() + "; gid=" + testA.gid());
        }
    }

    public static void parseMethodAnnotation() {
        Method[] methods = UserAnnotation.class.getDeclaredMethods();
        for (Method method : methods) {
            boolean hasAnnotation = method.isAnnotationPresent(TestAnnotation.class);
            if (hasAnnotation) {
                TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
                System.out.println("Method=" + method.getName() + ";id=" + annotation.id() + ";description="
                        + annotation.name() + ";gid=" + annotation.gid());

            }

        }
    }

    public static void parseConstructAnnotation() {
        Constructor[] constructors = UserAnnotation.class.getConstructors();
        for (Constructor constructor : constructors) {
            boolean hasAnnotation = constructor.isAnnotationPresent(TestAnnotation.class);
            if (hasAnnotation) {
                TestAnnotation annotation = (TestAnnotation) constructor.getAnnotation(TestAnnotation.class);
                System.out.println("constructor=" + constructor.getName() + ";id=" + annotation.id() + ";description="
                        + annotation.name() + ";gid=" + annotation.gid());

            }
        }
    }

    public static void parseFieldAnnotation() {
        Field[] fields = UserAnnotation.class.getFields();
        for (Field field : fields) {
            boolean hasAnnotation = field.isAnnotationPresent(TestAnnotation.class);
            if (hasAnnotation) {
                TestAnnotation annotation = (TestAnnotation) field.getAnnotation(TestAnnotation.class);
                System.out.println("field=" + field.getName() + ";id=" + annotation.id() + ";description="
                        + annotation.name() + ";gid=" + annotation.gid());
            }
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        paraseTypeAnnotation();
        parseMethodAnnotation();
        parseConstructAnnotation();
        parseFieldAnnotation();

    }
}

执行结果:

id= 5;name=type; gid=class java.lang.Long
Method=c;id=5;description=private method;gid=class java.lang.Long
Method=a;id=3;description=public method;gid=class java.lang.Long
constructor=com.lus.annotation.UserAnnotation;id=2;description=construct;gid=class java.lang.Long
field=age2;id=2;description=旁白;gid=class java.lang.Long
field=age4;id=2;description=param;gid=class java.lang.Long

熟悉完自定义注解在看一下SpringBoot运行原理

先看@SpringBootApplication

@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 {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;
}

@SpringBootConfiguration:标记当前类为配置类
@EnableAutoConfiguration:开启自动配置
@ComponentScan:扫描主类所在的同级包以及下级包里的Bean
关键是@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
   Class<?>[] exclude() default {};
   String[] excludeName() default {};
}

可以看到通过@Import(AutoConfigurationImportSelector.class)导入的配置功能,
AutoConfigurationImportSelector中的方法getCandidateConfigurations,得到待配置的class的类名集合,这个集合就是所有需要进行自动配置的类,而是是否配置的关键在于META-INF/spring.factories文件中是否存在该配置信息。

  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

打开,如下图可以看到所有需要配置的类全路径都在文件中,每行一个配置,多个类名逗号分隔,而\表示忽略换行


image.png

以SpringApplicationAdminJmxAutoConfiguration类来看其主要构成部分

@Configuration
@AutoConfigureAfter({JmxAutoConfiguration.class}) //配置完JmxAutoConfiguration后再配置当前类型
// spring.application.admin为前缀,属性为enabled,有值时为true,没有匹配到则为false:以上条件为true则实例化,否则不是实例化
@ConditionalOnProperty( prefix = "spring.application.admin", value = {"enabled"}, havingValue = "true",  matchIfMissing = false)
public class SpringApplicationAdminJmxAutoConfiguration 

SpringBoot的核心就是自动配置,自动配置又是基于条件判断来配置Bean。

从以上源码案例都能看到各种各样的条件判断注解,满足条件时就加载这个Bean并实例化
此类的条件注解是:@ConditionalOnProperty

@ConditionalOnBean:当容器里有指定Bean的条件下
@ConditionalOnClass:当类路径下有指定的类的条件下
@ConditionalOnExpression:基于SpEL表达式为true的时候作为判断条件才去实例化
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
@ConditionalOnMissingBean:当容器里没有指定Bean的情况下
@ConditionalOnMissingClass:当容器里没有指定类的情况下
@ConditionalOnWebApplication:当前项目时Web项目的条件下
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnOnSingleCandidate:当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean
这些注解都组合了@Conditional注解,只是使用了不同的条件组合最后为true时才会去实例化需要实例化的类,否则忽略
这种spring4.X带来的动态组合很容易后期配置,从而避免了硬编码,使配置信息更加灵活多变,同时也避免了不必要的意外异常报错。使用的人只要知道配置的条件即可也不用去阅读源码,方便快捷,这也是sprignboot快捷方式带来的好处。

参考HttpEncodingAutoConfiguration配置信息如下:

@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
.....
}

@Configuration:标明为配置类
@EnableConfigurationProperties(HttpEncodingProperties.class)声明开启属性注入
@ConditionalOnClass(CharacterEncodingFilter.class)当CharacterEncodingFilter在类路径的条件下
@ConditionalOnProperty(prefix = “spring.http.encoding”, value = “enabled”, matchIfMissing = true)当spring.http.encoding=enabled的情况下,如果没有设置则默认为true,即条件符合
@ConditionalOnMissingBean当容器中没有这个Bean时新建Bean

案例扩展


/**
 * @author wuweifeng wrote on 2020/08/10.
 * 根据部署环境动态决定是否启用eureka
 线上的环境开启eureka,就在application-prod.yml里配上open.eureka=true,
 其他的yml什么也不写就行了。这样本地启动时就相当于没有开启EnableDiscoveryClient
 */
@Component
@ConditionalOnProperty(value = "open.eureka")
@EnableDiscoveryClient
public class JudgeEnableDiscoveryClient 

自己实现一个自己的自动配置

项目

xm-common:普通jar项目
- src/main
    java
        BambooServer.java  需要被实例化的服务类
        BambooServerProperties.java 配置信息属性类
        BmbooServiceAutoConfiguration.java 自动配置类
    resources
        META-INF/spring.factories 配置自动配置的属性文件
demo:普通springboot-web项目

需要实例化的服务类

public class BambooServer {
    private String name;

    public String sayServerName(){
        return "I'm " + name + "! ";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

配置信息对应的属性映射类

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "bamboo")
public class BambooServerProperties {

    private static final String NAME = "bamboo_server0";

    private String name = NAME;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

自动配置文件

/**
 * Author: bamboo
 * Time: 2020/08/10
 * Describe: 自动配置类
 * 根据条件判断是否要自动配置,创建Bean
 */
@Configuration
@EnableConfigurationProperties(BambooServerProperties.class)
@ConditionalOnClass(BambooServer.class)//判断BambooServer这个类在类路径中是否存在
@ConditionalOnProperty(prefix = "bamboo",value = "enabled",matchIfMissing = true)
public class BmbooServiceAutoConfiguration {

    @Autowired
    private BambooServerProperties mistraServiceProperties;

    @Bean(name = "bambooServer")
    @ConditionalOnMissingBean(BambooServer.class)//当容器中没有这个Bean时(BambooServer)就自动配置这个Bean,Bean的参数来自于BambooServerProperties
    public BambooServer mistraService(){
        BambooServer mistraService = new BambooServer();
        mistraService.setName(mistraServiceProperties.getName());
        return mistraService;
    }
}

在创建如下路径文件src/main/resources/META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.bamboo.common.autoconfigure.bamboo.BmbooServiceAutoConfiguration

必须是自动配置类的全路径

mvn install 该项目

创建一个springboot-mvc项目pom依赖上面的jar

@SpringBootApplication
@RestController
@Import(value = {CorsConfig.class, LogFilter.class}) //跨域,接口访问请求日志
public class DemoApplication {
    @Autowired
    private BambooServer bmbooService;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RequestMapping("/")
    public Object index(){
        return "helll demo"+bmbooService.getName()+DateUtils.getDate();
    }
}

在applicaton.yml中加,重启刷新则会更新为如下信息

bamboo:
  name: 测试服务

相关文章

网友评论

      本文标题:Spring Boot 从配置文件注入到自动配置(二)

      本文链接:https://www.haomeiwen.com/subject/nrrjdktx.html