> 文章列表 > @Configuration的代理保证bean单例

@Configuration的代理保证bean单例

@Configuration的代理保证bean单例

背景

先看下面问题:

类A、B,类AppConfig是配置类加了@Configuration,并且对A、B加了@Configuration,要把beanA、beanB创建到spring容器中,注意创建B时,调用了getA()。

此时按照代码正常理解逻辑会打印两遍“new A()”,因为调用了两次new A()。但是实际你可以去试试,只打印了一遍。。。这就很奇怪了。。。

是因为spring默认bean是单例的这里不会创建两遍,那么spring是如何保证自己的“单例原则”没有被打破的往下看。。

public class A {public A(){System.out.println("new A()");}}public class B {public B(){System.out.println("new B()");}
}@ComponentScan("com.yonghui.yh")
@Configuration
public class AppConfig {@Beanpublic A getA(){System.out.print("new A()")return new A();}@Beanpublic B getB(){//这里调用了getA()getA();return new B();}
}public class ApplicationTest {public static void main(String[] args) {//初始化spring容器AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);// System.out.print("new A()")打印一遍}
}

其实源于我们AppConfig类中加了一个@Configuration,这个东西说明该类是一个配置类,spring会对这种加了@Configuration注解的类进行特殊处理,也就是传说中的代理,我们可以在容器启动时,看看这个类,可以发现他被cglib代理了。

AppConfig是一个配置类,任何一个类都可以被指定成为配置类,但是这个类并不一定需要加@Configuration注解,这个注解的作用就是能够是AppConfig能够产生一个代理对象,确保AppConfig类中@Bean创建的bean是单例的,如果没有AppConfig没有@Configuration就不是代理对象,那么出现@Bean方法相互调用会使单例原则被破坏。

public class ApplicationTest {public static void main(String[] args) {//初始化spring容器AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);System.out.println(context.getBean(AppConfig.class));// 因为AppConfig加了@Configuration: com.yonghui.yh.AppConfig$$EnhancerBySpringCGLIB$$4ce7fa7e@75881071}
}

有了思路去看看,到底是怎么做的,其实就是在ConfigurationClassPostProcessor bean工厂后置处理器中进行的。这个类实现了BeanDefinitionRegistryPostProcessor、而BeanDefinitionRegistryPostProcessor实现了BeanDefinitionPostProcessor。

(ConfigurationClassPostProcessor 这个处理器听说很重要很重要很重要,是spring开天辟地的五个BeanDefinition之一,我还没看,今天分享的是其中冰山一小🦶🏻,后面在学学)

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {

说的这就要看看cglib代理的一个大概流程。

补充

这里的cglib代理原理如下:通过Enhancerr生成了一个继承了A类的子类,并创建对象(代理对象),你可以清楚的看到setSuperclass、setCallback、、、不就是在创建子类嘛,而setCallback方法就是设置一个MethodInterceptor拦截器,“增强”就是在这里面做的。

你可以回顾一下jdk动态代理得区别,可以在梳理梳理。

public class A {public void a(){System.out.println("aaaa");}
}public class CglibA {public static void main(String[] args) {MethodInterceptor methodInterceptor = new MethodInterceptor() {public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("cglib before");Object invoke = methodProxy.invoke(o, null);System.out.println("cglib after");return invoke;}};//理解为通过Enhancer生成了一个继承了A类的子类,并创建对象(代理对象)Enhancer enhancer = new Enhancer();enhancer.setSuperclass(A.class);// 这里可以调用setCallbacks方法就传多个增强,行程链式增强(拦截器组)enhancer.setCallback(methodInterceptor);enhancer.setUseFactory(false);enhancer.setCallbackType(methodInterceptor.getClass());A a = (A) enhancer.create();a.a();/*打印:cglib beforeaaaacglib after*/}}

解答

回到上面问题一开始的问题“为啥打印一遍”,直接看下源码:(我只截取了关键代码,并附上注释,可以进去点一点,跟着我的注释思路,很清晰的,最好是自己创建一个配置类,debug一哈)

ConfigurationClassPostProcessor实现的接口BeanDefinitionRegistryPostProcessor的父类接口的BeanFactoryPostProcessor的postProcessBeanFactory()

@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));
}

enhanceConfigurationClasses(beanFactory)

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()) {//...省略部分源码.....//判断是否是一个全配置类 fullif (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {//...省略部分源码.....// 全配置类进行记录,如果是lite非全配置类,那么不用管spring该怎么new就怎么newconfigBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);}}//无全配置类记录,直接结束if (configBeanDefs.isEmpty()) {// nothing to enhance -> return immediatelyenhanceConfigClasses.end();return;}if (IN_NATIVE_IMAGE) {throw new BeanDefinitionStoreException("@Configuration classes need to be marked as " +"proxyBeanMethods=false. Found: " + configBeanDefs.keySet());}//遍历全配置类configBeanDefs开始代理  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 classbeanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);// Set enhanced subclass of the user-specified bean class//代理逻辑,返回代理类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()));}//设置实际的beanClass为这个代理类,后面用这个bd时,就是使用的这个代理过的类了//要去看看,对他增强了什么beanDef.setBeanClass(enhancedClass);}//...省略部分源码.....
}

想知道上面打印一次那个原因,就需要看看,对他增强了什么。ConfigurationClassEnhancer封装了对这个全配置类增强的逻辑(即上面演示的cglib、enhance等,其中增强拦截器就是BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor,主要在BeanMethodInterceptor中)

class ConfigurationClassEnhancer {// setCallbacks方法就传多个增强,行程链式增强(拦截器组)// BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor是这个类的内部类private static final Callback[] CALLBACKS = new Callback[] {new BeanMethodInterceptor(),new BeanFactoryAwareMethodInterceptor(),NoOp.INSTANCE};private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);private static final String BEAN_FACTORY_FIELD = "$$beanFactory";private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class);private static final SpringObjenesis objenesis = new SpringObjenesis();/*** 调用enhancer.enhance(configClass, this.beanClassLoader);*/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;}/*** Creates a new CGLIB {@link Enhancer} instance.*/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.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));enhancer.setCallbackFilter(CALLBACK_FILTER);enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());return enhancer;}/*** Uses enhancer to generate a subclass of superclass,* ensuring that callbacks are registered for the new subclass.*/private Class<?> createClass(Enhancer enhancer) {Class<?> subclass = enhancer.createClass();// Registering callbacks statically (as opposed to thread-local)// is critical for usage in an OSGi environment (SPR-5932)...Enhancer.registerStaticCallbacks(subclass, CALLBACKS);return subclass;}/*** Marker interface to be implemented by all @Configuration CGLIB subclasses.* Facilitates idempotent behavior for {@link ConfigurationClassEnhancer#enhance}* through checking to see if candidate classes are already assignable to it, e.g.* have already been enhanced.* <p>Also extends {@link BeanFactoryAware}, as all enhanced {@code @Configuration}* classes require access to the {@link BeanFactory} that created them.* <p>Note that this interface is intended for framework-internal use only, however* must remain public in order to allow access to subclasses generated from other* packages (i.e. user code).*/public interface EnhancedConfiguration extends BeanFactoryAware {}/*** Conditional {@link Callback}.* @see ConditionalCallbackFilter*/private interface ConditionalCallback extends Callback {boolean isMatch(Method candidateMethod);}/*** A {@link CallbackFilter} that works by interrogating {@link Callback Callbacks} in the order* that they are defined via {@link ConditionalCallback}.*/private static class ConditionalCallbackFilter implements CallbackFilter {private final Callback[] callbacks;private final Class<?>[] callbackTypes;public ConditionalCallbackFilter(Callback[] callbacks) {this.callbacks = callbacks;this.callbackTypes = new Class<?>[callbacks.length];for (int i = 0; i < callbacks.length; i++) {this.callbackTypes[i] = callbacks[i].getClass();}}@Overridepublic int accept(Method method) {for (int i = 0; i < this.callbacks.length; i++) {Callback callback = this.callbacks[i];if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {return i;}}throw new IllegalStateException("No callback available for method " + method.getName());}public Class<?>[] getCallbackTypes() {return this.callbackTypes;}}private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {/*** Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the* existence of this bean object.* @throws Throwable as a catch-all for any exception that may be thrown when invoking the* super implementation of the proxied method i.e., the actual {@code @Bean} method*/@Override@Nullablepublic 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-proxyif (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 enhancementreturn enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);}}/*** 判断是否正在被调用,同理:生命周期循环依赖的正在创建的bean* 这里和循环依赖相似:* 		判断正在调用的方法 和 正在创建bean的方法* 		1、如果	正在调用的方法 和 正在创建bean的方法 相同就会直接调用父类的方法(我们的@Bean方法)进行创建bean* 		2、如果不相同就会先去getBean*	这里不管是哪个方法都会进入代理的这个里面** 	例如:* 	创建beanA的生命周期中:* 		调用@Bean getA()方法时,此时记录set集合中:getA(),调用代理类的方法也是getA(),就会直接创建beanA,清空set集合中:getA()* 		调用	@Bean getB()方式时,此时记录set集合中:getB(),get()中又调用了getA(),(这里不管是哪个方法都会进入代理的这个里面),所以又会执行到* 		这里的判断当前执行(调用)的是getA(),当时当前创建的却是getB(),就会执行下面的resolveBeanReference(),resolveBeanReference()中会去getBean(),* 		保证了bean的单例,执行完getA(),后继续执行beanB的生命周期**/if (isCurrentlyInvokedFactoryMethod(beanMethod)) {if (logger.isInfoEnabled() &&BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +"assignable to Spring's BeanFactoryPostProcessor interface. This will " +"result in a failure to process annotations such as @Autowired, " +"@Resource and @PostConstruct within the method's declaring " +"@Configuration class. Add the 'static' modifier to this method to avoid " +"these container lifecycle issues; see @Bean javadoc for complete details.",beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));}//如果	正在调用的方法 和 正在创建bean的方法 相同就会直接调用父类的方法进行创建beanreturn cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);}//如果不相同就会先去getBeanreturn resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);}private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,ConfigurableBeanFactory beanFactory, String beanName) {// The user (i.e. not the factory) is requesting this bean through a call to// the bean method, direct or indirect. The bean may have already been marked// as 'in creation' in certain autowiring scenarios; if so, temporarily set// the in-creation status to false in order to avoid an exception.boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);try {if (alreadyInCreation) {beanFactory.setCurrentlyInCreation(beanName, false);}boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);if (useArgs && beanFactory.isSingleton(beanName)) {// Stubbed null arguments just for reference purposes,// expecting them to be autowired for regular singleton references?// A safe assumption since @Bean singleton arguments cannot be optional...for (Object arg : beanMethodArgs) {if (arg == null) {useArgs = false;break;}}}//getBean 去获取bean ,没有时就会先去创建那个bean的生命周期Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :beanFactory.getBean(beanName));if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {// Detect package-protected NullBean instance through equals(null) checkif (beanInstance.equals(null)) {if (logger.isDebugEnabled()) {logger.debug(String.format("@Bean method %s.%s called as bean reference " +"for type [%s] returned null bean; resolving to null value.",beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),beanMethod.getReturnType().getName()));}beanInstance = null;}else {String msg = String.format("@Bean method %s.%s called as bean reference " +"for type [%s] but overridden by non-compatible bean instance of type [%s].",beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),beanMethod.getReturnType().getName(), beanInstance.getClass().getName());try {BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();}catch (NoSuchBeanDefinitionException ex) {// Ignore - simply no detailed message then.}throw new IllegalStateException(msg);}}Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();if (currentlyInvoked != null) {String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);beanFactory.registerDependentBean(beanName, outerBeanName);}return beanInstance;}finally {if (alreadyInCreation) {beanFactory.setCurrentlyInCreation(beanName, true);}}}}