@AliasFor
@AliasFor注解用于声明注解属性的别名
显性注解内别名
表示注解中的两个属性相互互为别名
public @interface ContextConfiguration {
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
// ...
}
实现要求:
- 形成注解对的属性都必须使用@AliasFor标注,并且相互指向对方。从Spring Framework 5.2.1开始可以自定义注解对中的一个即可。不过,还是建议定义一对,以增强兼容性并让代码更好的被注释。
- 属性对的返回类型必须相同
- 属性对必须定义默认值,且必须相同.
- 不能设置AliasFor注解的annotation属性
显性元注解属性别名
配置本注解的该属性就相当于配置了本注解的元注解的对应属性。简化了注解的属性配置。
@ContextConfiguration
public @interface XmlTestConfig {
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
String[] xmlFiles();
实现要求:
- @AliasFor必须指定annotation,且值对应的注解必须在本注解的元注解中
- 匹配的属性对返回类型必须相同
@SpringBootApplication
用于注解Spring Boot应用的启动类,等效于同时使用@Configuration、@EnableAutoConfiguration和@ComponentScanIndicates三个注解:
- @Configuration:允许被注解的类作为一个配置类,其内部有@Bean注解的方法将声明bean
- @EnableAutoConfigurationa:启动自动注解扫描,将加载自动注解
- @ComponentScan:配置扫描的范围.
例如
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
其主要代码如下
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
// TypeExcludeFilter会自动找到所有注册到BeanFactory的TypeExcludeFilter子类,并用这些子类排除扫描范围
// AutoConfigurationExcludeFilter当前读取所有依赖包的META-INF/spring.factories文件,读取自动配置类。所有不在其中的自动配置类将排除出扫描范围。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
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 {};
/**
* 设置被@Bean注解的方法,是否被包装,详见@Bean说明。
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@Bean
通过代码定义Bean,和Spring XML定义Bean类似
@Bean
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
Bean名称
可以通过@Bean注解的name属性定义Bean名称,如果未设置,则默认Bean名称是@Bean注解所在的方法名。
@Bean({"b1", "b2"})
public MyBean myBean() {
return obj;
}
设置Bean生成的Profile条件
@Bean
@Profile("production")
public MyBean myBean() {
return obj;
}
设置Bean的范围
@Bean
@Scope("prototype")
public MyBean myBean() {
return obj;
}
@DependsOn
设置本Bean依赖的Bean,控制Bean生成的顺序
@Lazy
设置Bean在使用时才生成,只对默认的singleton访问有效
@Primary
设置如果有多个类型匹配注入类型时,优先使用的Bean。
被@Configuration注解的类中的@Bean方法
@Configuration
public class AppConfig {
@Bean
public FooService fooService() {
return new FooService(fooRepository());
}
@Bean
public FooRepository fooRepository() {
return new JdbcFooRepository(dataSource());
}
// ...
}
一般@Bean注解的方法都在@Configuration注解的类中。这时,一个生成Bean的方法可以直接调用这个类中其他方法。这叫做Bean内引用,确保了Bean之间的引用是强类型并可导航,符合AOP语义(虽然是直接调用方法获取对象,但其实际获得的是包装的Bean对象,不是原始对象,因此AOP可以拦截此次调用)。其内部通过CGLIB的编织子类来实现,因此@Configuration注解的类必须不能是final或private的。
轻量级模式
在普通类或@Component注解的类中也可以通过@Bean注解的方法生成的Bean。这种@Bean方法被处理的过程叫做轻量级模式。
轻量级模式的Bean方法将被容器当做普通的工厂方法,其生成的对象不是被CGLIB修改的子类对象,而是原始Java对象。
轻量级模式下,当一个@Bean方法调用另一个@Bean方法时,这次调研是标准的Java方法调用,无法被CGLIB代理拦截。
@Component
public class Calculator {
public int sum(int a, int b) {
return a+b;
}
@Bean
public MyBean myBean() {
return new MyBean();
}
}
@Configuration
指示内部有1或多个@Bean 方法的类将被Spring容器处理以生成Bean。
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}
启动
通过AnnotationConfigApplicationContext加载
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
MyBean myBean = ctx.getBean(MyBean.class);
// use myBean ...
通过Spring XML加载
其中context:annotation-config/ 是必须的,用于启用 ConfigurationClassPostProcessor 等注解相关的PostProcessor(它们将帮助处理 @Configuration的类)
<beans>
<context:annotation-config/>
<bean class="com.acme.AppConfig"/>
</beans>
通过component scan
@Configuration注解有一个元注解是@Component,因此其注解的类可以使用component scan找到其他Bean,例如,以下通过构造函数自动注入其他Bean。
@Configuration
public class AppConfig {
private final SomeBean someBean;
public AppConfig(SomeBean someBean) {
this.someBean = someBean;
}
// @Bean definition using "SomeBean"
}
也可以通过@ComponentScan 配置扫描范围
@Configuration
@ComponentScan("com.acme.app.services")
public class AppConfig {
// various @Bean definitions ...
}
和其他注解组合
@Import
如下例,只需要注册AppConfig,就可以让容器处理DatabaseConfig和AppConfig两个配置类。
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return DataSource
}
}
@Configuration
@Import(DatabaseConfig.class)
public class AppConfig {
private final DatabaseConfig dataConfig;
public AppConfig(DatabaseConfig dataConfig) {
this.dataConfig = dataConfig;
}
@Bean
public MyBean myBean() {
// reference the dataSource() bean method
return new MyBean(dataConfig.dataSource());
}
}
@ImportResource
和@Import类似,不过@ImportResource用于导入Spring XML配置文件
@Configuration
@ImportResource("classpath:/com/acme/database-config.xml")
public class AppConfig {
@Inject DataSource dataSource; // from XML
@Bean
public MyBean myBean() {
// inject the XML-defined dataSource bean
return new MyBean(this.dataSource);
}
}
@Configuration
@Configuration内部可以嵌套@Configuration
当AppConfig配置类加载时同时加载内部的DatabaseConfig
@Configuration
public class AppConfig {
@Inject DataSource dataSource;
@Bean
public MyBean myBean() {
return new MyBean(dataSource);
}
@Configuration
static class DatabaseConfig {
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder().build();
}
}
}
@Profile
指定配置类生效的Profile
@Profile("development")
@Configuration
public class EmbeddedDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return embedded DataSource
}
}
@Profile("production")
@Configuration
public class ProductionDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return production DataSource
}
}
当然也可以在方法上和@Bean注解一起使用
@Configuration
public class ProfileDatabaseConfig {
@Bean("dataSource")
@Profile("development")
public DataSource embeddedDatabase() { ... }
@Bean("dataSource")
@Profile("production")
public DataSource productionDatabase() { ... }
}
@Conditional及各类ConditionalOn
@Conditional
控制组件是否被注册到Spring容器,只有所有的Condition都满足才会注册
- 用于注解类,该同时类直接或间接用@Component(包括@Configuration)注解,控制这个组件是否被注册
- 注解到有@Bean注解的方法,控制该Bean是否注册
- 作为注解的元注解,则该注解同样可以控制对象(如以上两条)是否注册
Conditional代码如下
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 条件类数组,必须所有条件类的match方法都返回true才表示满足条件
*/
Class<? extends Condition>[] value();
}
Condition代码如下
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
ConditionalOnXXX
Spring在包org.springframework.boot.autoconfigure.condition下提供了各种ConditionalOnXXX
以下以ConditionalOnJava注解类和OnJavaCondition条件类为例说明其实现
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 表示使用OnJavaCondition类进行判断
@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {
/**
* 定于区间,大于等于指定版本 或 低于指定版本
*/
Range range() default Range.EQUAL_OR_NEWER;
/**
* 要比较的Java版本
*/
JavaVersion value();
/**
* 区间选项
*/
enum Range {
/**
* 大于等于指定版本
*/
EQUAL_OR_NEWER,
/**
* 低于指定版本
*/
OLDER_THAN
}
}
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnJavaCondition extends SpringBootCondition {
// 获取当前Java版本
private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 通过metadata获取@ConditionalOnJava注解的属性值
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
Range range = (Range) attributes.get("range");
JavaVersion version = (JavaVersion) attributes.get("value");
return getMatchOutcome(range, JVM_VERSION, version);
}
protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
// 比较版本,并返回ConditionOutcome包装的结果(包含匹配结果和匹配说明信息)
boolean match = isWithin(runningVersion, range, version);
String expected = String.format((range != Range.EQUAL_OR_NEWER) ? "(older than %s)" : "(%s or newer)", version);
ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, expected)
.foundExactly(runningVersion);
return new ConditionOutcome(match, message);
}
/**
* Determines if the {@code runningVersion} is within the specified range of versions.
* @param runningVersion the current version.
* @param range the range
* @param version the bounds of the range
* @return if this version is within the specified range
*/
private boolean isWithin(JavaVersion runningVersion, Range range, JavaVersion version) {
if (range == Range.EQUAL_OR_NEWER) {
return runningVersion.isEqualOrNewerThan(version);
}
if (range == Range.OLDER_THAN) {
return runningVersion.isOlderThan(version);
}
throw new IllegalStateException("Unknown range " + range);
}
}