// person 里的 book 和 Spring 容器中的是同一个 BookbookInSpringContainer= (Book) context.getBean("book"); BookbookInPerson= ((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 模式。
@Autowired @Qualifier("liteBook") private Book book;
@Bean public Person litePerson() { Personperson=newPerson(); // 在 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() { returnnewBook(); } }
在 Lite 模式下还会生成代理对象吗?在当前方法中调用另一个被 @Bean 注解标记的方法获取到的是 Spring 容器中的对象吗?
当被 @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 注解及其衍生注解的区别。
@Override publicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { intregistryId= System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { thrownewIllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { thrownewIllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId);
staticbooleanisConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { returnfalse; }
// Any of the typical annotations found? for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { returntrue; } }
// 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. */ @Override publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { intfactoryId= System.identityHashCode(beanFactory); // 确保当前方法只执行一次 if (this.factoriesPostProcessed.contains(factoryId)) { thrownewIllegalStateException( "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); }
ConfigurationClassEnhancerenhancer=newConfigurationClassEnhancer(); for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) { AbstractBeanDefinitionbeanDef= 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; }
// 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-- }
// Does the actual (non-CGLIB) superclass implement BeanFactoryAware? // If so, call its setBeanFactory() method. If not, just exit. if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) { return proxy.invokeSuper(obj, args); } returnnull; }