springboot控制依赖加载的高级注解

1,308 阅读4分钟

前言

我甚至连本篇的标题是否准确都不是特别确定。仅仅想聊下下面几个注解,用法其次的,感受下java程序员最骄傲的框架spring的精妙操作。

@Conditional
@Import
@AutoConfigureAfter
@utoConfigureBefore
@DependsOn

准备

新建两个类,TestA 和TestB,如下:

package com.cmdc.bean;

import lombok.extern.slf4j.Slf4j;

/**
 * @author : wuwensheng
 * @date : 15:59 2021/12/3
 */
@Slf4j
public class TestA {
    public TestA() {
        log.warn("testA is created!");
    }
}
package com.cmdc.bean;

import lombok.extern.slf4j.Slf4j;

/**
 * @author : wuwensheng
 * @date : 16:01 2021/12/3
 */
@Slf4j
public class TestB {
    public TestB() {
        log.warn("testB is created!");
    }
}

@ConditionalOnBean、@ConditionalOnMissingBean

@ConditionalOnBean:仅在该注解规定的类实例存在于 spring容器中时,使用该注解的config或者bean声明才会被实例化到容器中

@ConditionalOnMissingBean:仅在该注解规定的类实例不存在于 spring容器中时,使用该注解的config或者bean声明才会被实例化到容器中

下面试验下: 创建一个配置类:

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

/**
 * @author : wuwensheng
 * @date : 10:04 2021/12/6
 */
@Configuration
@Slf4j
public class TestConfiguration {

    @ConditionalOnMissingBean
    @Bean
    public TestA getTestA(){
      log.warn("start new TestA");
        return new TestA();
    }
}

目前,并没有TestA类型的Bean存在于spring的容器中。所以这个getTestA()方法会执行。TestA类的实例将被创建,名字为getTestA.

image.png

ok的。那么现在在TestConfiguration这个类中增加下面这段:

@ConditionalOnBean(TestA.class)
@Bean(value = "testB2")
@DependsOn("getTestA")
public TestB getTestB() {
    log.warn("start new TestB");
    return new TestB();
}

变成这个样子

image.png

@ConditionalOnBean(TestA.class)表示我希望仅仅在TestA的实例存在于spring容器中时,才调用getTestB方法创建TestB的实例。@DependsOn注解要求这个方法的执行依赖getTestA实例的创建。

那么下面运行下;

image.png

对的。

image.png

假设我们此时注释掉TestA实例的创建,那么自然而然的TestB的实例也无法创建了。

@ConditionalOnClass、@ConditionalOnMissingClass

这两个注解

@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean

@ConditionalOnMissingClass:某个class没有位于类路径上,才会实例化一个Bean

增加两个类,TestC、TestD

package com.cmdc.bean;

import lombok.extern.slf4j.Slf4j;
/**
 * @author : wuwensheng
 * @date : 16:04 2021/12/3
 */
@Slf4j
public class TestC {
    public TestC() {
        log.info("testC is created!");
    }
}
import lombok.extern.slf4j.Slf4j;

/**
 * @author : wuwensheng
 * @date : 16:22 2021/12/3
 */
@Slf4j
public class TestD {
    public TestD() {
        log.info("testD is created!");
    }
}

配置类增加如下两个配置方法:

@ConditionalOnClass(TestC.class)
@Bean
public TestC getTestC() {
    log.info("start new TestC");
    return new TestC();
}

@ConditionalOnMissingClass("com.cmdc.bean.Test")
@Bean
public TestD getTestD() {
    log.info("start new TestD");
    return new TestD();
}

那么TestC位于类路径,所以getTestC方法会被执行。

“com.cmdc.bean.Test”是一个不存在的类路径,getTestD也会被执行。

image.png

运行结果符合预期的。

最后多说一句,控制依赖顺序的Conditional家族不仅仅这几个注解还有许多,感兴趣的可以都看一看,但是一般来说,即便封装个小框架,上面提到的掌握了基本也是够用的。

image.png

@DependsOn

作用:
   用于指定某个类的创建依赖的bean对象先创建。spring中没有特定bean的加载顺序,
   使用此注解则可指定bean的加载顺序。(在基于注解配置中,是按照类中方法的书写顺序决定的)
属性:
   value:
   用于指定bean的唯一标识。被指定的bean会在当前bean创建之前加载。
   

这个注解咱们刚才已经使用过了,就不再多做阐述。用于指定bean的依赖关系。

@AutoConfigureAfter、@AutoConfigureBefore

这两个注解用于配置类上,规定类加载的顺序问题,在框架的源码中也是非常常见的。 下面继续咱们的实验。

有这么几点注意事项: 1、使用该注解的配置类必须在springboot启动类扫描不到的地方。 2、使用spring.factories的方式来加载使用这个注解的配置类。

新建TestE和TestF类

image.png

import lombok.extern.slf4j.Slf4j;

/**
 * @author : wuwensheng
 * @date : 15:15 2021/12/6
 */
@Slf4j
public class TestE {
    public TestE() {
        log.info("testE is created!");
    }
}
import lombok.extern.slf4j.Slf4j;

/**
 * @author : wuwensheng
 * @date : 15:15 2021/12/6
 */
@Slf4j
public class TestF {
    public TestF() {
        log.info("testF is created!");
    }
}

我在启动类下新建了一个test包。

image.png

这两个配置类分别加载TestE和TestF

import com.cmdc.bean.TestF;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 *
 * @author : wuwensheng
 * @date : 14:44 2021/12/6
 */
@Slf4j
@AutoConfigureAfter({TestEConfiguration.class})
@Configuration
public class AutoConfiguration {
    public AutoConfiguration() {
        log.info("执行。。。");
    }

    @Bean
    public TestF tetstF(){
        log.info("create testF!!");
        return new TestF();
    }
}
import com.cmdc.bean.TestE;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author : wuwensheng
 * @date : 14:48 2021/12/6
 */
@Slf4j
@Configuration
public class TestEConfiguration {
    public TestEConfiguration() {
        log.info("执行");
    }

    @Bean
    public TestE testC(){
        log.info("create testE!!!!!!");
        return new TestE();
    }
}

配置spring.factories文件

image.png

ok,那么现在在AutoConfiguration配置类中使用的是@AutoConfigureAfter,所以是TestEConfiguration先执行,AutoConfiguration后执行:

image.png

是的,然后将注解改成@AutoConfigureBefore

image.png

是对的! 这两个注解作用于标记了@Configuration的类!

总结

本文到此,随时补充。