对于 @Configuration 注解有一个特地经典的面试题:
- @Configuration 和 @Component 有什么区别?
无论小伙伴们之前是否背过相干的面试题,明天这篇文章学完之后置信大家对这个问题都会有更深一层的了解,废话不多少,咱们开始剖析。
1. 情景展示
@Configuration 和 @Component 到底有何区别呢?我先通过如下一个案例,在不剖析源码的状况下,小伙伴们先来直观感受一下这两个之间的区别。
@Configurationpublic class JavaConfig01 {}@Componentpublic class JavaConfig02 {}
首先,别离向 Spring 容器中注入两个 Bean,JavaConfig01 和 JavaConfig02,其中,JavaConfig01 上增加的是 @Configuration 注解而 JavaConfig02 上增加的则是 @Component 注解。
当初,在 XML 文件中配置包扫描:
<context:component-scan base-package="org.javaboy.demo.p6"/>
最初,加载 XML 配置文件,初始化容器:
public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans_demo.xml"); JavaConfig01 config01 = ctx.getBean(JavaConfig01.class); JavaConfig02 config02 = ctx.getBean(JavaConfig02.class); System.out.println("config01.getClass() = " + config01.getClass()); System.out.println("config02.getClass() = " + config02.getClass()); }}
最终打印进去后果如下:
从下面这段代码中,咱们能够得进去两个论断:
- @Configuration 注解也是 Spring 组件注解的一种,通过一般的 Bean 扫描也能够扫描到 @Configuration。
- @Configuration 注解注册到 Spring 中的 Bean 是一个 CGLIB 代理的 Bean,而不是原始 Bean,这一点和 @Component 不一样,@Component 注册到 Spring 容器中的还是原始 Bean。
一个问题来了,@Configuration 标记的类为什么注册到 Spring 容器之后就变成了代理对象了呢?闭着眼睛大家也能猜到,必定是为了通过代理来加强其性能,那么到底加强什么性能呢?接下来咱们通过源码剖析来和小伙伴们梳理一下这里的条条框框。
2. 源码剖析
要了解这个问题,首先得联合咱们后面的文章@Configuration 注解的 Full 模式和 Lite 模式!,在该文中,松哥提到了 @Configuration 模式分为了 Full 模式和 Lite 模式,所以,对于 @Configuration 注解的解决,在加载的时候,就须要首先辨别进去是 Full 模式还是 Lite 模式。
负责 @Configuration 注解的是 ConfigurationClassPostProcessor,这个处理器是一个 BeanFactoryPostProcessor,BeanFactoryPostProcessor 的作用就是在 Bean 定义的时候,通过批改 BeanDefinition 来从新定义 Bean 的行为,这个松哥之前有过专门的文章介绍,不相熟的小伙伴能够先看看这里:
- Spring 中 BeanFactory 和 FactoryBean 有何区别?
同时,ConfigurationClassPostProcessor 也是 BeanDefinitionRegistryPostProcessor 的实例,BeanDefinitionRegistryPostProcessor 是干嘛的呢?
BeanDefinitionRegistryPostProcessor 是 Spring 框架中的一个接口,它的作用是在应用程序上下文启动时,对 BeanDefinitionRegistry 进行后置解决。具体来说,BeanDefinitionRegistryPostProcessor 能够用于批改或扩大应用程序上下文中的 BeanDefinition,即在 Bean 实例化之前对 BeanDefinition 进行批改。它能够增加、删除或批改 BeanDefinition 的属性,甚至能够动静地注册新的 BeanDefinition。通过实现 BeanDefinitionRegistryPostProcessor 接口,咱们能够在 Spring 容器启动过程中干涉 Bean 的定义,以满足特定的需要。这使得咱们能够在应用程序上下文加载之前对 Bean 进行一些自定义的操作,例如动静注册 Bean 或者批改 Bean 的属性。须要留神的是,BeanDefinitionRegistryPostProcessor 在 BeanFactoryPostProcessor 之前被调用,因而它能够影响到 BeanFactoryPostProcessor 的行为。
BeanFactoryPostProcessor 中的办法是 postProcessBeanFactory,而 BeanDefinitionRegistryPostProcessor 中的办法是 postProcessBeanDefinitionRegistry,依据后面的介绍,postProcessBeanDefinitionRegistry 办法将在 postProcessBeanFactory 办法之前执行。
所以,咱们就从 postProcessBeanDefinitionRegistry 办法开始看起吧~
2.1 postProcessBeanDefinitionRegistry
@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry);}
这个方面后面的代码次要是为了确保该办法执行一次,咱们就不多说了。关键在于最初的 processConfigBeanDefinitions 办法,这个办法就是用来决策配置类是 Full 模式还是 Lite 模式的。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } //省略。。。}
我省略了其余代码,大家看,这个办法中,会首先依据 beanName 取出来 BeanDefinition,而后判断 BeanDefinition 中是否蕴含 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE
属性,这个属性上记录了以后配置类是 Full 模式还是 Lite 模式,不同模式未来的解决计划必定也是不同的。如果是第一次解决,显然 BeanDefinition 中并不蕴含该属性,因而就会进入到 ConfigurationClassUtils.checkConfigurationClassCandidate 办法中,正是在该办法中,判断以后配置类是 Full 模式还是 Lite 模式,并进行标记,checkConfigurationClassCandidate 办法的逻辑也挺长的,我这里挑出来跟咱们感兴趣的局部:
static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { //省略。。。 Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; } //省略}
Full 模式状况很简略,就是如果配置类上存在 @Configuration 注解,并且该注解的 proxyBeanMethods 属性值不为 false,那么就是 Full 模式,这个跟松哥在 @Configuration 注解的 Full 模式和 Lite 模式!一文中的介绍是统一的。
Lite 模式就状况多一些,首先 config!=null
就是说当初也存在 @Configuration 注解,然而 proxyBeanMethods 属性值此时为 false,那么就是 Lite 模式(proxyBeanMethods 属性值为 true 的话就进入到 if 分支中了)。
另外就是在 isConfigurationCandidate 办法中有一些判断逻辑去锁定是否为 Lite 模式:
static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; } // Any of the typical annotations found? for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // Finally, let's look for @Bean methods... return hasBeanMethods(metadata);}
这个办法的判断逻辑是这样:
- 首先注解要是标记的是接口,那就不能算是 Lite 模式。
- 遍历 candidateIndicators,判断以后类上是否蕴含这个 Set 汇合中的注解,这个 Set 汇合中的注解有四个,别离是 @Component、@ComponentScan、@Import、@ImportResource 四个,也就是,如果类上标记的是这四个注解的话,那么也依照 Lite 模式解决。
- 判断以后类中是否有 @Bean 标记的办法,如果有则依照 Lite 模式解决,否则就不是 Lite 模式。
如果小伙伴们看过松哥之前的 @Configuration 注解的 Full 模式和 Lite 模式!一文,那么下面这些代码应该都很好了解,跟松哥在该文章中的介绍都是统一的。
好了,通过下面的解决,当初就曾经标 BeanDefinition 中标记了这个配置类到底是 Full 模式还是 Lite 模式了。
2.2 postProcessBeanFactory
接下来咱们就来看 postProcessBeanFactory 办法。
/** * Prepare the Configuration classes for servicing bean requests at runtime * by replacing them with CGLIB-enhanced subclasses. */@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { int factoryId = System.identityHashCode(beanFactory); if (this.factoriesPostProcessed.contains(factoryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + beanFactory); } this.factoriesPostProcessed.add(factoryId); if (!this.registriesPostProcessed.contains(factoryId)) { // BeanDefinitionRegistryPostProcessor hook apparently not supported... // Simply call processConfigurationClasses lazily at this point then. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } enhanceConfigurationClasses(beanFactory); beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));}
首先大家看一下这个办法的正文,正文说的很明确了,将 Configuration 类通过 CGLIB 进行加强,以便在运行时较好的解决 Bean 申请。
这个办法中还会再次确认一下 postProcessBeanDefinitionRegistry 办法曾经解决过了,如果没有解决的话,则会在该办法中调用 processConfigBeanDefinitions 去确认 Bean 应用的是哪种模式。
该办法的关键在于 enhanceConfigurationClasses,这个就是用来通过动静代理加强配置类的,当然这个办法也是比拟长的,我这里列出来一些要害的逻辑:
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance"); Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE); if ((configClassAttr != null || methodMetadata != null) && (beanDef instanceof AbstractBeanDefinition abd) && !abd.hasBeanClass()) { // Configuration class (full or lite) or a configuration-derived @Bean method // -> eagerly resolve bean class at this point, unless it's a 'lite' configuration // or component class without @Bean methods. boolean liteConfigurationCandidateWithoutBeanMethods = (ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) && annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata)); if (!liteConfigurationCandidateWithoutBeanMethods) { try { abd.resolveBeanClass(this.beanClassLoader); } } } if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) { configBeanDefs.put(beanName, abd); } } ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(); for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) { AbstractBeanDefinition beanDef = entry.getValue(); // If a @Configuration class gets proxied, always proxy the target class beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); // Set enhanced subclass of the user-specified bean class Class<?> configClass = beanDef.getBeanClass(); Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); if (configClass != enhancedClass) { beanDef.setBeanClass(enhancedClass); } } enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();}
这个办法的逻辑,我整体上将之分为两局部:
第一局部就是先找到 Full 模式的配置类的名称,存入到 configBeanDefs 汇合中。
具体寻找的逻辑就是依据配置类的模式去寻找,如果配置类是 Full 模式,就将之存入到 configBeanDefs 中。如果配置类是 Lite 模式,且里边没有 @Bean 标记的办法,那就阐明这可能并不是一个配置类,就是一个一般 Bean,那么就在这里加载类就行了。
第二步则是遍历 configBeanDefs 汇合,加强配置类。
这个如果大家理解 CGLIB 动静代理的话,这个就很好懂了,对于 CGLIB 动静代理松哥这里不啰嗦,最近更新的 Spring 源码视频中都有具体讲到。那么这里次要是通过 enhancer.enhance 办法来生成代理类的,如下:
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) { if (EnhancedConfiguration.class.isAssignableFrom(configClass)) { return configClass; } Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader)); return enhancedClass;}private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(configSuperClass); enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setAttemptLoad(true); enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes()); return enhancer;}
小伙伴们看到,加强类中的 setCallbackFilter 是 CALLBACK_FILTER,这个里边蕴含了几个办法拦截器,跟咱们相干的是 BeanMethodInterceptor,咱们来看下:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback { @Override @Nullable public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance); String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); // Determine whether this bean is a scoped-proxy if (BeanAnnotationHelper.isScopedProxy(beanMethod)) { String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName); if (beanFactory.isCurrentlyInCreation(scopedBeanName)) { beanName = scopedBeanName; } } // To handle the case of an inter-bean method reference, we must explicitly check the // container for already cached instances. // First, check to see if the requested bean is a FactoryBean. If so, create a subclass // proxy that intercepts calls to getObject() and returns any cached bean instance. // This ensures that the semantics of calling a FactoryBean from within @Bean methods // is the same as that of referring to a FactoryBean within XML. See SPR-6602. if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanFactory, beanName)) { Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName); if (factoryBean instanceof ScopedProxyFactoryBean) { // Scoped proxy factory beans are a special case and should not be further proxied } else { // It is a candidate FactoryBean - go ahead with enhancement return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName); } } if (isCurrentlyInvokedFactoryMethod(beanMethod)) { return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); } return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName); }}
本人写过 CGLIB 动静代理的小伙伴都晓得这里 intercept 办法的含意,这就是真正的拦挡办法了,也就是说,如果咱们的配置类是 Full 模式的话,那么未来调用 @Bean 注解标记的办法的时候,调用的其实是这里的 intercept 办法。
下面办法,首先会判断以后代理是否为作用域代理,咱们这里当然不是。
接下来判断申请的 Bean 是否是一个 FactoryBean,如果是,则须要去代理其 getObject 办法,当执行到 getObject 办法的时候,就去 Spring 容器中查找须要的 Bean,当然,咱们这里也不属于这种状况。
接下来判断以后正在执行的办法,是否为容器中正在调用的工厂办法。
例如我有如下代码:
@Configurationpublic class JavaConfig { @Bean User user() { User user = new User(); user.setDog(dog()); return user; } @Bean Dog dog() { return new Dog(); }}
那么如果是间接调用 dog() 办法,则 isCurrentlyInvokedFactoryMethod 返回 true,如果是在 user() 办法中调用的 dog() 办法,则 isCurrentlyInvokedFactoryMethod 返回 false。
当 isCurrentlyInvokedFactoryMethod 返回 true 的时候,就执行 invokeSuper 办法,也就是真正的触发 dog() 办法的执行。
当 isCurrentlyInvokedFactoryMethod 返回 false 的时候,则执行上面的 resolveBeanReference 办法,这个办法会先去 Spring 容器中查找相应的 Bean,如果 Spring 容器中不存在该 Bean,则会触发 Bean 的创立流程。
当初,小伙伴们应该明确了为什么 Full 模式下,调用 @Bean 注解标记的办法并不会导致 Bean 的反复创立了吧~
好啦,本文联合上文 @Configuration 注解的 Full 模式和 Lite 模式! 一起食用成果更佳哦~