目录

  • 前言
  • 注解驱动开发使用
    • 需求
    • 代码实现
    • 测试效果
  • 源码分析
    • BeanDefinitionRegistryPostProcessor接口
    • 解析BeanDefinition
    • 处理Bean上配置的注解
    • 处理@Import注解
    • 校验
    • 加载BeanDefinition
    • 注册Bean到Spring容器
    • 初始化对象
    • 扩展知识
  • 小结

前言

  • 在我们实际开发中,你是不是也经常使用各种@Enablexxx(如@EnableAspectJAutoProxy@EnableTransactionManagement)之类的注解呢?只要使用了它,某个功能(组件)就应用上了。为我们提供了很多便利。
  • 下面我们就从自己模仿写一个Enablexxx注解开始,一步步揭开注解驱动开发背后真相。

注解驱动开发使用

需求

  • 假设有这样一个需求:有比较多的微服务,接口请求要打印下请求参数、请求地址什么的。
  • 我的想法是使用AOP实现,可以把它封装成一个组件,引入项目中然后使用如@EnableWebLogAspect这样的注解就可以启用它了

这里方案排除Spring Boot自动装配spring.factories的方式 。本章主角是注解驱动开发,其实Spring Boot @EnableAutoConfiguration的原理也是注解驱动。

代码实现

  • WebLogAspect.java拦截controller打印参数的地方。

注意:下面我使用了@Component,但其实它并不会因为这个注解初始化,因为我的扫描包路径是com.zzq.core
之所以加@Component,是老版本要加Component、Bean、Configuration其一注解,或者派生,我用的是Spring 3.2.18的源码 registerBeanDefinitionForImportedConfigurationClass方法中ConfigurationClassUtils.checkConfigurationClassCandidate在检查 ,否则异常
Configuration problem: com.zzq.core.annotationdrivendevelopment.WebLogAspect was @Import’ed but is not annotated with @Configuration nor does it declare any @Bean methods; it does not implement ImportSelector or extend ImportBeanDefinitionRegistrar. Update the class to meet one of these requirements or do not attempt to @Import it.
我看了高版本的代码如 5.1.16.RELEASE没有这个限制,不用加注解

package com.zzq.annotationdrivendevelopment;import java.util.Map;import javax.servlet.http.HttpServletRequest;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.fasterxml.jackson.databind.ObjectMapper;/**** 2022年4月13日11:17:09* @author zzq* 打印日志 ,需要<aop:aspectj-autoproxy>标签否则 注解方式不生效*//**** 老版本要加Component、Bean、@Configuration其一注解,我用的是Spring 3.2.18的源码 registerBeanDefinitionForImportedConfigurationClass方法中ConfigurationClassUtils.checkConfigurationClassCandidate在检查 ,否则异常* Configuration problem: com.zzq.core.annotationdrivendevelopment.WebLogAspect was @Import'ed but is not annotated with @Configuration nor does it declare any @Bean methods; it does not implement ImportSelector or extend ImportBeanDefinitionRegistrar. Update the class to meet one of these requirements or do not attempt to @Import it.* */
@Component
@Aspect
public class WebLogAspect {private final ObjectMapper objectMapper = new ObjectMapper();@Pointcut("execution(* com.*.core.controller..*.*(..))")public void webLog() {System.out.println("webLog");}/*** 在切点之前织入** @param joinPoint* @throws Throwable*/@Before("webLog()")public void doBefore(JoinPoint joinPoint) throws Throwable {Object [] args = joinPoint.getArgs();// 开始打印请求日志ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();Map<String, String[]> parameterMap = request.getParameterMap();System.out.println("========================================== Request start ==========================================");System.out.println("URL            : " + request.getRequestURL().toString());System.out.println("HTTP Method    : " + request.getMethod());System.out.println("Class Method   : "+ joinPoint.getSignature().getDeclaringTypeName() +"."+ joinPoint.getSignature().getName());System.out.println("IP             :  " + request.getRemoteAddr());if (null != args && args.length>0) {StringBuilder sb = new StringBuilder();for (int i = 0; i < args.length; i++) {sb.append(objectMapper.writeValueAsString(args[i]) + "\t");}System.out.println("Request Args   :  " +sb.toString());}else{System.out.println("Request Args   :  " );}System.out.println("========================================== Request end ==========================================");}}
  • 创建WebLogAspectImportSelector,选择导入刚才的WebLogAspect
 package com.zzq.annotationdrivendevelopment;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;public class WebLogAspectImportSelector implements ImportSelector{@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {System.out.println("使用注解驱动  ImportSelector");return new String[]{WebLogAspect.class.getName()};}}
  • 创建注解EnableWebLogAspect,导入WebLogAspectImportSelector
package com.zzq.annotationdrivendevelopment;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;/**** @author zzq* 2022年4月13日11:32:18* 开启日志的注解**/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(WebLogAspectImportSelector.class)
public @interface EnableWebLogAspect {}

前面的代码都可以封装成一个公共模块

