曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解
写在前面的话#
相关背景及资源:
曹工说 Spring Boot 源码系列开讲了(1)-- Bean Definition 到底是什么,附 spring 思维导图分享
工程结构图:
正文#
我这里,先把org.springframework.beans.factory.config.BeanDefinition
接口的方法再简单列一下:
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { // scope:单例 String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; // scope:prototype String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; // 角色:属于应用程序的 bean int ROLE_APPLICATION = 0; // 角色:支持?不甚了解,先跳过 int ROLE_SUPPORT = 1; // 角色:属于框架自身的 bean int ROLE_INFRASTRUCTURE = 2; // parent bean 的名字 String getParentName();
<span class="token keyword">void</span> <span class="token function">setParentName</span><span class="token punctuation">(</span><span class="token class-name">String</span> parentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 核心属性,此为bean的class名称</span> <span class="token class-name">String</span> <span class="token function">getBeanClassName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setBeanClassName</span><span class="token punctuation">(</span><span class="token class-name">String</span> beanClassName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 核心属性,本属性获取工厂bean的名称,getFactoryMethodName获取工厂方法的名称,配合使用,生成 // 本bean</span> <span class="token class-name">String</span> <span class="token function">getFactoryBeanName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setFactoryBeanName</span><span class="token punctuation">(</span><span class="token class-name">String</span> factoryBeanName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">String</span> <span class="token function">getFactoryMethodName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setFactoryMethodName</span><span class="token punctuation">(</span><span class="token class-name">String</span> factoryMethodName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//scope,bean是单例的,还是每次new一个(prototype),就靠它了</span> <span class="token class-name">String</span> <span class="token function">getScope</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setScope</span><span class="token punctuation">(</span><span class="token class-name">String</span> scope<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 懒bean?默认情况下,都是容器启动时,初始化;如果设置了这个值,容器启动时不会初始化,首次getBean // 时才初始化</span> <span class="token keyword">boolean</span> <span class="token function">isLazyInit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setLazyInit</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> lazyInit<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 在本bean初始化之前,需要先初始化的bean;注意,这里不是说本bean依赖的其他需要注入的bean</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getDependsOn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setDependsOn</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dependsOn<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 是否够资格作为自动注入的候选bean。。。如果这里返回false,那就连自动注入的资格都没得</span> <span class="token keyword">boolean</span> <span class="token function">isAutowireCandidate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setAutowireCandidate</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> autowireCandidate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 当作为依赖,要注入给某个bean时,当有多个候选bean时,本bean是否为头号选手</span> <span class="token keyword">boolean</span> <span class="token function">isPrimary</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">setPrimary</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> primary<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 通过xml <bean>方式定义bean时,通过<constructor-arg>来定义构造器的参数,这里即:获取构造器参数</span> <span class="token class-name">ConstructorArgumentValues</span> <span class="token function">getConstructorArgumentValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 通过xml <bean>方式定义bean时,通过 <property name="testService" ref="testService"/> 这种方 式来注入依赖,这里即:获取property注入的参数值</span> <span class="token class-name">MutablePropertyValues</span> <span class="token function">getPropertyValues</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 是否单例</span> <span class="token keyword">boolean</span> <span class="token function">isSingleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 是否prototype</span> <span class="token keyword">boolean</span> <span class="token function">isPrototype</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 是否为抽象的,还记得<bean>方式定义的时候,可以这样指定吗?<bean id="testByPropertyController" class="org.springframework.simple.TestByPropertyController" abstract="true"></span> <span class="token keyword">boolean</span> <span class="token function">isAbstract</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取角色</span> <span class="token keyword">int</span> <span class="token function">getRole</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取描述</span> <span class="token class-name">String</span> <span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">String</span> <span class="token function">getResourceDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 未知。。。</span> <span class="token class-name">BeanDefinition</span> <span class="token function">getOriginatingBeanDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
beanName#
虽然这个接口里没这个东西,但这个我要重点说下,默认规则是:beanClassName 按驼峰转换后的名字。
这里面有个重点是,org.springframework.beans.factory.support.DefaultListableBeanFactory
中,采用了下面的字段来存 bean 和对应的 BeanDefinition。
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
这里说了,key 是 beanName。
那大家想过没,我如果同一个上下文中,有两个 beanName 相同的 BeanDefinition 会怎样呢?
之前 spring cloud 项目集成 feign 时,我们的代码是下面这样的, 即假设生产者提供了 10 个服务,分属不同的模块:
@FeignClient(name = "SCM",path = "scm",configuration = { FeignConfig.class })
public interface ScmCenterService extends ScmFeignCenterService {
}
@FeignClient(name = "SCM",path = "scm",configuration = { FeignConfig.class })
public interface ScmClientConfigService extends ScmFeignClientConfigService {
}
我们这里,就是按照不同模块,在多个接口里来继承 feign api。
结果呢,启动报错了,就是因为 beanName 重复了,具体可以参考下面的链接:
最终解决这个问题,就是要加个配置,
spring:
main:
allow-bean-definition-overriding: true
这个配置,在 spring 之前的版本里,默认是 true,结果在 spring boot 里,默认改为 false 了。
我这边通过下面的代码测试了一下:
-
当这个配置为 true 时
public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:application-context.xml"},false); // 这里设为 true,不设也可以,默认就是 true context.setAllowBeanDefinitionOverriding(true); context.refresh();
<span class="token class-name">DefaultListableBeanFactory</span> beanFactory <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">DefaultListableBeanFactory</span><span class="token punctuation">)</span> context<span class="token punctuation">.</span><span class="token function">getBeanFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">GenericBeanDefinition</span> beanDefinition <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GenericBeanDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> beanDefinition<span class="token punctuation">.</span><span class="token function">setBeanClass</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> beanFactory<span class="token punctuation">.</span><span class="token function">registerBeanDefinition</span><span class="token punctuation">(</span><span class="token string">"testService"</span><span class="token punctuation">,</span> beanDefinition<span class="token punctuation">)</span><span class="token punctuation">;</span>
console 中有如下提示:
信息: Overriding bean definition for bean 'testService': replacing [Generic bean: class [org.springframework.simple.TestService]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [application-context.xml]] with [Generic bean: class [java.lang.String]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
-
当这个配置为 false 时
context.setAllowBeanDefinitionOverriding(false);
会直接报错:
Invalid bean definition with name 'testService' defined in null: Cannot register bean definition [Generic bean: class [java.lang.String]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] for bean 'testService':
// 这里说,早已存在 xxx
There is already [Generic bean: class [org.springframework.simple.TestService]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [application-context.xml]] bound.
测试代码在:
scope#
这个不多说了,默认为singleton
,在容器内只会有一个 bean。prototype,每次 getBean 的时候都会 new 一个对象,这个一般不会在启动时去初始化,如果写的有问题的话,启动时不报错,runtime 的时候报运行时异常。
其他几个 scope,web 相关的,先不多说。
parentName#
指定 parentBean 的名称,以前 xml 的时候可能会用,现在注解驱动了,基本很少用了。
beanClassName#
核心属性,bean 的 class 类型,这里说的是实际类型,而一般不是接口的名称。比如,我们的注解一般是加在 class 上的,而不是加在接口上,对吧;即使加在接口上,那肯定也是动态代理技术,对吧,毕竟,bean 是要以这个 class 的元数据来进行创建(一般通过反射)
factoryBeanName、factoryMethodName#
如果本 bean 是通过其他工厂 bean 来创建,则这两个字段为对应的工厂 bean 的名称,和对应的工厂方法的名称
lazyInit#
是否延迟初始化,取值为 true、false、default。
Indicates whether or not this bean is to be lazily initialized. If false, it will be instantiated on startup by bean factories that perform eager initialization of singletons. The default is "false".
简单说:如果设了这个为 true,则启动时不初始化;否则在启动时进行初始化,这也是 spring 官方推荐的,可以尽早发现问题。
dependsOn#
通过这个属性设置的 bean,会保证先于本 bean 被初始化
The names of the beans that this bean depends on being initialized. The bean factory will guarantee that these beans get initialized before this bean.
autowireCandidate,布尔#
这个是个 boolean 值,表示是否可以被其他的 bean 使用 @autowired 的方式来注入。如果设置为 false 的话,那完了,没资格被别人注入。
primary,布尔#
表示当有多个候选 bean 满足 @autowired 要求时,其中 primary 被设置为 true 的,会被注入;否则会报二义性错误,即:程序期待注入一个,却发现了很多个。
constructorArgumentValues#
构造函数属性值。我测试发现,通过下面的方式,这个字段是用不上的:
public class TestController {
<span class="token class-name">TestService</span> testService<span class="token punctuation">;</span> <span class="token annotation punctuation">@Autowired</span> <span class="token keyword">public</span> <span class="token class-name">TestController</span><span class="token punctuation">(</span><span class="token class-name">TestService</span> testService<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>testService <span class="token operator">=</span> testService<span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
这个字段,什么时候有值呢,当采用下面的方式的时候,就会用这种:
public class TestController {
<span class="token class-name">TestService</span> testService<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">TestController</span><span class="token punctuation">(</span><span class="token class-name">TestService</span> testService<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>testService <span class="token operator">=</span> testService<span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
<bean name="testService" class="org.springframework.simple.TestService" />
<bean id="testController" class="org.springframework.simple.TestController">
<constructor-arg ref="testService"/>
</bean>
演示图如下:
propertyValues#
property 方式注入时的属性值。在以下方式时,生效:
public class TestByPropertyController {
<span class="token class-name">TestService</span> testService<span class="token punctuation">;</span> <span class="token comment">//注意,这里是set方法注入</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setTestService</span><span class="token punctuation">(</span><span class="token class-name">TestService</span> testService<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>testService <span class="token operator">=</span> testService<span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
<bean name="testService" class="org.springframework.simple.TestService" />
<bean id="testByPropertyController" class="org.springframework.simple.TestByPropertyController" >
<property name="testService" ref="testService"/>
</bean>
演示图如下:
总结#
今天内容大概到这里,有问题请留言