@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);
}
}