1. 程式人生 > 實用技巧 >Spring 高階話題

Spring 高階話題

1. Spring Aware

1.1 基礎知識

Spring 的依賴注入,即所有的 Bean 對 Spring 容器的存在是沒有意識的。就說明 Bean 之間的耦合度很低。
但在實際專案中,要想用到 Spring 容器中所提供的資源就需要使 Bean 意識到容器 Spring 的存在,才能呼叫 Spring 所提供的資源,這就是所謂的 Spring Aware。若使用了 Spring Aware ,你的 Bean 將會和 Spring 框架耦合。
Spring 提供的 Aware 介面如下表所示:

函式名 函式功能
BeanNameAware 獲得到容器中 Bean 的名稱
BeanFactoryAware 獲得當前 bean factory,這樣可以呼叫容器的服務
ApplicationContextAware* 當前application context,這樣可以呼叫容器的服務
MessageSourceAware 獲得message source,這樣可以獲得文字資訊
ApplicationEventPublisherAware 應用事件釋出器,可以釋出事件
ResourceLoaderAware 獲得資源載入器,可以獲得外部資原始檔

Spring Aware 的目的是為了讓 Bean 獲得 Spring 容器的服務。因為 ApplicationContext 介面集成了 MessageSource 介面、ApplicationEventPublisher 介面和 ResourceLoader 介面,所以 Bean 繼承 ApplicationContextAware 可以獲得 Spring 容器的所有服務。

2. 多執行緒

2.1 基礎知識

Spring 通過任務執行器(TaskExecutor)來實現多執行緒和併發程式設計。使用 ThreadPoolTaskExecutor 可實現一個基於執行緒池的 TaskExecutor。而實際開發中任務一般是非阻礙的,即非同步的,所以我們要在配置類中通過 @EnbaleAsync 開啟對非同步任務的支援,並通過在實際執行的 Bean 的方法中使用 @Async 註解來宣告其是一個非同步任務。

3. 計劃任務

3.1 基礎知識

首先通過在配置類中註解 @EnableScheduling 來開啟對計劃任務的支援,然後在要執行計劃任務的方法上註解 @Scheduled ,宣告這是一個計劃任務。

4. 條件註解 @Conditional

4.1 基礎知識

@Conditional 根據滿足某一個特定條件建立一個特定的 Bean。比方說,當某一個 jar 包在一個類路徑下的時候,自動配置一個或多個 Bean;或者只有某個 Bean 被建立才會建立另外一個 Bean。總的來說,就是根據特定條件來控制 Bean 的建立行為,這樣我們可以利用這個特性來進行一些自動配置。

5. 組合註解與元註解

5.1 基礎知識

元註解 其實就是可以註解到別的註解上的註解,被註解的註解稱之為 組合註解,組合註解具備註解其上的元註解的功能。Spring 的很多註解都可以作為元註解,而且 Spring 本身已經有很多組合註解,如 @Configuration 就是一個組合 @Component 註解,表明這個類其實也是個 Bean。

6. @Enable* 註解的工作原理

6.1 基礎知識

@Enable* 的例子有:@EnableAspectJAutoProxy 開啟對 AspectJ 自動代理的支援;@EnableAsync 開啟非同步方法的支援;@EnableScheduling 開啟計劃任務的支援。

通過觀察這些 @Enable* 註解的原始碼,我們發向所有的註解都有一個 @Import 註解,@Import 是用來匯入配置類的,這也就意味著這些自動開啟的實現其實是匯入了一些自動配置的 Bean。這些匯入的配置方式主要分為以下三種類型。
(1)直接匯入配置類

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}

直接匯入配置類 SchedulingConfiguraiton,這個類註解了 @Configuration,且註冊了一個 scheduledAnnotationProcessor 的 Bean,原始碼如下:

@Configuration
@Role(2)
public class SchedulingConfiguration {
    public SchedulingConfiguration() {
    }

    @Bean(
        name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
    )
    @Role(2)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        return new ScheduledAnnotationBeanPostProcessor();
    }
}

(2)依據條件選擇配置類

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
    Class<? extends Annotation> annotation() default Annotation.class;

    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default 2147483647;
}

AsyncConfigurationSelector 通過條件來選擇需要匯入的配置類,AsyncConfigurationSelector 的根介面為 ImportSelector,這個介面需重寫 selectImports 方法,在此方法內進行事先條件判斷。此例中,若 adviceMode 為 PROXY,則返回 ProxyAsyncConfiguration 這個配置類;若 activeMode 為 ASPECTJ,則返回 AspectJAsyncConfiguration 配置類,原始碼如下:

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
    private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

    public AsyncConfigurationSelector() {
    }

    @Nullable
    public String[] selectImports(AdviceMode adviceMode) {
        switch(adviceMode) {
        case PROXY:
            return new String[]{ProxyAsyncConfiguration.class.getName()};
        case ASPECTJ:
            return new String[]{"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"};
        default:
            return null;
        }
    }
}

(3)動態註冊 Bean

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

AspectJAutoProxyRegistrar 實現了 ImportBeanDefinitionRegistrar 介面,它的作用是在執行時自動新增 Bean 到已有的配置類,通過重寫方法:

registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

其中,AnnotationMetadata 引數用來獲得當前配置類上的註解;BeanDefinitionRegistry 引數來註冊 Bean。原始碼如下:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    AspectJAutoProxyRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }

    }
}