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ÿ