@Setter@Getterstatic class Person { private Book book; private Book anotherBook;}static class Book {}@Configurationstatic class FullConfig { @Bean public Person person() { Person person = new Person(); person.setBook(book()); return person; } @Bean public Book book() { return new Book(); }}
private static final String LABEL_VALUE = "$$SpringCGLIB$$";@Test@SneakyThrowspublic void testFullConfig() { ApplicationContext context = new AnnotationConfigApplicationContext(FullConfig.class); FullConfig config = context.getBean(FullConfig.class); // 生成的是 config 代理对象 assertThat(config.getClass().getName()).contains(LABEL_VALUE); // person 里的 book 和 Spring 容器中的是同一个 Book bookInSpringContainer = (Book) context.getBean("book"); Book bookInPerson = ((Person) (context.getBean("person"))).getBook(); assertThat(bookInPerson).isSameAs(bookInSpringContainer);}
Full 模式下会为配置类生成代理对象,代理对象的 className 中包含 $$SpringCGLIB$$ 字符串,表明该对象由 Spring CGLib 生成。
在 person() 方法中调用 book() 方法获取到的 Book 对象与 Spring 容器中的 Book 对象是同一个。也就是说在调用 book() 方法时会去 Spring 容器中查看是否存在 Book 对象,如果已经存在,那么直接使用该对象,而不会再去执行 book() 方法创建新的 Book 对象,如果 Spring 容器中不存在 Book 对象,才会创建新的 Book 对象。
1.2 Lite 模式
Lite 模式,即精简模式。移除 @Configuration 注解,取而代之的是 @Component 注解,或者使用 @ComponentScan、@Import 等注解将类扫描、导入进 Spring 容器,最终都将采用 Lite 模式。
@Componentstatic class LiteConfig { @Autowired @Qualifier("liteBook") private Book book; @Bean public Person litePerson() { Person person = new Person(); // 在 IDEA 中这样调用时会出现 error 提示: // Method annotated with @Bean is called directly. Use dependency injection instead. person.setBook(liteBook()); // 也可以通过方法参数传进来 person.setAnotherBook(book); return person; } @Bean public Book liteBook() { return new Book(); }}
在 Lite 模式下还会生成代理对象吗?在当前方法中调用另一个被 @Bean 注解标记的方法获取到的是 Spring 容器中的对象吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Testpublic void testLiteConfig() { ApplicationContext context = new AnnotationConfigApplicationContext(LiteConfig.class); LiteConfig config = context.getBean(LiteConfig.class); // 生成的是原始对象 assertThat(config.getClass().getName()) .doesNotContain(LABEL_VALUE) .isEqualTo(LiteConfig.class.getName()); // person 中的 book 与 Spring 容器中的不是同一个 Person person = (Person) context.getBean("litePerson"); Book liteBook = (Book) context.getBean("liteBook"); assertThat(person.getBook()).isNotSameAs(liteBook); assertThat(person.getAnotherBook()).isSameAs(liteBook);}
在 Lite 模式下不会生成代理对象,在当前方法中调用另一个被 @Bean 注解标记的方法获取到的不再是 Spring 容器中的对象,此时被 @Bean 注解标记的方法只是一个普通的工厂方法。
由于不再生成代理对象,对于类或方法的设计不再有要求,它们可以是 final 的,方法还可以是 private 的。
如果要确保 Person 对象中的 Book 对象与 Spring 容器中的是同一个,可以通过方法参数将 Book 传进来,也可以使用 @Autowired 注解注入 Book 对象,然后使用。
当被 @Bean 标记的方法存在于未被 @Configuration 注解标记的类里时,这些方法将以 Lite 模式处理,反之则以 Full 模式处理。
与 Full 模式不同,Lite 模式不能声明 Bean 之间的依赖关系。在 Lite 模式下,@Bean 标记的方法调用其他 @Bean 标记的方法时,后者被认为是一个普通的工厂方法,返回的是一个普通对象,而不是被 Spring 管理的 Bean。除此之外,Lite 模式下不会创建 CGLib 动态代理对象,在类设计方面没有限制(类可以是 final 的,被 @Bean 标记的方法可以是 private 或 final 的)。
通常情况下,@Bean 注解应当与 @Configuration 注解一起使用,确保始终使用 Full 模式,使跨方法的调用能够被重定向到 Spring 容器的生命周期的管理,减少在 Lite 模式下难以定位的 BUG。
2. 源码分析
2.1 提出问题
被 @Configuration 注解标记的类也会被 Spring 托管,但这个 Bean 并不是原始对象,而是代理对象,这也是与 @Component 注解及其衍生注解的区别。
private final Set<Integer> registriesPostProcessed = new HashSet<>();private final Set<Integer> factoriesPostProcessed = new HashSet<>();@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);}
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)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // --snip--}
private static final Set<String> candidateIndicators = Set.of( Component.class.getName(), ComponentScan.class.getName(), Import.class.getName(), ImportResource.class.getName());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);}
一共三种判断:
不考虑接口和注解
是否存在典型注解,存在的情况下直接返回 true,也就是将采用 Lite 模式;
配置类中是否存在被 @Bean 注解标记的方法,如果存在,也采用 Lite 模式。
到此,配置类对应的 BeanDefinition 已经被标记了将要采取 Full 模式还是 Lite 模式。
/** * 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); // 还记得在 postProcessBeanDefinitionRegistry() 方法中的判断吗? 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));}
遍历 BeanDefinition,找到采用 Full 模式的配置类对应的 BeanDefinition,将它们放到 configBeanDefs 中;
遍历 configBeanDefs 对采用 Full 模式的配置类进行增强。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
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()) { // 对应第一点 } if (configBeanDefs.isEmpty()) { // nothing to enhance -> return immediately enhanceConfigClasses.end(); return; } ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(); for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) { // 对应第二点 } enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();}
看看是如何增强的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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) { if (logger.isTraceEnabled()) { logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " + "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName())); } beanDef.setBeanClass(enhancedClass); }}
并不会直接生成代理对象,而是获取到增强后的 Class 对象。 这很好理解,现在是后置处理 BeanDefinition 的阶段,如果直接生成代理对象要放在哪里呢?因此只能先获取到增强后的 Class 对象,后续在实例化 Bean 时根据这个 Class 对象生成代理对象。
ConfigurationClassEnhancer#enhance() 实现了增强的主要逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) { if (EnhancedConfiguration.class.isAssignableFrom(configClass)) { if (logger.isDebugEnabled()) { logger.debug(String.format("Ignoring request to enhance %s as it has " + "already been enhanced. This usually indicates that more than one " + "ConfigurationClassPostProcessor has been registered (e.g. via " + "<context:annotation-config>). This is harmless, but you may " + "want check your configuration and remove one CCPP if possible", configClass.getName())); } return configClass; } Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader)); if (logger.isTraceEnabled()) { logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s", configClass.getName(), enhancedClass.getName())); } return enhancedClass;}
static final Callback[] CALLBACKS = new Callback[] { new BeanMethodInterceptor(), new BeanFactoryAwareMethodInterceptor(), NoOp.INSTANCE};private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
public int accept(Method method) { for (int i = 0; i < this.callbacks.length; i++) { Callback callback = this.callbacks[i]; if (!(callback instanceof ConditionalCallback conditional) || conditional.isMatch(method)) { return i; } } throw new IllegalStateException("No callback available for method " + method.getName());}
@Override@Nullablepublic Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { // 通过代理对象获取 BeanFactory,这是怎么做到的? ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance); String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); // Determine whether this bean is a scoped-proxy if (BeanAnnotationHelper.isScopedProxy(beanMethod)) { // --snip-- } // 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)) { // --snip-- } if (isCurrentlyInvokedFactoryMethod(beanMethod)) { // --snip-- } return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);}
核心逻辑如下:
首先判断当前代理是否为作用域代理,此处显然不是;
然后判断获取的 Bean 是否是 FactoryBean,如果是,就去代理 getObject(),执行到 getObject() 方法时,去 Spring 容器中搜索需要的 Bean,此处显然也不是;
@Configurationstatic class FullConfig { @Bean public Person person() { Person person = new Person(); person.setBook(book()); return person; } @Bean public Book book() { return new Book(); }}