对于 @Configuration 注解有一个特地经典的面试题:
- @Configuration 和 @Component 有什么区别?
无论小伙伴们之前是否背过相干的面试题,明天这篇文章学完之后置信大家对这个问题都会有更深一层的了解,废话不多少,咱们开始剖析。
1. 情景展示
@Configuration 和 @Component 到底有何区别呢?我先通过如下一个案例,在不剖析源码的状况下,小伙伴们先来直观感受一下这两个之间的区别。
@Configuration
public class JavaConfig01 {
}
@Component
public 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
@Override
public 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.
*/
@Override
public 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,当然,咱们这里也不属于这种状况。
接下来判断以后正在执行的办法,是否为容器中正在调用的工厂办法。
例如我有如下代码:
@Configuration
public 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 模式!一起食用成果更佳哦~