自动装配
自动装配可以说是 SpringBoot 的一个核心,它的基础就是 Spring4.0 增加的条件化配置。涉及到的注解有:
- @Condition 
- @Enable 
- @Import 
- @EnableAutoConfigure 
- … 
其实说白了就是在 SpringBoot 启动过程中,根据预先定义好的条件,判断是否可以创建相应的 Bean,那么如果要完成自动装配的功能,就需要有这么几步
- 定义自动装配类 ,用于创建 Bean 
- 配置自定义装配 
- 使用自定义装配类 
我们以 redisson-spring-boot-starter 为例,查看如何使用
定义自动装配类
我们在项目中引入 redisson-spring-boot-starter , 对于 starter 的命名有以下约定:
- 官方包命名一般是:spring-boot-starter-xxx,如 spring-boot-starter-data-redis 
- 第三方包命令一般是: xxx-spring-boot-starter,如 mybatis-plus-boot-starter,pagehelper-spring-boot-starter 
在其中我们可以看到有两个类: RedissonAutoConfiguration 与 RedissonProperties,其中 RedissonAutoConfiguration 中的代码如下:
 @Configuration@ConditionalOnClass({Redisson.class, RedisOperations.class})@AutoConfigureBefore(RedisAutoConfiguration.class)@EnableConfigurationProperties({RedissonProperties.class, RedisProperties.class})public class RedissonAutoConfiguration {
    @Autowired    private RedissonProperties redissonProperties;
    @Autowired    private RedisProperties redisProperties;
    @Autowired    private ApplicationContext ctx;
    @Bean    @ConditionalOnMissingBean(name = "redisTemplate")    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();        template.setConnectionFactory(redisConnectionFactory);        return template;    }
    //省略部分代码
    @Bean(destroyMethod = "shutdown")    @ConditionalOnMissingBean(RedissonClient.class)    public RedissonClient redisson() throws IOException {        Config config = null;        Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class, "getCluster");        Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class, "getTimeout");        Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, redisProperties);        int timeout;        if(null == timeoutValue){            timeout = 10000;        }else if (!(timeoutValue instanceof Integer)) {            Method millisMethod = ReflectionUtils.findMethod(timeoutValue.getClass(), "toMillis");            timeout = ((Long) ReflectionUtils.invokeMethod(millisMethod, timeoutValue)).intValue();        } else {            timeout = (Integer)timeoutValue;        }
        if (redissonProperties.getConfig() != null) {            try {                InputStream is = getConfigStream();                config = Config.fromJSON(is);            } catch (IOException e) {                // trying next format                try {                    InputStream is = getConfigStream();                    config = Config.fromYAML(is);                } catch (IOException e1) {                    throw new IllegalArgumentException("Can't parse config", e1);                }            }        } else if (redisProperties.getSentinel() != null) {            Method nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");            Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod, redisProperties.getSentinel());
            String[] nodes;            if (nodesValue instanceof String) {                nodes = convert(Arrays.asList(((String)nodesValue).split(",")));            } else {                nodes = convert((List<String>)nodesValue);            }
            config = new Config();            config.useSentinelServers()                .setMasterName(redisProperties.getSentinel().getMaster())                .addSentinelAddress(nodes)                .setDatabase(redisProperties.getDatabase())                .setConnectTimeout(timeout)                .setPassword(redisProperties.getPassword());        } else if (clusterMethod != null && ReflectionUtils.invokeMethod(clusterMethod, redisProperties) != null) {            Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, redisProperties);            Method nodesMethod = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");            List<String> nodesObject = (List) ReflectionUtils.invokeMethod(nodesMethod, clusterObject);
            String[] nodes = convert(nodesObject);
            config = new Config();            config.useClusterServers()                .addNodeAddress(nodes)                .setConnectTimeout(timeout)                .setPassword(redisProperties.getPassword());        } else {            config = new Config();            String prefix = "redis://";            Method method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");            if (method != null && (Boolean)ReflectionUtils.invokeMethod(method, redisProperties)) {                prefix = "rediss://";            }
            config.useSingleServer()                .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())                .setConnectTimeout(timeout)                .setDatabase(redisProperties.getDatabase())                .setPassword(redisProperties.getPassword());        }
        return Redisson.create(config);    }
    //省略部分代码
}
   复制代码
 
- @Configuration 注解表明该类是一个配置类 
- @ConditionalOnClass(RedisOperations.class) 表明在存在 RedisOperations 类的前提下才会加载配置 
- @AutoConfigureBefore(RedisAutoConfiguration.class) 表明该配置文件的顺序,在 RedisAutoConfiguration 之前配置 
- @EnableConfigurationProperties({RedissonProperties.class, RedisProperties.class}) 表明配置信息可由其传入 
- @ConditionalOnMissingBean(name = “redisTemplate”) 当容器中没有名字为 - redisTemplate的 Bean 时,自动创建一个
 
深入 @ConditionalOnClass
 @Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Conditional(OnClassCondition.class)public @interface ConditionalOnClass {
	/**	 * The classes that must be present. Since this annotation is parsed by loading class	 * bytecode, it is safe to specify classes here that may ultimately not be on the	 * classpath, only if this annotation is directly on the affected component and	 * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to	 * use this annotation as a meta-annotation, only use the {@link #name} attribute.	 * @return the classes that must be present	 */	Class<?>[] value() default {};
	/**	 * The classes names that must be present.	 * @return the class names that must be present.	 */	String[] name() default {};
}
   复制代码
 
@ConditionalOnClass 的基础是 @Conditional ,它是由 Spring4 引入的条件装配, 它关注于在运行阶段动态选择配置(与 @Profile 相比),
 @Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Conditional {
	/**	 * All {@link Condition Conditions} that must {@linkplain Condition#matches match}	 * in order for the component to be registered.	 */	Class<? extends Condition>[] value();
}
   复制代码
 
