SpringBoot 加載外部Bean的幾種方式

Spring從3.0之後,就逐步傾向於使用java code config方式來進行bean的配置,在spring-boot中,這種風格就更為明顯了。在查看spring-boot工程的時候,總想著探究一下spring-boot如何簡單的聲明一個starter、Enable××,就能額外增加一個強大的功能,spring是如何找到這些具體的實現bean的呢。目前,我總結大概有這麼幾種:

  1. 直接在工程中使用@Configuration註解
    這個就是基本的java code方式,在@SpringBootApplication裡面就包含了@ComponentScan,因而會把工程中的@Configuration註解找到,並加以解釋。
  2. 通過@Enable××註解裡面的@Import註解
    我們在Enable某個功能時,實際上是通過@Import註解加載了另外的配置屬性類。
    例如: 如果要給工程加上定時任務的功能,只需要在某個配置文件上加上@EnableScheduling,實際上它是引入了SchedulingConfiguration.class,代碼如下:
<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}
/<code>

而SchedulingConfiguration就是一個標準的配置文件了,裡面定義了ScheduledAnnotationBeanPostProcessor這個bean。

<code>@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}

}
/<code>

有了ScheduledAnnotationBeanPostProcessor這bean,就會在context初始化時候,查找我們代碼中的@Scheduled,並把它們轉換為定時任務。

  1. 通過@EnableAutoConfiguration註解
    添加了這個異常強大的註解,spring-boot會利用AutoConfigurationImportSelector搜索所有jar包中META-INF文件夾中spring.factories,找到其中org.springframework.boot.autoconfigure.EnableAutoConfiguration的屬性值,並把它作為需要解析的@Configuration文件。
    例如:spring-cloud-commons裡面的spring.factories
<code># AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\\
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration
/<code>
  1. 自己實現ImportSelector
    AutoConfigurationImportSelector顯然有時候還是不夠用的,這時候就可以自己實現ImportSelector,實現更靈活的加載功能。
<code>public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
/<code>

ImportSelector接口定義了一個selectImports()方法,根據程序裡面定義的註解信息,動態返回能被加載的類列表。這個實現就非常靈活了,簡單的可以自己判斷註解,直接返回類名;複雜的可以自己定製類似AutoConfigurationImportSelector的功能。例如:我們看看spring-cloud的@EnableCircuitBreaker

<code>@Target(ElementType.TYPE) 

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableCircuitBreakerImportSelector.class)
public @interface EnableCircuitBreaker {

}
/<code>

發現,它引入了EnableCircuitBreakerImportSelector,它本身並沒有實現ImportSelector,而是其父類SpringFactoryImportSelector實現的。

<code>@Override
public String[] selectImports(AnnotationMetadata metadata) {
if (!isEnabled()) {
return new String[0];
}
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(this.annotationClass.getName(), true));

Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?");

// Find all possible auto configuration classes, filtering duplicates
// 調用SpringFactoriesLoader的loadFactoryNames去加載
List<string> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));

//省略了錯誤判斷和多於一個的log

return factories.toArray(new String[factories.size()]);
}
/<string>/<code>

這裡,我們看到,實際加載的代碼是傳入了this.annotationClass,那麼對於EnableCircuitBreakerImportSelector來說,就是在spring.factories找它的全類名:org.springframework.cloud.client.circuitbreaker.EnableCircuitBreakerImportSelector對應的值。最終在spring-cloud-netflix-core-××.jar的pring.factories中找到如下配置

<code>org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\\
org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration
/<code>

這樣就完成了通過@EnableCircuitBreaker的註解,最終加載到Hystrix的實現ystrixCircuitBreakerConfiguration,實現了功能定義和具體實現的分離。

SpringBoot 加載外部Bean的幾種方式


分享到:


相關文章: