掃描文末二維碼或者微信搜索公衆號
菜鳥飛呀飛
,便可關注微信公衆號,閱讀更多Spring源碼分析文章java
經過Import註解,咱們有三種方式能夠向Spring容器中註冊Bean。至關於Spring中XML的標籤。git
@Import(RegularBean.class)
,這樣就能從容器中獲取到RegularBean的實例對象的。/** * 自定義的一個普通類 */
public class RegularBean {
}
複製代碼
/** * 在配置類上經過Import註解向Spring容器中註冊RegularBean */
@Configuration
@Import(RegularBean.class)
public class AppConfig {
}
複製代碼
public class MainApplication {
public static void main(String[] args) {
// 啓動容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// 獲取bean
RegularBean bean = applicationContext.getBean(RegularBean.class);
// 打印bean
// 打印結果: com.tiantang.study.components.RegularBean@7a675056
System.out.println(bean);
}
}
複製代碼
/** * 自定義的一個普通類 */
public class SelectorBean {
}
複製代碼
/** * 經過@Import導入DemoImportSelector類 * DemoImportSelector類是自定義的一個類,實現了ImportSelector接口 */
@Configuration
@Import(DemoImportSelector.class)
public class AppConfig {
}
複製代碼
/** * 經過實現ImportSelector接口來向Spring容器中添加一個Bean * 該類重寫了ImportSelector接口的selectImports()方法 */
public class DemoImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 該方法的返回值是一個String[]數組
// 向數組中添加類的全類名,這樣就能將該類註冊到Spring容器中了
return new String[]{"com.tiantang.study.components.SelectorBean"};
}
}
複製代碼
public class MainApplication {
public static void main(String[] args) {
// 啓動容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// 獲取bean
SelectorBean bean = applicationContext.getBean(SelectorBean.class);
// 打印bean
// 打印結果:com.tiantang.study.components.SelectorBean@4ef37659
System.out.println(bean);
}
}
複製代碼
/** * 自定義的一個普通類 */
public class RegistrarBean {
}
複製代碼
/** * 經過@Import導入DemoImportRegistrar類 * DemoImportRegistrar類是自定義的一個類 * 實現了ImportBeanDefinitionRegistrar接口 * 重寫了registerBeanDefinitions()方法 */
@Configuration
@Import(DemoImportRegistrar.class)
public class AppConfig {
}
複製代碼
/** * 經過實現ImportBeanDefinitionRegistrar接口, * 重寫registerBeanDefinitions()方法來向Spring容器彙總註冊一個bean */
public class DemoImportRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 經過BeanDefinitionBuilder來建立一個BeanDefinition(建造者設計模式瞭解一下)
// 這裏也能夠直接經過關鍵字new來建立一個BeanDefinition。因爲BeanDefinition是一個接口,接口是不能new的,所以須要new它的實現類
// 例如: GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
// genericBeanDefinition.setBeanClass(RegistrarBean.class);
// 上面兩行代碼徹底是下面兩行代碼等價的。固然也能夠new一個AnnotatedBeanDefinition。咱們寫的AppConfig類就是被Spring解析成一個AnnotatedBeanDefinition
// 這裏其實有不少API,例如BeanDefinitionRegistry中註冊bean的方法,BeanDefinition中爲bean設置相關特性的方法
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RegistrarBean.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
// 上面兩行代碼是將RegistrarBean的解析成BeanDefinition,下面則是向Spring中註冊RegistrarBean類對應的BeanDefinition
// 注意,調用registry類的registerBeanDefinition()方法時,咱們爲這個Bean指定了beanName。
registry.registerBeanDefinition("demoBean",beanDefinition);
}
}
複製代碼
public class MainApplication {
public static void main(String[] args) {
// 啓動容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// 獲取bean
RegistrarBean bean = applicationContext.getBean(RegistrarBean.class);
// 打印bean
// 打印結果:com.tiantang.study.components.RegistrarBean@2f465398
System.out.println(bean);
// 經過beanName來獲取bean
RegistrarBean demoBean = (RegistrarBean)applicationContext.getBean("demoBean");
// 打印結果:com.tiantang.study.components.RegistrarBean@2f465398
// 和上面的打印結果是同樣的,這也說明二者是同一個對象
System.out.println(demoBean);
}
}
複製代碼
經過上面的三個Demo,瞭解了Import註解的三種用法,是否是發現咱們不用經過@ConponentScan和@Component等註解,咱們也能向Spring容器中註冊Bean?那麼問題來了,爲何經過Import註解,就能實現向Spring容器中註冊bean?原理是什麼?github
自動
究竟是怎麼實現的呢?在Spring容器中,Spring容器要想爲某個類建立實例對象,就必須先把對應的class類解析爲BeanDefinition,而後才能實例化對象。那麼咱們經過Import註解的方式向容器中註冊Bean,也是必定
會先把要註冊的類的class解析爲BeanDefinition。ConfigurationClassPostProcessor
,而這個類就是BeanFactoryPostProcessor的實現類,參與了BeanFactory的建造。這個類處理了@Configuration、@ComponentScan等註解,實際上,Import註解也是在這一步被處理的。接下來就看下Import註解的實現原理。在Spring容器自動過程當中,會執行refresh()方法,refresh()方法中會調用postProcessBeanFactory()。在postProcessBeanFactory()方法中又會執行全部BeanFactoryPostProcessor後置處理器。那麼就會執行到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()方法,而在該方法中調用了processConfigBeanDefinitions()方法。下面是processConfigBeanDefinitions()的部分代碼(只保留了幾行和今天內容有關的代碼,能夠閱筆者的上一篇文章點擊此處查看上一篇文章,裏面詳細介紹了該方法的所有代碼)。spring
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 建立一個parser
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
do {
// 解析配置類,在此處會解析配置類上的註解(ComponentScan掃描出的類,@Import註冊的類,以及@Bean方法定義的類)
// 注意:這一步只會將加了@Configuration註解以及經過@ComponentScan註解掃描的類纔會加入到BeanDefinitionMap中
// 經過其餘註解(例如@Import、@Bean)的方式,在parse()方法這一步並不會將其解析爲BeanDefinition放入到BeanDefinitionMap中,而是先解析成ConfigurationClass類
// 真正放入到map中是在下面的this.reader.loadBeanDefinitions()方法中實現的
parser.parse(candidates);
// 將上一步parser解析出的ConfigurationClass類加載成BeanDefinition
// 實際上通過上一步的parse()後,解析出來的bean已經放入到BeanDefinition中了,可是因爲這些bean可能會引入新的bean,例如實現了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,或者bean中存在被@Bean註解的方法
// 所以須要執行一次loadBeanDefinition(),這樣就會執行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean註釋的方法
this.reader.loadBeanDefinitions(configClasses);
}
while (!candidates.isEmpty());
}
複製代碼
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass) throws IOException {
// 省略無關代碼...
// Process any @Import annotations
// 處理Import註解註冊的bean,這一步只會將import註冊的bean變爲ConfigurationClass,不會變成BeanDefinition
// 而是在loadBeanDefinitions()方法中變成BeanDefinition,再放入到BeanDefinitionMap中
processImports(configClass, sourceClass, getImports(sourceClass), true);
// 省略無關代碼...
return null;
}
複製代碼
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// 處理DeferredImportSelector的實現類,回調開發人員重寫的selectImports()方法
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
// 處理DeferredImportSelector的實現類,回調開發人員重寫的selectImports()方法
// 返回值是一個字符串數組,數組元素爲類的全類名,而後把全類名變爲SourceClass
// 爲何要變爲SourceClass呢?由於在此處解析時,Spring是經過SourceClass來解析類的
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 遞歸調用processImports,爲何要遞歸調用該方法?
// 由於上面返回的全類名所表示的類多是ImportSelector或者ImportBeanDefinitionRegistrar
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 處理ImportBeanDefinitionRegistrar類
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
// 在此處回調開發人員重寫的ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 處理經過Import註解導入的普通類,例如本次Demo中的RegularBean
// 這裏只須要直接調用processConfigurationClass()方法便可,把RegularBean當作一個配置類去解析
// 由於RegularBean這個類型可能加了@ConponentScan,@Bean等註解
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
複製代碼
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
// metadata中包含的就是咱們要註冊的類的信息,例如本次demo中的RegularBean、SelectorBean、RegistrarBean
AnnotationMetadata metadata = configClass.getMetadata();
// new一個BeanDefinition的實現類
AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
configBeanDef.setScope(scopeMetadata.getScopeName());
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 經過registry對象,將BeanDefinition註冊到BeanDefinitionMap中
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
configClass.setBeanName(configBeanName);
}
複製代碼
在實際工做當中,咱們常常會碰到帶有@Enable前綴的註解,一般咱們稱之爲開啓某某功能,例如@EnableAsync(開啓@Async註解實現異步執行的功能)、@EnableScheduling(開啓@Scheduling註解實現定時任務的功能)、@EnableTransactionManagement(開啓事物管理的功能)等註解,尤爲是如今絕大部分項目中都要SpringBoot框架搭建,接入了SpringCloud等微服務,碰見@Enable系列的註解更是屢見不鮮,例如:@EnableDiscoveryClient、@EnableFeignClients。既然這麼常見,那就頗有必要知道@Enable系列註解的原理了。docker
@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 Ordered.LOWEST_PRECEDENCE;
}
複製代碼
簡單介紹下@Target、@Retention、@Documented這三個註解的做用。@Target註解用來指明咱們定義的註解能夠做用在什麼地方,例如ElementType.TYPE表示能夠做用在類上、ElementType.METHOD表示能夠做用在方法上,其餘枚舉值能夠參考ElementType的枚舉類。@Retention註解是指明咱們定義的註解的生命週期,RetentionPolicy.RUNTIME表示在JVM虛擬機加載咱們的class文件時,仍保留咱們所寫的註解;RetentionPolicy.SOURCE表示註解在Java源文件中存在,當編譯成class文件時就去除了咱們定義的註解信息;RetentionPolicy.CLASS表示當類編譯成class文件時,咱們自定義的註解信息仍存在,但當虛擬機加載class文件時,不會加載咱們自定義的註解。@Documented註解表示註釋會成爲API文檔中展現。數據庫
/** * 該類繼承了AdviceModeImportSelector,而AdviceModeImportSelector實現了ImportSelector接口 * 在父類中重寫了selectImports(AnnotationMetadata importingClassMetadata)。 * 同時在父類中又重載了selectImports(AdviceMode adviceMode)。 */
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] { ProxyAsyncConfiguration.class.getName() };
case ASPECTJ:
return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
default:
return null;
}
}
}
複製代碼
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";
protected String getAdviceModeAttributeName() {
return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
}
// 重寫ImportSelector接口中的方法
@Override
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annoType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
if (attributes == null) {
throw new IllegalArgumentException(String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
}
// @EnableAsync註解中有個mode屬性,能夠指定一個值,此處是獲取指定的值。
// 開發人員在使用@EnableAsync若是沒有指定具體值,則使用@EnableAsync註解中的默認值AdviceMode.PROXY
AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName());
// 調用子類的selectImports()方法
String[] imports = selectImports(adviceMode);
if (imports == null) {
throw new IllegalArgumentException(String.format("Unknown AdviceMode: '%s'", adviceMode));
}
return imports;
}
// 重載selectImports()方法
protected abstract String[] selectImports(AdviceMode adviceMode);
}
複製代碼
@Async的做用就是讓方法異步執行,因此須要對目標方法進行加強,那麼能夠採用代理的方式或者AspectJ技術操做字節碼,對字節碼進行靜態織入,從而達到目標。設計模式
經過上面分析咱們知道,@Enable系列註解,就是向容器中註冊一個Bean,既然註冊一個Bean,咱們爲何不經過@Component等註解註冊呢?而要用@Import註解這種方式?數組
最後推薦一款本人所在公司開源的性能監控工具——
Pepper-Metrics
微信
Pepper-Metrics
是坐我對面的兩位同事一塊兒開發的開源組件,主要功能是經過比較輕量的方式與經常使用開源組件(jedis/mybatis/motan/dubbo/servlet
)集成,收集並計算metrics
,並支持輸出到日誌及轉換成多種時序數據庫兼容數據格式,配套的grafana dashboard
友好的進行展現。項目當中原理文檔齊全,且所有基於SPI
設計的可擴展式架構,方便的開發新插件。另有一個基於docker-compose
的獨立demo
項目能夠快速啓動一套demo
示例查看效果https://github.com/zrbcool/pepper-metrics-demo
。若是你們以爲有用的話,麻煩給個star
,也歡迎你們參與開發,謝謝:)掃描下方二維碼便可關注微信公衆號
菜鳥飛呀飛
,一塊兒閱讀更多Spring源碼。mybatis