spring 3.0 之后出提供了@Configuration
和@Bean
注解,我们可以通过编码的方式达到和 XML 配置等同的效果。 我们可以把@Configuration
看作成<beans>
标签,把@Bean
看作成<bean>
标签。
一个汽车配置类 CarConfiguration
,包含 3 个工厂方法( @Bean
),分别是创建一个 Engine
对象和两个 Car
对象,在创建 Car
对象方法内调用创建 Engine
对象的方法。
示例代码如下:
@Configuration
public class CarConfiguration {
/**
* 发动机
*/
public static class Engine {
public Engine() {
System.out.println("Engine");
}
}
/**
* 汽车
*/
public static class Car {
private String name;
private Engine engine;
public Car(String name, Engine engine) {
this.name = name;
this.engine = engine;
}
public String getName() {
return name;
}
}
/**
* 造一个引擎
*/
@Bean
public Engine engine() {
return new Engine();
}
/**
* 造一辆 LP 700-4
*/
@Bean
public Car aventador() {
return new Car("LP700-4", engine());
}
/**
* 造一辆 X6
*/
@Bean
public Car bmw() {
return new Car("X6", engine());
}
}
那么问题来了, System.out.println("Engine")
这行代码会被执行几次?
揭密问题之前,我们来做个推理。我们知道 spring 中 bean 的 scope 默认是 singleton。上述代码上我们并没有在engine()
方法中指定其 scope,按照单例的语义,那么只应该创建Engine
对象一次。若因为engine()
方法被调用多次而创建了多个Engine
对象那岂不是违背了 scope 为 singleton 本身的语义。
带着问题,我们把程序跑起来,验证一下结果,果然和猜想中的一致,字符串 Engine
只会被打印一次。
那么问题又来了,spring 是如何做到的呢?
AOP! 对,我们很容易联想到 spring 中的 AOP,通过 AOP 我可以增强类的行为,诸如大家都很熟悉的声明式事物。
为了验证猜想,我们在 pom 中添加依赖 spring-boot-starter-actuator
,通过浏览器访问 http://<host>:<port>/beans
来查看 CarConfiguration
的相关信息,如下:
{
bean: "com.owenxh.springadvancedexamples.configure.CarConfiguration",
aliases: [ ],
scope: "singleton",
type: "com.owenxh.springadvancedexamples.configure.CarConfiguration$$EnhancerBySpringCGLIB$$7c72aec8",
resource: "null",
dependencies: [ ]
}
我们看到了 CarConfiguration$$EnhancerBySpringCGLIB
,这表明 CarConfiguration
已经被 spring 通过 CGLIB 来进行增强了。我们现在可以确定 spring 通过 aop 对标注了 @Bean
的工厂方法进行了拦截处以保证 scope 的语义正确性。
注:为了方便访问/beans
, 我们需要在properties
文件中添加如下配置:
management.security.enabled = false
新的问题又来了,spring 在什么时候对 @Configuration
类进行增强的呢?
这就需要去翻 spring 的源码了。
长话短说,最终我们找到两个类: ConfigurationClassPostProcessor
和 ConfigurationClassEnhancer
。
ConfigurationClassPostProcessor
是 BeanDefinitionRegistryPostProcessor
(从 BeanFactoryPostProcessor
派生)接口的实现,在 AbstractApplicationContext.refresh()
方法中被触发执行。
看一下 ConfigurationClassPostProcessor
中的方法 postProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)
的代码:
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);
}
// 增强 Configuration 类
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
在 enhanceConfigurationClasses(beanFactory)
这一行代码中用到了前面提到的 ConfigurationClassEnhancer
对 Configuration 类进行增强(代码过长,不贴了)。 增强后的 Configuration 内置了拦截器 BeanMethodInterceptor
和 BeanFactoryAwareMethodInterceptor
,这两个都是 ConfigurationClassEnhancer
中的内部类,我们来分别介绍。
BeanFactoryAwareMethodInterceptor
增强后的 Configuration 类实现EnhancedConfiguration
接口(也是ConfigurationClassEnhancer
中的内部类),BeanFactoryAwareMethodInterceptor
的作用就是给增强后的 Configuration 类注入BeanFactory
。
EnhancedConfiguration
代码如下:
public interface EnhancedConfiguration extends BeanFactoryAware {
}
BeanMethodInterceptor
BeanMethodInterceptor
中的逻辑就是决定直接执行@Bean
工厂方法创建 bean 或者从BeanFactory
中来获取 bean,核心代码如下:
public 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-proxy
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
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 enhancement
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// The factory is calling the bean method in order to instantiate and register the bean
// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
// create the bean instance.
// 省略 log 代码
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}
通过以上案例分析以及对源码的解析,我们进一步了解了@Configuration
注解。spring 通过 CGLIB 对标注了@Configuration
注解的 Configuration 类进行了增强,增加后的后被注入BeanFactory
,spring 会对@Bean
方法进行拦截,根据触发点的不同,以决定直接执行工厂方法还是从BeanFactory
中获取 bean。
推荐:Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现
上一篇:Sentinel Dashboard中修改规则优雅同步到Apollo
点击原文阅读更多
Original url: Access
Created at: 2019-05-10 14:09:41
Category: default
Tags: none
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论