自动配置类源码中的注解眼熟的你认识几个???

204 阅读7分钟

@ConditionalOnXXX

此类注解做为Bean的注册条件。

@ConditionalOnClass

@ConditionalOnClass(value = XXX.class)XXX.class存在就将A对象注册到容器中、存在与不存在最好的区分方法就是如果XXX.class没有编译出问题就肯定存在、但是这样的话就体验不出这个注解存在的作用、可以使用在类上和方法上。

@Bean
@ConditionalOnClass(value = XXX.class)
public A a() {
  return new A();
}

真要亲自演示一遍的话可以试着我这样、这个注解一般用在引入第三方jar出现的比较多、简单理解一下、让你设计一个第三方的一个工具、但是你要根据项目中某个依赖类才能够将第三方jar中的类注册到容器中、如我当前项目使用的mysql、而我第三方jar中有个类需要是mysql中才能用的。

第三方jar

我们通过一个META-INF/spring.factories、当做自动装配对象、然后在去配置依赖pom.xml

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wei.CustomConfigOne
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <optional>true</optional>
  </dependency>
  <!--mysql驱动包-->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <optional>true</optional>
  </dependency>
</dependencies>

为什么这个第三方依赖需要加mysql依赖呢?是因为@ConditionalOnClass(JDBC4Connection.class)在编译的时候会报错所以需要把它的依赖先给加上、不同的就是<optional>true</optional>后面演示区别。

@ConditionalOnClass(JDBC4Connection.class)
public class CustomConfigOne {

}

第三方的jar就算弄好了、就相当于是引入了这个第三方jar的话JDBC4Connection.class类必须存在、这个CustomConfigOne类才会注入到spring容器中。

本地项目

将第三方jar引用到本地项目中

<dependency>
  <groupId>com.wei</groupId>
  <artifactId>demo2</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

然后设置启动后打印所有Bean名称

@SpringBootApplication
public class Demo1Application {
    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(Demo1Application.class, args);
        for (String name : applicationContext.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
}

启动中会发现没有叫CustomConfigOne这个名称的Bean、然后我们将本地项目中的mysql依赖放入进去

<dependency>
  <groupId>com.wei</groupId>
  <artifactId>demo2</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

<!--mysql驱动包-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.47</version>
</dependency>

这个时候在启动就会发现CustomConfigOne这个被注册进去了。

<optional>true</optional>区别如果第三方不设置这个、本地项目maven如下

1672056901613.png

如果第三方设置了这个<optional>true</optional>本地项目mavne如下

1672056961785.png

以上就是演示@ConditionalOnClass注解如果设置不存在的类编译报错的情况下如何使用、以上说的要注意一个点就是第三方和本地的依赖版本的管理、如果本地用mysql8点几的依赖、而第三方又用mysql5点几、这样会导致你本地是引入了mysql的依赖但是没有@ConditionalOnClass中设置的对象这就很尴尬了。

还有一个方式就是通过@ConditionalOnClassname属性写全路径的的方式、这样就不存在类编译报错问题。

@ConditionalOnClass(name = "com.mysql.jdbc.JDBC4Connection")

简单来说:设置的全路径是一个存在且正确的位置、标记了该对象的话就会加载到容器中。

@ConditionalOnMissingClass

相比于上面@ConditionalOnClass、这个@ConditionalOnMissingClass还简单一些因为这个只有一个参数、就是设置全路径的方式、可以使用在类和方法上。

@Bean
@ConditionalOnMissingClass(value = {"com.wei.CustomConfigOne"})
public A a() {
  return new A();
}

@ConditionalOnMissingClass中设置的类路径能找到时、A对象不会被注册到容器中、是否能找到怎么样才算找到?你可以理解为使用Class.forName()能加载出来类对象就证明这个类存在。

简单来说:设置的全路径是一个存在且正确的位置、标记了该对象则不会加载到容器中。

@ConditionalOnMissingBean

@ConditionalOnMissingBean(XXXX.class)中的XXXX.class类型没有被加载到Spring容器中的话就将A对象加载到容器中。

PS:注解支持放在类上。

@Bean
@ConditionalOnMissingBean(XXXX.class)
public A a(){
  return new A();
}

例子

@Configuration
public class CustomConfig {

    @Bean
    public B b() {
        return new B();
    }
  
    @Bean
    @ConditionalOnMissingBean(value = B.class)
    public A a() {
        return new A();
    }
}

上面例子就会加载B对象到容器中、而A对象要加载的时候发现使用了这个注解、而B对象已经在容器中了、这个时候就不会加载A对象到容器中、要注意的在@Configuration中加载bean的顺序是从上往下、如果B写在A后面加载的话则A对象也会被加载到容器中。

简单一句话概括就是:如果指定对象已经在容器中存在就不会加载该对象到容器中。

@ConditionalOnBean

@ConditionalOnBean(value = XXX.class)中的XXX类存在容器中就会加载A对象到容器中

PS:注解支持放在类上。

@Bean
@ConditionalOnBean(value = XXX.class)
public A a() {
  return new A();
}

例子

@Configuration
public class CustomConfig {

    @Bean
    @ConditionalOnBean(value = B.class)
    public A a() {
        return new A();
    }

