1.缘起
在看一段基于 spring security 的鉴权代码的时候,我发现一个有趣的 Bean 声明和方法调用。在一个 @Configuration 注解的配置类中用 @Bean 注解了一个方法 tokenStore,声明了 Spring bean: tokenStore。在 Spring 中,把 @Bean 注解的方法称为工厂方法,即用于创建 Spring bean 的方法。在同一个配置类另一个方法 configure 中调用了这个工厂方法 tokenStore 来获取 TokenStore 实例。
那么这会导致系统中存在多个 TokenStore 实例吗?如果是两个实例,则一个应该是 Spring bean 实例,一个是 configure 方法中通过 tokenStroe() 方法创建的实例。代码如下:
@Configuration
public class AuthorizationConfig {
@Bean
public TokenStore tokenStore() {
RedisTokenStore redis = new RedisTokenStore(connectionFactory);
return redis;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/*使用oauth2的密码模式时需要配置authenticationManager*/
endpoints.authenticationManager(authenticationManager);
//直接调用工厂方法获取 TokenStore 实例
endpoints.tokenStore(tokenStore())
...
}
...
}
复制代码
答案是否定的,在系统中只有一个实例,这个实例就是 spring bean。这是怎么做到的呢?继续阅读之前,可以闭上眼睛思考一下。
2.原理
通过调式代码,我们可以发现这个配置类已经被 CGLIB 代理了,这个配置类的实例类名变成了 AuthorizationConfig$$EnhancerBySpringCGLIB
,如图:
断点堆栈如图:
这个堆栈图从最底下向上看,配置类的 configure 方法调用本类工厂方法 tokenStore,变成了调用AuthorizationConfig$$EnhancerBySpringCGLIB
的tokenStore了,而这个调用被 ConfigurationClassEnhancer$$BeanMethodInterceptor 拦截器拦截,接着堆栈出现了我们熟悉的 Spring getBean 的调用堆栈(当 bean 不存在的时候,就会触发创建 bean)。
由此可见spring 容器通过 CGLIB 代理了配置类,调用配置类 @Bean 注解的工厂方法时,这个方法会被拦截。拦截器会通过 Spring 容器的机制去获取这个工厂方法上声明的 bean,如果这个bean 实例还不存在,Spring 容器会创建 bean 实例,而这个 bean 是通过配置类的 tokenStore 方法创建的,所以最终找到通过代理类调用到了配置类的 tokenStore 方法创建了 bean 实例。
3.机制探究
Spring 为 @Configuration 专门设计了一个 BeanFactoryPostProcessor 实现类ConfigurationClassPostProcessor,我们知道 Spring 在初始化加载 bean 的过程中,预留了 BeanFactoryPostProcessor 扩展点
。这个扩展点的执行时机是在 BeanFactory 初始化之后,所有的Bean定义已经被加载,但Bean的实例还没被创建(不包括 BeanFactoryPostProcessor 实例)的时候。Spring 会在这个时候调用 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法。这个扩展点通常用于修改 bean 的定义,bean 的属性值等。
我们来看看 ConfigurationClassPostProcessor 的 postProcessBeanFactory 方法实现(省略了很多代码,只列出关键部分,可以参考 spring 5.2 版本的源代码:
/**
* Prepare the Configuration classes for servicing bean requests at runtime
* by replacing them with CGLIB-enhanced subclasses.
* 通过 CGLIB 增强的子类来代替配置类来为 bean 请求提供支持
*/
@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));
}
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
...
//如果配置类是 full 模式,则将配置类加入到需要增强的配置类列表中
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
...
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
//遍历需要增强的配置类列表,为每个配置类实现增强逻辑
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
...
// 获取配置类的 Class 对象
Class<?> configClass = beanDef.getBeanClass();
// 增强实现
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
...
//将增强后的配置类设置到 bean 定义对象中
beanDef.setBeanClass(enhancedClass);
}
}
}
复制代码
从上面关键代码,我们可以看出 Spring 会为符合条件的 full 模式的配置类实施增强。
注: Full 模式和 lite 模式
Spring 把 bean 分成两类:full 模式和 lite 模式。在 @Configuration 注解的配置类中声明的 bean 就是 full 模式的,其他的 spring bean,比如在 @Component 注解的类中声明的 bean 都是 lite 模式。也就是说通常只有 @Configuration 注解的配置类需要增强,这也是 @Configuration 注解和其他类型的组件注解的一个重要的区别。
下面我们接着探索 ConfigurationClassEnhancer 是如何实现增强的,关键代码如下:
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
...
//说明已经增强过了,直接返回
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);
//设置增加类的命名策略,即增加 BySpringCGLIB,可以看前面调试贴图中的类名
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();
// 注册拦截器
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
return subclass;
}
复制代码
拦截器分析
增加逻辑主要是通过CGLIB enhancer 以配置类为父类创建一个代理子类,并设置了调用配置方法的时候,需要执行的拦截器。下面我们看看拦截器。
class ConfigurationClassEnhancer {
// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
复制代码
我们再来看看 BeanMethodInterceptor 的 intercept 实现逻辑:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
...
@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
...
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
return 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;
}
}
}
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
beanFactory.getBean(beanName));
if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
// Detect package-protected NullBean instance through equals(null) check
if (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);
}
}
}
复制代码
这里面和我们主题相关的就是 BeanMethodInterceptor。这个拦截器的主要逻辑就是拦截对于 @Bean 注解方法的调用,并看声明的 Spring bean 是否已经存在,如果存在则直接返回容器中的 Spring bean。否则真正的配置类的方法创建 Spring bean 实例。
总结
Spring 利用 BeanFactoryPostProcessor 扩展点, 通过 CGLIB enhancer 增强了 @Configuration 注解的配置类。重载的方式是创建了一个新的以配置类为父类增强子类。对于配置类中 @Bean 注解的方法的调用将会被拦截器拦截。拦截器的逻辑是判断声明的 Spring bean 在容器中是否已经存在,如果存在则直接返回容器中的 Spring bean。否则真正的配置类的方法创建 Spring bean 实例。