  • 新建配置类,ConfigurationTest使用EnableWebLogAspect注解
@Configuration
@ComponentScan("com.zzq.core")
@EnableWebLogAspect // 启用
public class ConfigurationTest {}
  • 新建TestController,用于测试。

@Controller
@RequestMapping("/test")
public class TestController {@RequestMapping(value ="/test8")@ResponseBodypublic String test8(com.zzq.core.dto.TestReq testReq){System.out.println("test8 controller " + testReq.getUserId());return "test8 result :" + testReq.getUserId();}
}
  • 请求参数TestReq
package com.zzq.core.dto;
import java.lang.Integer;
public class TestReq {private Integer userId;public Integer getUserId() {return userId;}public void setUserId(Integer userId) {this.userId = userId;}
}

测试效果

  • 浏览器访问:http://localhost:8080/test_web/test/test8?userId=2,打印如下图所示。

源码分析

  • 下面我们就来揭开@EnableWebLogAspect使某功能(组件)启用的奥秘。

BeanDefinitionRegistryPostProcessor接口

  • 首先我们要定位到ConfigurationClassPostProcessor类(这个类一直是Spring实现注解开发的核心之一,到了Spring Boot时代依旧如此),它实现了BeanDefinitionRegistryPostProcessor接口,项目启动时就会去找BeanDefinitionRegistryPostProcessor的类,找到后for循环遍历调用其postProcessBeanDefinitionRegistry方法。代码片段如下图所示。

具体关于BeanDefinitionRegistryPostProcessor扩展接口详细信息,请参考拙作spring扩展 BeanDefinitionRegistryPostProcessor详解

