Spring之@Bean方法
@Bean方法解析
一、bean实例化的三种方式
Spring中的一个Bean,需要实例化得到一个对象,而实例化就需要用到构造方法。
- 构造方法实例化,默认的:让Spring调用bean的构造方法,生成bean实例对象给我们
- 工厂静态方法实例化:让Spring调用一个工厂类的静态方法,得到一个bean实例对象
- 工厂非静态方法实例化(实例化方法):让Spring调用一个工厂对象的非静态方法,得到一个bean实例对象
1、构造方法实例化
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
2、工厂静态方法实例化
- 工厂类如下:
com.guang.factory.StaticFactory
public class StaticFactory{
public static UserDao createUserDao(){
return new UserDaoImpl();
}
}
- 配置如下:
<bean id="userDao" class="com.itheima.factory.StaticFactory"
factory-method="createUserDao"></bean>
表示的是调用StaticFactory类中的createUserDao方法来创建出来一个bean对象。
3、工厂非静态方法实例化
- 工厂类如下:
com.guang.factory.InstanceFactory
public class InstanceFactory{
public UserDao createUserDao(){
return new UserDaoImpl();
}
}
- 配置如下:
<!-- 先配置工厂 -->
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<!-- 再配置UserDao -->
<bean id="userDao" factory-bean="instanceFactory" factory-method="createUserDao"></bean>
表示的是利用instanceFactory对象调用createUserDao方法来创建出来一个bean对象。
对于工厂的静态方法和非静态方法不止只有xml的使用方式,还有对应的java注解版本控制。
二、注解版本
可以使用这种方式来进行配置,在spring容器中也可以生成对应的Bean对象。
三、源码解析流程
3.1、入口
在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry中会来解析配置类,在对于@Bean方法所在的类来说,也是一种配置类。
在org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass中有下面几行代码来解析@Bean方法的
// 解析配置类中的@Bean,但并没有真正处理@Bean,只是暂时找出来
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
首先找到配置类中加了@Bean的方法,添加遍历循环,添加到ConfigurationClass的beanMethods属性中来,并将当前方法创建出来一个新的BeanMethod对象来进行保存。BeanMethod表示的是创建bean的方法。
那么具体的解析在外面:
// 可能会生成很多的BD,spring还得去检查有没有配置类
this.reader.loadBeanDefinitions(configClasses);
开始来解析加了@Bean方法的配置类
从保存在configClass中beanMethods属性集合中获取得到,然后来进行遍历每个@Bean方法进行解析
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
下面重点就是loadBeanDefinitionsForBeanMethod如果来对@Bean注解来进行解析的。
3.2、常用注解解析
那么这里就需要涉及到@Bean方法的一些常用属性,这些属性肯定是要来进行解析的。所以spring就是要先来做这些事情的。
public @interface Bean {
// 当前bean对象的别名
@AliasFor("nme")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
// 当前bean是否能够成为一个候选的bean对象,跟依赖注入阶段相关
boolean autowireCandidate() default true;
// 初始化方法
String initMethod() default "";
// 销毁方法。有默认值,一般不用
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
3.3、是否有static关键字的判断
// 创建@Bean注解对应的BeanDefinition
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
// 如果是statci修饰的
if (metadata.isStatic()) {
// static @Bean method
if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
}
else {
// 设置当前配置类的全限定类名 当前为String,如:com.guang.config.MyAppConfig
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
}
// 设置唯一的method为当前的方法名称,如:userService
beanDef.setUniqueFactoryMethodName(methodName);
// 即调用com.guang.config.MyAppConfig类中的静态方法userService来创建对象
}
else {
// 不是静态的,会执行到这里来
// instance @Bean method
// myAppConfig
beanDef.setFactoryBeanName(configClass.getBeanName());
// 设置唯一的method为当前的方法名称,如:userService
beanDef.setUniqueFactoryMethodName(methodName);
// 即调用myAppConfig的userService方法来创建对象
}
3.4、@Bean对应的方法是否是唯一的
注意:在setUniqueFactoryMethodName方法中,如下设置:
public void setUniqueFactoryMethodName(String name) {
Assert.hasText(name, "Factory method name must not be empty");
setFactoryMethodName(name);
// @Bean对应的方法是否是独一无二的。如果只有一个,那么为true;如果存在多个,那么为false
this.isFactoryMethodUnique = true;
}
什么意思呢?对应的代码如下所示:
@Bean
public UserService userService(){
return new UserService();
}
@Bean
public UserService userService(OrderService orderService){
return new UserService();
}
因为两个方法是重载,而且配置在同一个配置类中,那么spring最终会通过哪个配置方法来进行创建对象呢?
首先判断当前的BeanDefinitionMap中有几个UserService对应的Map?答案是只有一个
因为有一行代码判断:
// 如果出现了两个@Bean修改的方法名字一样(比如方法重载了),则直接return,并且会把已经存在的BeanDefinition的isFactoryMethodUnique为false
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
// 如果beanName等于"appConfig",就会抛异常
if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
"' clashes with bean name for containing configuration class; please make those names unique!");
}
return;
}
那么看下如何来判断重载的?
但是一般来说,这种情况很少见的。这种情况都是比较生僻的方式。
在方法命名的时候,通常都是采用不同的方法名称来进行命名的。如下所示:
3.5、额外属性设置
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
3.6、注册BeanDefinition
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
3.7、实例化过程
在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中的进行实例化的过程中
来到org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance方法中来
// @Bean对应的BeanDefinition
// 在解析的时候来进行处理的
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
最终这里会拿到对应的@Configuration所在的类,
1、如果是static关键字修饰的@Bean,会拿到对应的Class(CGLIB代理类);
2、如果是非static关键字修饰的@Bean,会拿到对应的bean对象和对应的Class(CGLIB代理类);
然后根据代理类获取得到被代理的类:
factoryClass = ClassUtils.getUserClass(factoryClass);
然后找到类中的@Bean方法
// 根据BD中的属性isFactoryMethodUnique判断@Bean对应的方法是不是唯一的
if (mbd.isFactoryMethodUnique) {
if (factoryMethodToUse == null) {
factoryMethodToUse = mbd.getResolvedFactoryMethod();
}
if (factoryMethodToUse != null) {
candidates = Collections.singletonList(factoryMethodToUse);
}
}
然后找到对应的方法开始来进行执行:
然后来执行对应的方法
然后就来到了@Configuration中最关键的一个环节:
正常来说,这里执行完了对应的方法之后,就会返回对应的result结果。
但是这里能够解决的问题是:
能够保证使用的是同一个OrderService对象,就是利用上面源码中的ThreadLocal来进行保证的。