《spring设计思想》31-Spring组合注解

上一节介绍了spring注解中的范式注解以及@Component和@ComponentScan的简单实现原理。

这一节介绍一下spring的组合注解。

所谓组合注解跟java中的组合模式差不多,多个注解组合使用,以达到特殊的功能。

比如Spring-boot中的@SpringBootApplication注解,是个非常有代表性的组合注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //1
@EnableAutoConfiguration //2
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM,
				classes = AutoConfigurationExcludeFilter.class) }) //3
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 {};

}

我们看到@SpringBootApplication注解上有三个比较偏重逻辑的注解,@SpringBootConfiguration/@EnableAutoConfiguration/@ComponentScan.这三个注解被标注在同一个注解上,就能继承三个注解的所有能力:

@ComponentScan我们上一节已经梳理了他的生效原理,

这一节我们可以看下@SpringBootConfiguration和@EnableAutoConfiguration

1:@SpringBootConfiguration

/**
 * Indicates that a class provides Spring Boot application
 * {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
 * standard {@code @Configuration} annotation so that configuration can be found
 * automatically (for example in tests).
 * 

* Application should only ever include one {@code @SpringBootConfiguration} and * most idiomatic Spring Boot applications will inherit it from * {@code @SpringBootApplication}. * * @author Phillip Webb * @since 1.4.0 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }

看注解我们能知道,@SpringBootConfiguration其实就是一个特殊的@Configuration,表示是SpringBoot的配置类型,一般的被@Configuration标注的类会结合@Bean/@Import/@Configuration@PropertySource使用,在上一节的ConfigurationClassParser.java中的解析中,会特别处理这几种注解。

2-EnableAutoConfiguration注解:

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

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

看起来很复杂,其实@Enable***注解一般都是跟@Import/@Conditional注解搭配使用.

首先介绍一下@Import注解:查看javadoc显示,@Import会引入spring的组件(Component),一般引入@Configuration的配置类,或者是搭配ImportSelector或者是ImportBeanDefinitionRegistrar的实现类来加载BeanDefinition

查看简单的例子:下面的demo包括注释掉的三个@import代表了Import的四种用法

//@Import(AnnotationPojo.class) //引入一个@Component
//@Import(MyConfigurationDemo.class) //引入一个@Configuration
//@Import(MyImportSelector.class) //引入一个ImportSelector的实现类
@Import(MyImportBeanDefinitionRegistrar.class) //引入一个ImportBeanDefinitionRegistrar的实现类
public class ImportAnnotationDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(ImportAnnotationDemo.class);

        configApplicationContext.getBean(AnnotationPojo.class);
        configApplicationContext.close();
    }
}

其实@Import的具体使用逻辑还是在上一节我们讲到的ConfigurationClassParser里面的#processImports方法具体解析

所以,@EnableConfiguration注解就是借助了@import的能力,引入了AutoConfigurationImportSelector.class这个特殊的类型,做了一些特殊的处理,这个我们在学习Spring-boot的时候具体解析。

下面再讲一个比较通用的@Conditional注解。

@Conditional注解必须搭配一个特殊的Condition接口的实现,达到编程化的效果:

例子:

@Configuration
public class ConditionalDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(ConditionalDemo.class);

        ConfigurableEnvironment configurableEnvironment = context.getEnvironment();
        configurableEnvironment.setActiveProfiles("dev");
        configurableEnvironment.addActiveProfile("sit");

        context.refresh();
        System.out.println(context.getBean("tips",String.class));
        context.close();
    }


    @Bean(name="tips")
//    @Profile("dev")
    @Conditional(ProfileDevConditional.class)
    public String devTips(){
        return "devTips";
    }

    @Bean(name="tips")
    @Profile("sit")
    public String sitTips(){
        return "sitTips";
    }
}

有个特殊的类ProfileDevConditional.class实现了Condition的接口

public class ProfileDevConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().acceptsProfiles("dev");
    }
}

matches方法表明:当环境的profile包括dev的时候,当前判断返回true,条件满足

那么我们的String devTips才会被加载到上下文中,那输出的话,就会是devTips。

上面我们讲的@EnableAutoConfiguration和@SpringBootConfiguration是依赖了底层的@Import和Condition接口实现的。所以这两个注解是组合注解。那么下一节我们讲一下Condition接口在Spring 中是如何作用的。

你可能感兴趣的:(spring,组合注解,Import注解,Enable注解,Conditional注解)