Spring Boot(2) 註解解析

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

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章