spring boot中的EnableXXX的實現原理

spring boot大大提高了spring的易用性,並且裏面包含很多神奇的註解例如:@EnableAutoConfiguration, @EnableAsync, @EnableCaching, @EnableScheduling等,每個註解背後都有非常強大的功能,開啓這些功能的方式卻非常的簡單。那麼,這到底是如何實現的呢?本篇博客就來討論一下如何實現一個這種EnableXXX的註解。

場景

這種用法的場景集中在,你作爲一個公共組建的開發者,想要通過一個lib來爲別人提供一個功能,但你不能控制package scan的範圍,所以你的包在maven中被引入以後,你的帶有@Component的class並不會被加載到spring中。

@Import說起

這個問題在spring中又是如此的簡單,通過一個@Import就可以輕鬆實現。下面來看一個demo。

假設我希望定義一個EnableMyFeature的註解,只要在spring boot的main方法所在的類中使用這個註解,就可以將我的一個名爲MyBean的class加入到spring容器中,可以這麼實現

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyBean.class)
public @interface EnableMyFeature {
}

這裏通過@Import(MyBean.class)的方式將MyBean加載到了spring容器中。

下面是MyBean的代碼

import javax.annotation.PostConstruct;

public class MyBean {
    @PostConstruct
    public void init() {
        System.out.println("myBean init .................");
    }
}

它什麼都沒做,只是在init方法中打印一個日誌,來標識它被spring加載了。

用這樣的方法,我們就可以通過EnableXXX這種形式的註解來啓動某個功能。

進一步瞭解@Import

下面是@Import的源代碼

/**
 * Indicates one or more <em>component classes</em> to import &mdash; typically
 * {@link Configuration @Configuration} classes.
 *
 * <p>Provides functionality equivalent to the {@code <import/>} element in Spring XML.
 * Allows for importing {@code @Configuration} classes, {@link ImportSelector} and
 * {@link ImportBeanDefinitionRegistrar} implementations, as well as regular component
 * classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).
 *
 * <p>{@code @Bean} definitions declared in imported {@code @Configuration} classes should be
 * accessed by using {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
 * injection. Either the bean itself can be autowired, or the configuration class instance
 * declaring the bean can be autowired. The latter approach allows for explicit, IDE-friendly
 * navigation between {@code @Configuration} class methods.
 *
 * <p>May be declared at the class level or as a meta-annotation.
 *
 * <p>If XML or other non-{@code @Configuration} bean definition resources need to be
 * imported, use the {@link ImportResource @ImportResource} annotation instead.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
 * @see Configuration
 * @see ImportSelector
 * @see ImportResource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();

}

首先,如果我們的功能比較簡單,需要加入的類不多的話,可以只使用@Import就可以搞定,因爲它的value屬性是數據類型。

如果場景比這複雜,把所有要注入的Bean都定義在一個class中,就類似常規情況下使用@Configuration那樣,然後將這個class注入,這個類中定義的bean就都會注入到spring容器中。

如果場景再複雜一些,你需要注入的bean是動態的,那麼可以配合@ImportBeanDefinitionRegistrar來使用,例如下面的例子

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyBeanRegister implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        String beanName = "myBean";
        BeanDefinition bd = new RootBeanDefinition(MyBean.class);
        beanDefinitionRegistry.registerBeanDefinition(beanName, bd);
    }
}

registerBeanDefinitions方法可以動態的通過beanDefinitionRegistry對象來加載bean,然後通過@Import將這個MyBeanRegisterimport到spring中就好了。

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