  • 定位到ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {RootBeanDefinition iabpp = new RootBeanDefinition(ImportAwareBeanPostProcessor.class);iabpp.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(IMPORT_AWARE_PROCESSOR_BEAN_NAME, iabpp);//取得registry的id并做判重处理或记录int registryId = System.identityHashCode(registry);if (this.registriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanDefinitionRegistry already called for this post-processor against " + registry);}if (this.factoriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanFactory already called for this post-processor against " + registry);}//保存处理过的registry,避免重复处理this.registriesPostProcessed.add(registryId);//处理java配置形式的bean定义processConfigBeanDefinitions(registry);}

解析BeanDefinition

  • 进入ConfigurationClassPostProcessor#processConfigBeanDefinitions
 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();//加载当前已知所有bean定义for (String beanName : registry.getBeanDefinitionNames()) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);//   判断对应bean是否为配置类,如果是,则加入到configCandidates// Component、Bean、Configuration其一注解,或者派生if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were found//如果找不到 Component、Bean、Configuration其一注解,或者派生的类,则立即返回if (configCandidates.isEmpty()) {return;}// Detect any custom bean name generation strategy supplied through the enclosing application context// 3. 如果BeanDefinitionRegistry 是SingletonBeanRegistry 子类的话,由于我们当前传入的是DefaultListableBeanFactory,是// SingletonBeanRegistry 的子类。因此会将registry强转为SingletonBeanRegistrySingletonBeanRegistry singletonRegistry = null;if (registry instanceof SingletonBeanRegistry) {singletonRegistry = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet && singletonRegistry.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {// 如果localBeanNameGeneratorSet 等于false 并且SingletonBeanRegistry 中有 id 为 org.springframework.context.annotation.internalConfigurationBeanNameGenerator// 的bean .则将componentScanBeanNameGenerator,importBeanNameGenerator 赋值为 该bean.BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}// Parse each @Configuration class// 实例化ConfigurationClassParser 为了解析 各个配置类ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition();System.out.println("ConfigurationClassPostProcessor bd " + bd.getBeanClassName());if (bd.getBeanClassName().equals("com.zzq.core.configuration.ConfigurationTest")) {System.out.println("ConfigurationClassPostProcessor com.zzq.core.configuration.ConfigurationTest ");}try {if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parser.parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {//ConfigurationTest 会进入这里parser.parse(bd.getBeanClassName(), holder.getBeanName());}}catch (IOException ex) {throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);}}parser.validate();// Handle any @PropertySource annotationsStack<PropertySource<?>> parsedPropertySources = parser.getPropertySources();if (!parsedPropertySources.isEmpty()) {if (!(this.environment instanceof ConfigurableEnvironment)) {logger.warn("Ignoring @PropertySource annotations. " +"Reason: Environment must implement ConfigurableEnvironment");}else {MutablePropertySources envPropertySources = ((ConfigurableEnvironment)this.environment).getPropertySources();while (!parsedPropertySources.isEmpty()) {envPropertySources.addLast(parsedPropertySources.pop());}}}// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory,this.resourceLoader, this.environment, this.importBeanNameGenerator);}// 加载 BeanDefinition,里面会完成注册逻辑this.reader.loadBeanDefinitions(parser.getConfigurationClasses());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (singletonRegistry != null) {if (!singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}
  • 进入处理配置类ConfigurationClassParser#processConfigurationClass
 protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {AnnotationMetadata metadata = configClass.getMetadata();if (this.environment != null && metadata.isAnnotated(Profile.class.getName())) {AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) {return;}}if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) {// Explicit bean definition found, probably replacing an import.// Let's remove the old one and go with the new one.this.configurationClasses.remove(configClass);for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {if (configClass.equals(it.next())) {it.remove();}}}// Recursively process the configuration class and its superclass hierarchy.do {// 处理Beanmetadata = doProcessConfigurationClass(configClass, metadata);}while (metadata != null);// 添加到集合中this.configurationClasses.add(configClass);}

处理Bean上配置的注解

  • 进入真正处理配置类的地方,要处理好几种注解ConfigurationClassParser#doProcessConfigurationClass
 protected AnnotationMetadata doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {// Recursively process any member (nested) classes firstprocessMemberClasses(metadata);// Process any @PropertySource annotations//处理@PropertySource  加载外面资源文件AnnotationAttributes propertySource = MetadataUtils.attributesFor(metadata,org.springframework.context.annotation.PropertySource.class);if (propertySource != null) {processPropertySource(propertySource);}// Process any @ComponentScan annotations//处理  @ComponentScan 扫描包AnnotationAttributes componentScan = MetadataUtils.attributesFor(metadata, ComponentScan.class);if (componentScan != null) {// The config class is annotated with @ComponentScan -> perform the scan immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, metadata.getClassName());// scannedBeanDefinitions 有可能是空元素,因为在扫描包时会事先注册好// Check the set of scanned definitions for any further config classes and parse recursively if necessary// 如果有配置类,递归解析for (BeanDefinitionHolder holder : scannedBeanDefinitions) {if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());}}}//处理@Import注解// Process any @Import annotationsSet<Object> imports = new LinkedHashSet<Object>();Set<String> visited = new LinkedHashSet<String>();collectImports(metadata, imports, visited);if (!imports.isEmpty()) {processImport(configClass, metadata, imports, true);}// Process any @ImportResource annotationsif (metadata.isAnnotated(ImportResource.class.getName())) {AnnotationAttributes importResource = MetadataUtils.attributesFor(metadata, ImportResource.class);String[] resources = importResource.getStringArray("value");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// Process individual @Bean methods//处理@Bean注解Set<MethodMetadata> beanMethods = metadata.getAnnotatedMethods(Bean.class.getName());for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process superclass, if anyif (metadata.hasSuperClass()) {String superclass = metadata.getSuperClassName();if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// superclass found, return its annotation metadata and recurseif (metadata instanceof StandardAnnotationMetadata) {Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();return new StandardAnnotationMetadata(clazz.getSuperclass(), true);}else {MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superclass);return reader.getAnnotationMetadata();}}}return null;}

处理@Import注解

  • 我们本章要关注的就是处理@Import注解部分
  • 先收集导入的类collectImports
 private void collectImports(AnnotationMetadata metadata, Set<Object> imports, Set<String> visited) throws IOException {String className = metadata.getClassName();if (visited.add(className)) {if (metadata instanceof StandardAnnotationMetadata) {StandardAnnotationMetadata stdMetadata = (StandardAnnotationMetadata) metadata;for (Annotation ann : stdMetadata.getIntrospectedClass().getAnnotations()) {if (!ann.annotationType().getName().startsWith("java") && !(ann instanceof Import)) {collectImports(new StandardAnnotationMetadata(ann.annotationType()), imports, visited);}}//得到Import注解Map<String, Object> attributes = stdMetadata.getAnnotationAttributes(Import.class.getName(), false);if (attributes != null) {Class<?>[] value = (Class<?>[]) attributes.get("value");if (!ObjectUtils.isEmpty(value)) {for (Class<?> importedClass : value) {// Catch duplicate from ASM-based parsing...imports.remove(importedClass.getName());//把Import注解的值添加进去 imports.add(importedClass);}}}}else {for (String annotationType : metadata.getAnnotationTypes()) {if (!className.startsWith("java") && !className.equals(Import.class.getName())) {try {collectImports(new StandardAnnotationMetadata(this.resourceLoader.getClassLoader().loadClass(annotationType)),imports, visited);}catch (ClassNotFoundException ex) {// Silently ignore...}}}Map<String, Object> attributes = metadata.getAnnotationAttributes(Import.class.getName(), true);if (attributes != null) {String[] value = (String[]) attributes.get("value");if (!ObjectUtils.isEmpty(value)) {for (String importedClassName : value) {// Catch duplicate from reflection-based parsing...boolean alreadyThereAsClass = false;for (Object existingImport : imports) {if (existingImport instanceof Class &&((Class<?>) existingImport).getName().equals(importedClassName)) {alreadyThereAsClass = true;}}if (!alreadyThereAsClass) {imports.add(importedClassName);}}}}}}}

  • 找到要导入的类后,再进行处理processImport
 private void processImport(ConfigurationClass configClass, AnnotationMetadata metadata,Collection<?> classesToImport, boolean checkForCircularImports) throws IOException {if (checkForCircularImports && this.importStack.contains(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));}else {this.importStack.push(configClass);try {for (Object candidate : classesToImport) {Object candidateToCheck = (candidate instanceof Class ? (Class) candidate :this.metadataReaderFactory.getMetadataReader((String) candidate));//实现ImportSelector接口的处理if (checkAssignability(ImportSelector.class, candidateToCheck)) {// Candidate class is an ImportSelector -> delegate to it to determine importsClass<?> candidateClass = (candidate instanceof Class ? (Class) candidate :this.resourceLoader.getClassLoader().loadClass((String) candidate));ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);processImport(configClass, metadata, Arrays.asList(selector.selectImports(metadata)), false);}//实现ImportBeanDefinitionRegistrar接口的处理else if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) {// Candidate class is an ImportBeanDefinitionRegistrar ->// delegate to it to register additional bean definitionsClass<?> candidateClass = (candidate instanceof Class ? (Class) candidate :this.resourceLoader.getClassLoader().loadClass((String) candidate));ImportBeanDefinitionRegistrar registrar =BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);invokeAwareMethods(registrar);registrar.registerBeanDefinitions(metadata, this.registry);}else {//候选类不是importSelector或importBeanDefinitionRegistrar //@Configuration类的处理// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->// process it as a @Configuration classthis.importStack.registerImport(metadata,(candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate));processConfigurationClass(candidateToCheck instanceof Class ?new ConfigurationClass((Class) candidateToCheck, true) :new ConfigurationClass((MetadataReader) candidateToCheck, true));}}}catch (ClassNotFoundException ex) {throw new NestedIOException("Failed to load import candidate class", ex);}finally {this.importStack.pop();}}}

  • selector.selectImports便会执行到自定义的WebLogAspectImportSelector#selectImports导入我们的Aop类com.zzq.annotationdrivendevelopment.WebLogAspect,然后再递归调用processImport
  • 这一次会调用,注意new ConfigurationClass是imported为true,这个属性在后面会用到。
this.importStack.registerImport(metadata,(candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate));processConfigurationClass(candidateToCheck instanceof Class ?new ConfigurationClass((Class) candidateToCheck, true) :new ConfigurationClass((MetadataReader) candidateToCheck, true))
  • 又会进入ConfigurationClassParser#processConfigurationClass,处理完成后加入configurationClasses集合。

校验

  • 校验逻辑ConfigurationClassParser#validate
     public void validate() {for (ConfigurationClass configClass : this.configurationClasses) {if ("configurationTest".equals(configClass.getBeanName())) {System.out.println("ConfigurationClassParser validate ConfigurationTest");}configClass.validate(this.problemReporter);}}
  • 例如对于配置类的校验,ConfigurationClass#validate(如判断是否配置类方法名称重复,因为默认用方法名称作为beanName)
 public void validate(ProblemReporter problemReporter) {// A configuration class may not be final (CGLIB limitation)if (getMetadata().isAnnotated(Configuration.class.getName())) {if (getMetadata().isFinal()) {problemReporter.error(new FinalConfigurationProblem());}}// An @Bean method may only be overloaded through inheritance. No single// @Configuration class may declare two @Bean methods with the same name.Map<String, Integer> methodNameCounts = new HashMap<String, Integer>();for (BeanMethod beanMethod : this.beanMethods) {String fqMethodName = beanMethod.getFullyQualifiedMethodName();Integer currentCount = methodNameCounts.get(fqMethodName);int newCount = (currentCount != null ? currentCount + 1 : 1);methodNameCounts.put(fqMethodName, newCount);}for (String fqMethodName : methodNameCounts.keySet()) {int count = methodNameCounts.get(fqMethodName);// 判断是否配置类方法名称重复,因为默认用方法名称作为beanNameif (count > 1) {String shortMethodName = ConfigurationMethod.getShortMethodName(fqMethodName);problemReporter.error(new BeanMethodOverloadingProblem(shortMethodName, count));}}for (BeanMethod beanMethod : this.beanMethods) {beanMethod.validate(problemReporter);}}

加载BeanDefinition

  • 进入ConfigurationClassBeanDefinitionReader#loadBeanDefinitions,使用for循环一个个加载。
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {for (ConfigurationClass configClass : configurationModel) {loadBeanDefinitionsForConfigurationClass(configClass);}
}
  • 进入ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass,还记得前面递归调用processImport中最后new ConfigurationClass是imported为true吗?就是在这里应用的
     private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {// 还记得前面递归调用processImport中最后new ConfigurationClass是imported为true吗?就是在这里应用的// WebLogAspect  ConfigurationClass imported为trueif (configClass.isImported()) {registerBeanDefinitionForImportedConfigurationClass(configClass);}for (BeanMethod beanMethod : configClass.getBeanMethods()) {//载入、注册@Configuration注解的@Bean注解的每个方法loadBeanDefinitionsForBeanMethod(beanMethod);}loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());}

  • 往下进入ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass

前面说的Configuration problem: com.zzq.core.annotationdrivendevelopment.WebLogAspect was @Import’ed but is not annotated with @Configuration nor does it declare any @Bean methods; it does not implement ImportSelector or extend ImportBeanDefinitionRegistrar. Update the class to meet one of these requirements or do not attempt to @Import it.异常就是在这个方法产生的

 private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {AnnotationMetadata metadata = configClass.getMetadata();BeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);//判断是否 Component、Bean、Configuration其一注解,或者派生if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) {String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);this.registry.registerBeanDefinition(configBeanName, configBeanDef);configClass.setBeanName(configBeanName);if (logger.isDebugEnabled()) {logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName));}}else {this.problemReporter.error(new InvalidConfigurationImportProblem(metadata.getClassName(), configClass.getResource(), metadata));}}

注册Bean到Spring容器

  • 进入DefaultListableBeanFactory#registerBeanDefinition
 private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if("configurationTest".equals(beanName)){System.out.println(" registerBeanDefinition(String beanName, BeanDefinition beanDefinition) " + beanName);}if(null != beanName && beanName.indexOf( "AspectJPointcutAdvisor") > -1){System.out.println(" registerBeanDefinition AspectJPointcutAdvisor");}if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}// old? 还记得 “允许 bean 覆盖” 这个配置吗?allowBeanDefinitionOverridingBeanDefinition oldBeanDefinition;synchronized (this.beanDefinitionMap) {// 之后会看到,所有的 Bean 注册后会放入这个 beanDefinitionMap 中oldBeanDefinition = this.beanDefinitionMap.get(beanName);// 处理重复名称的 Bean 定义的情况if (oldBeanDefinition != null) {if (!this.allowBeanDefinitionOverriding) {// 如果不允许覆盖的话,抛异常throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +"': There is already [" + oldBeanDefinition + "] bound.");}else {if (this.logger.isInfoEnabled()) {this.logger.info("Overriding bean definition for bean '" + beanName +"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");}}}else {//添加beanDefinitionNamesthis.beanDefinitionNames.add(beanName);this.frozenBeanDefinitionNames = null;}// 没有就填加 有就覆盖this.beanDefinitionMap.put(beanName, beanDefinition);}if (oldBeanDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}}

初始化对象

  • 注册进去后,又会在DefaultListableBeanFactory#preInstantiateSingletons被初始化(初始化非懒加载对象)。

毫无悬念,最终又会调用经典的getBean方法初始化对象。

  • 这样我们的这个组件就算应用上了。

扩展知识

  • 在处理@Import注解时,processImport有这样一段代码。
...省略...
if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) {// Candidate class is an ImportBeanDefinitionRegistrar ->// delegate to it to register additional bean definitionsClass<?> candidateClass = (candidate instanceof Class ? (Class) candidate :this.resourceLoader.getClassLoader().loadClass((String) candidate));ImportBeanDefinitionRegistrar registrar =BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);invokeAwareMethods(registrar);registrar.registerBeanDefinitions(metadata, this.registry);
}
...省略...
  • 从源码中找到,还可以实现ImportBeanDefinitionRegistrar接口,去导入我们自定义的Bean。
  • 创建WebLogAspectImportBeanDefinitionRegistrar,实现ImportBeanDefinitionRegistrar接口方式导入
 package com.zzq.annotationdrivendevelopment;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class WebLogAspectImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {System.out.println("使用注解驱动  ImportBeanDefinitionRegistrar");BeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClassName(WebLogAspect.class.getName());registry.registerBeanDefinition("webLogAspect", beanDefinition );}
}
... 省略...
/**** @author zzq* 2022年4月13日11:32:18* 开启日志的注解**/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(WebLogAspectImportBeanDefinitionRegistrar.class)
public @interface EnableWebLogAspect {}
  • 这样也绕过了低版本源码中检查我问题,两种方式都可以。有关ImportBeanDefinitionRegistrar扩展接口的具体内容,请参考拙作ImportBeanDefinitionRegistrar

小结

  • 本文实现了2中自定义注解驱动开发的方式ImportBeanDefinitionRegistrarImportSelector。知道我们的组件是如何通过一个简单的注解去启用的。我们回顾下过程。

    • 1、进入BeanDefinitionRegistryPostProcessor的实现类ConfigurationClassPostProcessor,项目启动时会回调其postProcessBeanDefinitionRegistry方法。
    • 2、收集使用了Import的类。
    • 3、处理Import的类,回调方法,如果实现ImportSelector就是selectImports;如果是实现ImportBeanDefinitionRegistrar就调用registerBeanDefinitions
    • 4、向Spring容器注册导入的BeanDefinition
    • 5、容器初始化类,初始化后就相当于这个组件被启用了。
  • Spring Boot的EnableAutoConfiguration注解的原理很类似的,同样可以找到ImportSelector的踪迹,只是导入类调用了AutoConfigurationImportSelector#getAutoConfigurationEntry
...省略...
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}
  • 所以理解了本篇文章,对于看Spring Boot自动装配源码还是有一定帮助的。

Spring自定义注解驱动开发使用及源码分析相关推荐

  1. 【Spring】IOC:基于注解的IOC容器初始化源码分析

    从 Spring2.0 以后的版本中,Spring 也引入了基于注解(Annotation)方式的配置,注解(Annotation)是 JDK1.5 中引入的一个新特性,用于简化 Bean 的配置,可 ...

  2. SpringSecurity自定义Filter的ignoring()失效问题源码分析

    目录 问题 分析问题 服务器的Filter实现原理 注册Filter Filter过滤流程 Security Filter原理 代理类生成​​​​​​​ 配置的注入 Filter注册为bean的问题 ...

  3. 聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据转换:Converter.ConversionServic ...

  4. 聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)【享学Spring】

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 前言 数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是web框架),它能让框架更多的自动化,更好容 ...

  5. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 > 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAcce ...

  6. linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar

    压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...

  7. windows xp 驱动开发(七)WDK源码 UsbSamp例子的编译及使用

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 参考文章: http://msdn.microsoft.com/zh-cn/library/windows/ ...

  8. spring boot实战(第九篇)Application创建源码分析

    前言 通过前面的文章了解到在spring boot的启动时,利用的是编写的Application类,使用了注解@SpringBootApplication,本篇将阐述该Bean的加载过程. [html ...

  9. 【spring】Spring事件监听器ApplicationListener的使用与源码分析

    ApplicationEvent以及Listener是Spring为我们提供的一个事件监听.订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性. ...

最新文章

  1. vim文本编辑器使用技巧
  2. 【Linux 操作系统】阿里云服务器 操作实战 部署C语言开发环境(vim配置,gcc) 部署J2EE网站(jdk,tomcat)
  3. 210506阶段四Python基本语法
  4. Mysql基本用法-left join、right join、 inner join、子查询和join-02
  5. Python之smtpd及smtplib(邮件服务器及客户端)
  6. JavaScript 条件语句和循环语句
  7. 状态空间模型中参数的贝叶斯估计
  8. 一次性杯子机器人挂件手工制作_变废为宝 ———环保笔筒制作
  9. bluecam连接步骤说明_迈拓维距Type-C扩展坞手机连接电视图文教程
  10. 阿里巴巴Java编程规范试题答案
  11. Dell笔记本降低Bios版本简单而可靠的方法(1.15亦成功降级)
  12. 类对象模型实验:类数据成员大小
  13. 人工智能,机器学习, 深度学习框架图
  14. 无法打开msdn主页以及与微软相关的其他主页,但能打开其他网页
  15. markdown 做图 : Mermaid语法
  16. Learning to Rank 简介
  17. PyQt5-五十音图听写
  18. informatica odbc oracle,Informatica数据源配置
  19. Linux系统基本知识(4)
  20. windows下创建python虚拟环境

热门文章

  1. 计算机---网络基础小知识点11.14
  2. linux fonts目录,linux fonts
  3. 文学专业发语言学c刊,投c刊一般需要多少时间才回复
  4. arcgis中符号的旋转问题
  5. 电容的充放电过程及应用
  6. 自定义控件其实很简单 七
  7. cesium学习--初识
  8. server2012微软输入法切换_[WS2012]HOWTO:在 Windows Server 2012 中启用触摸键盘
  9. Markdown编辑器使用说明
  10. Win10 无人参与系统睡眠超时