上一节介绍了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 中是如何作用的。