    @Bean
    public B b() {
        return new B();
    }
}

而上面例子中就会加载B对象在容器中、这个注解的作用就是容器中有对应对象就会加载A对象。

简单一句话概括就是:如果指定对象已经在容器中存在就会加载该对象到容器中。

@ConditionalOnProperty

这个注解的作用是指、指定存在某个配置项的时候就注册bean到容器中、不存在则不加载。

PS:注解支持放在类上。

a:
  b:
    c: d
@Configuration
public class CustomConfig {

    @Bean
    @ConditionalOnProperty("a.b.c")
    public A a() {
        return new A();
    }
}

参数说明

// 默认值、设置包括前缀以及配置名称
String[] value() default {};
// 前缀名称
String prefix() default "";
// 配置名称、一般以最后一个name做为配置名称
String[] name() default {};
// 设置配置的值、如果不相同则不加载、确保配置项存在的情况下
String havingValue() default "";
// 默认找不到配置项则不加载bean
boolean matchIfMissing() default false;

例子

@Configuration
public class CustomConfig {
    @Bean
    @ConditionalOnProperty(prefix = "a.b", name = "c", havingValue= "d")
    public A a() {
        return new A();
    }
}

配置项要匹配、并值要等于b、否者不加载A对象。

简单来说:使用这个注解的话配置类中需要对应配置的内容、否则不加载该类。

@AutoConfigureXXX

@AutoConfigureOrder@AutoConfigureBefore@AutoConfigureAfter这三个注解适用于自动配置类的加载顺序。 1671368145874.png

SpringBoot中的自动配置类都是这个文件中配置我们也可以在自己项目中的resourcesMETA-INF/spring.factories、写一个这样的文件。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.wei.config.C,com.wei.config.B,com.wei.config.A

注意:这三种注解只能在spring.factories中配置的类才能使用这三个注解生效、其次不会按照文件中类的顺序加载、目前你可以理解为A-B-C这种加载方式

@AutoConfigureOrder

这个最好理解、值越小加载就加载自动配置的类。

@AutoConfigureOrder(1)
public class A {
    public A() {
        System.out.println("A.......");
    }
}

@AutoConfigureOrder(2)
public class B {
    public B() {
        System.out.println("B.......");
    }
}

@AutoConfigureOrder(0)
public class C {
    public C() {
        System.out.println("C.......");
    }
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.wei.config.C,com.wei.config.B,com.wei.config.A

这个是必须要的、要不然的话都没有加载到容器中。

测试结果

C.......
A.......
B.......

@AutoConfigureAfter

可以这样理解:在加载当前类的时候先去加载@AutoConfigureAfter中指定的类、等指定的类加载后在去加载当前的类、注意的是要在spring.factories中配置的类

public class A {
    public A() {
        System.out.println("A.......");
    }
}

@AutoConfigureAfter(C.class)
public class B {
    public B() {
        System.out.println("B.......");
    }
}

public class C {
    public C() {
        System.out.println("C.......");
    }
}

测试结果

A.......
C.......
B.......

@AutoConfigureBefore

可以这样理解:当前类的时候先会去有没有类使用了@AutoConfigureBefore注解标记当前加载的类我就先加载标记的类、再去加载当前类。

public class A {
    public A() {
        System.out.println("A.......");
    }
}

public class B {
    public B() {
        System.out.println("B.......");
    }
}

@AutoConfigureBefore(A.class)
public class C {
    public C() {
        System.out.println("C.......");
    }
}

测试结果

C.......
A.......
B.......

默认的话肯定是A-B-C的这种加在顺序、按默认加载方式、我加载A的时候我先去找有没有类被@AutoConfigureBefore标记了A类、发现C类标记了所以先加载C类、在加载A类、后面就在加载B类。

@EnableConfigurationProperties

这个注解@EnableConfigurationProperties一般配合@ConfigurationProperties注解使用、只能放在类上、先看个例子

a:
  b:
    c: dfffff
@ConfigurationProperties(prefix = "a.b")
public class AProperties {

    private String c;

    public String getC() {
        return c;
    }
    public void setC(String c) {
        this.c = c;
    }
}
@Configuration
@EnableConfigurationProperties(AProperties.class)
public class CustomConfig {

    @Autowired
    private AProperties aProperties;

    @PostConstruct
    public void init(){
        System.out.println(aProperties.getC());
    }
}

最后输出的结果就是dfffff、最后可以理解为@EnableConfigurationProperties注解会将设置的配置类注入到容器中、但是配置类中需要加上@ConfigurationProperties注解、不加就报错!!!

还有一种就是不使用@EnableConfigurationProperties@ConfigurationProperties、使用@Component@ConfigurationProperties这样就是直接标记配置类到容器中了。

还有一个要注意的就是使用了@Component又使用@EnableConfigurationProperties、在@Autowired对象的时候就会报错:说找到了两个类型的类、不知道选择哪一个。

@Import

这个@Import相当于是将填写的类注入到容器中使用、如一个类加了@Configuration、但是@Import中填写的类中不需要@Configuration

public class CustomConfigOne {
}
@Configuration
@Import(CustomConfigOne.class)
public class CustomConfig {

}
customConfig
com.wei.config.CustomConfigOne

输出Bean名称会发现两种Bean的名称不一样、注入使用是没有问题的。

小结

以上注解说明了一下使用的方式、没有包含很细节的源码实现解释、还有一些不起眼的注解你们也可以发来一起看看它的使用方式.....以及以上注解有什么问题可以指出来。