Conditional 可以指定多个 Condition ,如果所有的 Condition.matches() 方法返回 true,则条件满足,可以创建。
 public interface Condition {
	/**	 * Determine if the condition matches.	 * @param context the condition context	 * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}	 * or {@link org.springframework.core.type.MethodMetadata method} being checked	 * @return {@code true} if the condition matches and the component can be registered,	 * or {@code false} to veto the annotated component's registration	 */	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
   复制代码
 
以 @ConditionalOnClass 为例,其对应的条件是 OnClassCondition ,其继承自 FilteringSpringBootCondition , FilteringSpringBootCondition.matches 方法调用了一些模板方法用来完成条件校验
 @Order(Ordered.HIGHEST_PRECEDENCE)class OnClassCondition extends FilteringSpringBootCondition {
	//省略部分代码
	@Override	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {		ClassLoader classLoader = context.getClassLoader();		ConditionMessage matchMessage = ConditionMessage.empty();		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);		if (onClasses != null) {			List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);			if (!missing.isEmpty()) {				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)						.didNotFind("required class", "required classes").items(Style.QUOTE, missing));			}			matchMessage = matchMessage.andCondition(ConditionalOnClass.class)					.found("required class", "required classes")					.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));		}		List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);		if (onMissingClasses != null) {			List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);			if (!present.isEmpty()) {				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)						.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));			}			matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)					.didNotFind("unwanted class", "unwanted classes")					.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));		}		return ConditionOutcome.match(matchMessage);	}
	//省略部分代码
}
   复制代码
 
- OnClassCondition 对 ConditionalOnMissingClass 与 ConditionalOnClass 都有判断 
- 判断类是否存在,最终都是判断能否用类加载其加载 
配置自定义装配类
SpringBoot 能够识别自动依赖, 是因为其会去扫描 classpath:META-INF/spring.factories 中配置的信息,对于 redisson-spring-boot-starter ,在 spring.factories 中有如下配置:
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.redisson.spring.starter.RedissonAutoConfiguration
   复制代码
 
激活自动装配
一般我们的 SpringBoot 项目的启动类都会用注解@SpringBootApplication,而自动装配的激活就隐藏在其中。
 @SpringBootApplicationpublic class OnlineShopApplication {
	public static void main(String[] args) {		SpringApplication.run(OnlineShopApplication.class, args);	}
}
   复制代码
 
 @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 {   // 省略}
   复制代码
 
从以上得知 @SpringBootApplication 这个注解由: @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解组成,我们则可以接着往下分析这三个注解。
- @SpringBootConfiguration 只是对 @Configuration 注解进行了包装, 任何一个加了 @Configuration 注解的类都是一个 IOC 容器配置类,可以创建 Bean 
- @ComponentScan 注解的主要作用是扫描指定路径下标识了需要装配的类(默认扫描当前类路径),自动装配到 Spring IOC 容器管理。标识需要装配的类主要形式是:@Component 及派生自 @Component 注解的注解。 
- @EnableAutoConfiguration 自动装配类到 Spring IOC 容器管理,用于激活 @Enable 开头的注解,比如常见的 @EnableWebMvc 引入 springmvc 框架需要使用到的 bean 
 @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {    // 省略}
   复制代码
 
- @Import 主要用于导入 @Configuration 类,ImportSelector 和 ImportBeanDefinitionRegistrar 接口的实现类 
- ImportSelector 接口的主要作用是 判断那些类需要被导入 
- AutoConfigurationImportSelector 中会对在 spring.factories 中定义的 org.springframework.boot.autoconfigure.EnableAutoConfiguration 进行解析,然后通过过滤器等形式将符合条件的 Bean 导入 
举例说明
假设我们对外提供一个组件,其需要提供自动装配功能,那么我们可以提供自己的 starter ,
 public class MyComponent {
    public void doSomething() {        System.out.println("该组件会做一些事情");    }}
   复制代码
 
所以我们的 starter 可以命名为 mystart-spring-boot-starter ,创建新项目在其中引入 SpringBoot 的依赖支持
 <dependency>			<groupId>org.springframework.boot</groupId>			<artifactId>spring-boot-starter</artifactId>			<version>2.1.7.RELEASE</version>			<optional>true</optional></dependency>
   复制代码
 
创建自动配置类
创建配置类的作用是为了使 Spring 能够将组件纳入管理,以便其他类使用
 import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
@Configurationpublic class MyComponentAutoConfiguration {
    @Bean    public MyComponent myComponent() {        return new MyComponent();    }
}
   复制代码
 
目前的配置比较简单,没有配置一些依赖条件
配置自定义装配
如果要配置自定义装配,那么需要让 SpringBoot 能够识别自动依赖, SpringBoot 会去扫描 classpath:META-INF/spring.factories 中配置的信息, 所以在其中配置
 # Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=top.shaozuo.mystart.MyComponentAutoConfiguration
   复制代码
 
使用自定义装配
将自动装配设置好之后,就可以在另外的项目中使用了, 比如我们新建一个 first-app 的 SpringBoot 项目,在其中引入 mystart-spring-boot-starter 依赖,
 <dependency><groupId>top.shaozuo</groupId>	<artifactId>mystart-spring-boot-starter</artifactId>	<version>0.0.1-SNAPSHOT</version></dependency>
   复制代码
 
使用所依赖的组件
 @RunWith(SpringRunner.class)@SpringBootTestclass OrderServiceTest {
    @Autowired    private MyComponent myComponent;
    @BeforeEach    void setUp() throws Exception {    }
    @Test    void test() {        myComponent.doSomething();    }
}
   复制代码
 
执行后如下: 
评论