扫描文末二维码或者微信搜索公众号
菜鸟飞呀飞
,便可关注微信公众号,阅读更多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