Spring-Boot之@Enable*注解的工作原理

@enable*是springboot中用來啟用某一個功能特性的一類注解。其中包括我們常用的@SpringBootApplication注解中用于開啟自動注入的annotation@EnableAutoConfiguration,開啟異步方法的annotation@EnableAsync,開啟將配置文件中的屬性以bean的方式注入到IOC容器的annotation@EnableConfigurationProperties等。

一、觀察任一@Enable*注解的源碼,以@EnableAsync為例

@EnableAsync源碼

@EnableAsync的作用是啟用異步執(zhí)行,使標注@Async注解的方法能夠和其他方法異步執(zhí)行。讀者可以Google一下@EnableAsync這個注解的使用場景,本文不再贅述

我們發(fā)現(xiàn),這個注解的重點在我標紅的@Import({AsyncConfigurationSelector.class})這段代碼。解釋一下@ImportXxxSelector.class的作用。

1)@Import

用來導入一個或多個class,這些類會注入到spring容器中,或者配置類,配置類里面定義的bean都會被spring容器托管。在這里我們加入的AsyncConfigurationSelector.class放入Spring容器中管理。

2)AsyncConfigurationSelector.class

我們從源碼一直追溯這個類的父類,最終找到頂端的父類ImportSelector.class

追溯父類ImportSelector.class

打開ImportSelector.class閱讀源碼:
ImportSelector.class

Spring會把實現(xiàn)ImportSelector接口的類中的SelectImport方法返回的值注入到Spring容器中。這個方法的返回值必須是一個class的全類名的String[]。舉個例子:

public class MyImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.springboot.enable.User", "com.springboot.enable.Car"};
    }
}

spring容器會把com.springboot.enable包下的User和Car這兩個類放入容器中。
回歸正題,我們不是需要通過@Enable*注解開啟一些功能嘛?答案就我自定義的MyImportSelector中。簡單來說就是@Enable*會將XxxImportSelector放入容器中,當Spring啟動,會執(zhí)行selectImports(AnnotationMetadata annotationMetadata)方法,在這個方法中我們做了某些處理,使得和@Enable*搭配使用的注解生效。哈哈,是不是很繞,多閱讀兩遍,你就理解了。

還有一個和ImportSelector功能差不多的類,ImportBeanDefinitionRegistrar使用beanDefinitionRegistry對象將bean加入Spring容器中,源碼如下:

ImportBeanDefinitionRegistrar

二、小實驗:

下面我們做一個小實驗印證一下,下圖有三個包,每個包下分別有三個bean,他們都加了@Component注解,會被spring加入到容器中。

beans

User

Bird

Car

需求是,當注入dto和vo兩個包下的bean時,輸出一段話:echo bean :+ bean的全類名,注入entity包下的bean時,不輸出。

1)

創(chuàng)建EchoBeanPostProcessor.class,實現(xiàn)BeanPostProcessor接口,作用是實現(xiàn)上文的業(yè)務邏輯。我們同樣可以創(chuàng)建一個@EchoBean,然后通過AOP的方式實現(xiàn)。

//實現(xiàn)BeanPostProcessor接口的類,放入spring容器中后,容器啟動和關閉時會執(zhí)行以下兩個重寫的方法
public class EchoBeanPostProcessor implements BeanPostProcessor {

    //getter、setter省略,讀者在試驗的時候要加上
    private List<String> packages;

    //該方法在spring容器初始化前執(zhí)行
    @Override
    public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException {
        for (String pack : packages) {
            if (bean.getClass().getName().startsWith(pack)) {
                System.out.println("echo bean: " + bean.getClass().getName());
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String s) throws BeansException {
        return bean;
    }
}

2)

創(chuàng)建BamuImportBeanDefinitionRegistrar .class,實現(xiàn)ImportBeanDefinitionRegistrar

public class BamuImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

        //獲取EnableEcho注解的所有屬性的value
        Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(EnableEcho.class.getName());
        //獲取package屬性的value
        List<String> packages = Arrays.asList((String[]) attributes.get("packages"));

        //使用beanDefinitionRegistry對象將EchoBeanPostProcessor注入至Spring容器中
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(EchoBeanPostProcessor.class);
        //給EchoBeanPostProcessor.class中注入packages
        beanDefinitionBuilder.addPropertyValue("packages", packages);
        beanDefinitionRegistry.registerBeanDefinition(EchoBeanPostProcessor.class.getName(), beanDefinitionBuilder.getBeanDefinition());
    }
}

3)

創(chuàng)建注解@EnableEcho,ImportBamuImportBeanDefinitionRegistrar.class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({BamuImportBeanDefinitionRegistrar.class})
public @interface EnableEcho {
    //傳入包名
    String[] packages() default "";
}

4)

在springboot啟動類中加入我們創(chuàng)建的注解,并傳入指定的包名,執(zhí)行main方法:

@SpringBootApplication
@EnableEcho(packages = {"com.springboot.vo", "com.springboot.dto"})
public class BlogApplication {

    public static void main(String[] args) {

        ConfigurableApplicationContext context = SpringApplication.run(BlogApplication.class, args);
        context.close();
    }
}

控制臺輸出結(jié)果:只有dto和vo包下的bean初始化時輸出,entity包下的bean初始化時沒有輸出,試驗成功。

console result

以上就是springboot @Enable*注解的工作原理,如有錯誤還請讀者告知,感謝!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容