在创建
IOC
容器的时候,是如何把配置文件解析成我们的BeanDefinition
。本文针对其标签中的属性及其
子标签
进行说明。
当我们需要去创建一个
Spring
配置文件的时候我们需要用到这个跟根标签,只有在
beans
下面定义的信息才可以被Spring
解析并保存到BeanDefinition
中。
先了解一下大致结构。外层是根节点
,内层是一个一个的
标签。
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="messageService" class="com.mfyuan.service.MessageServiceImpl"/>
beans>
源码中是通过这一个方法来解析的。
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
id:bean
的唯一标识,不可重复。
name:bean
的名称,不可重复。但是允许有多个可以通过,
或者;
来分割。
class: bean
的类型,IOC
容器会通过这个class
来通过反射
来创建这个对象。
singleton:标识Bean
对象在IOC容器中是否是单例
的。已弃用。新版采用scope
属性来替换。
scope:bean
的作用域。
singleton(默认):在IOC容器
中的是单例
的。当bean被其他多个bean所依赖的时候。其他都是依赖的同一样。
<bean id="messageService" name="messageService" class="com.mfyuan.service.MessageServiceImpl"/>
MessageService messageService = applicationContext.getBean("messageService", MessageService.class);
System.out.println(messageService);
messageService = applicationContext.getBean("messageService", MessageService.class);
System.out.println(messageService);
// 同时输出两个NotifyUtil的MessageServcie 根据内存地址判断为同一个并
com.mfyuan.service.MessageServiceImpl@675d3402
com.mfyuan.service.MessageServiceImpl@675d3402
prototype:原形的。表示当其他多个bean
去依赖该bean
时,与单例
不同的是他会创建一个新的实例去被依赖。
<bean id="messageService" name="messageService" class="com.mfyuan.service.MessageServiceImpl" scope="prototype"/>
// 两个对象不同
com.mfyuan.service.MessageServiceImpl@675d3402
com.mfyuan.service.MessageServiceImpl@51565ec2
request(web扩展的):每一个请求都会去创建不同bean,请求结束后销毁bean。
session(web扩展的):不同的session会创建不同bean,session结束后销毁bean。
application(web扩展的):不同的web Application会创建,不同bean,webApplication关闭后销毁bean。
abstract:抽象的bean
,默认为false
。为true
时,表示该bean
在IOC容器
启动的时候不会去初始化它。只会当做一个配置模版,可以被其他bean
的parent
属性使用。
lazy-init:懒加载。默认为false
,实时加载。为true
时,表示为延时加载的,只有当该bean
在IOC容器
中被使用或者被依赖的时候,才会去创建出这个bean
。
autowire:依赖注入的方式。通常有以下几种
no(默认):不采用autowire
的机制。当我们需要依赖其他bean
时,通过ref
来引入其他bean
。
byName:通过属性的名称来自动装配,可以免去使用
。它会自动根据属性名帮忙找到同样名称的bean
帮我们注入到当前bean
中。没有找到则不会注入。
<bean id="notifyUtil" class="com.mfyuan.util.NotifyUtil" autowire="byName"/>
byType:通过类型来自动装配。它会自动根据属性类型帮忙找到类型的bean
帮我们注入到当前bean
中。如果有多个同样类型的bean
会报错。可以使用primary
来指定那个是主要的。没有找到则不会注入。
<bean id="notifyUtil" class="com.mfyuan.util.NotifyUtil" autowire="byType"/>
constructor:通过构造器来自动装配。他会通过构造器的参数去帮我们找到同样的类型的bean
来创建当前bean
。跟byType类似。但是如何没有找到合适
的bean
来注入的话,则会报错。
default:采取父级标签(
)的default-autowire
。
dependency-check:依赖检查。有以下几种:
depends-on:设置当前bean
依赖那些bean
,可以是多个使用,
或;
分割。依赖的这些bean
在当前bean
前初始化,在当前bean
后销毁。
autowire-candidate:是否为候选的bean
,默认为true
。也就是是否可以被其他bean所依赖(只针对于byType,对于byName,不管这个值是true还是false都会注入成功)。
@Data
@ToString
public class NotifyListUtil {
private List<MessageService> messageServiceList;
}
<bean id="messageService" name="messageService" class="com.mfyuan.service.MessageServiceImpl"/>
<bean id="messageService1" class="com.mfyuan.service.MessageServiceImpl"/>
<bean id="notifyListUtil" class="com.mfyuan.util.NotifyListUtil" autowire="byType"/>
NotifyListUtil notifyListUtil = applicationContext.getBean("notifyListUtil", NotifyListUtil.class);
System.out.println(notifyListUtil);
// 输出 两个
NotifyListUtil(messageServiceList=[com.mfyuan.service.MessageServiceImpl@e25b2fe, com.mfyuan.service.MessageServiceImpl@754ba872])
<bean id="messageService1" class="com.mfyuan.service.MessageServiceImpl" autowire-candidate="false"/>
// 输出一个
NotifyListUtil(messageServiceList=[com.mfyuan.service.MessageServiceImpl@e25b2fe])
<bean id="notifyListUtil" class="com.mfyuan.util.NotifyListUtil" autowire="byName"/>
// 输出 0个 为什么呢?因为我这里的属性名是messageServiceList没有为该名称的bean所有就为0个。
// 思考为什么byName会使这个属性失效了。我们只要知道的是beanName是不允许重复的就能明白了。
primary:是否为主要的。默认为false
。当同时有多个同样类型的bean
时,优先注入primary
为true
的。
init-method:bean
初始化的方法。
destroy-method:bean
销毁的方法。
factory-method:分情况
单独使用factory-method
的时候。指定class
中的一个静态方法来创建这个bean
。创建出来的bean,不再是class
,而是指定静态方法的返回值
。
public class MyFactoryMethod {
public static String getFactoryMethod(){
return "myFactoryMethod";
}
public static String getFactoryMethod(String methodName){
return methodName;
}
}
<bean id="myFactoryMethod" class="com.mfyuan.factory.MyFactoryMethod"/>
<bean id="myFactoryMethodStr" class="com.mfyuan.factory.MyFactoryMethod" factory-method="getFactoryMethod"/>
<bean id="myFactoryMethodStr1" class="com.mfyuan.factory.MyFactoryMethod" factory-method="getFactoryMethod">
<constructor-arg index="0" value="consumeMethod"/>
bean>
Class<?> myFactoryMethod = applicationContext.getType("myFactoryMethod");
System.out.println(myFactoryMethod);
Class<?> myFactoryMethodStr = applicationContext.getType("myFactoryMethodStr");
System.out.println(myFactoryMethodStr);
Object myFactoryMethodStr1 = applicationContext.getBean("myFactoryMethodStr1");
System.out.println(myFactoryMethodStr1);
// 输出 一个为class指定的对象,一个则是String。
class com.mfyuan.factory.MyFactoryMethod
class java.lang.String
consumeMethod
与factory-bean
一起使用的时候。指定factory-bean
中的一个非静态方法
。class
可以省去了。
public class MyFactoryMethod {
public String consumeMethod(){
return "consumeMethod";
}
}
<bean id="myFactoryMethod" class="com.mfyuan.factory.MyFactoryMethod"/>
<!- 通过指定factory-bean工厂bean,及指定一个factory-method来生成实际的bean对象 ->
<bean id="myFactoryMethodStr2" factory-bean="myFactoryMethod" factory-method="consumeMethod"/>
Object myFactoryMethodStr1 = applicationContext.getBean("myFactoryMethodStr2");
System.out.println(myFactoryMethodStr2);
// 输出consumeMethod
factory-bean:通过指定factory-bean
工厂bean
,及指定一个factory-method
来生成实际的bean
对象。为什么使用factory-bean
后factory-method
必须是非静态的了?这个也很好理解,如果是静态的,直接使用class
+factory-method
即可。
子标签,也就是
xml
中bean
这个dom
元素的子节点
;例如
<bean> <description>hello descriptiondescription> <meta key="info" value="我是一个bean"/> <bean/>
description:bean的一个描述。
meta:bean的元信息(可以存在多个)。
parseMetaElements(ele, bd);
方法进行解析;lookup-method:不常用,但是说一下。
可以重写bean中的一个方法,把方法的返回值替换成
bean
指定的对象。要求是返回值与指定的对象类型相同。源码中由
parseLookupOverrideSubElements(ele, bd.getMethodOverrides())
;处理使用场景。在一个
单例对象
中,我需要他的一个属性
是原形
的。实体类
@Getter @Setter public class MyLockUpMethod { private User refreshUser; }
配置文件
<bean id="mfyuan" class="com.mfyuan.model.User" scope="prototype"> <property name="name" value="mfYuan" /> bean> <bean id="myLockUpMethod" class="com.mfyuan.component.MyLockUpMethod" > <property name="refreshUser" ref="mfyuan">property> bean>
操作类
MyLockUpMethod myLockUpMethod = applicationContext.getBean("myLockUpMethod",MyLockUpMethod.class); System.out.println(myLockUpMethod+"-"+myLockUpMethod.getRefreshUser()); myLockUpMethod = applicationContext.getBean("myLockUpMethod",MyLockUpMethod.class); System.out.println(myLockUpMethod+"-"+myLockUpMethod.getRefreshUser()); // 输出 这两个对象的refreshUser是相同的, com.mfyuan.component.MyLockUpMethod@39c0f4a-com.mfyuan.model.User@39c0f4a com.mfyuan.component.MyLockUpMethod@39c0f4a-com.mfyuan.model.User@39c0f4a
这个结果也很好能理解,因为我们的
MyLockUpMethod
是单例的。所以每次从IOC容器
中取的是一个对象。其属性也自然想同。修改配置文件
<bean id="myLockUpMethod" class="com.mfyuan.component.MyLockUpMethod" scope="prototype"> <property name="refreshUser" ref="mfyuan">property> bean>6 // 输出不同,但是这样无法保证myLockUpMethod是同一个对象。 com.mfyuan.component.MyLockUpMethod@39c0f4a-com.mfyuan.model.User@1794d431 com.mfyuan.component.MyLockUpMethod@42e26948-com.mfyuan.model.User@57baeedf
修改配置文件
<bean id="myLockUpMethod" class="com.mfyuan.component.MyLockUpMethod"> <lookup-method name="getRefreshUser" bean="mfyuan"/> bean> // 满足要求MyLockUpMethod相同Uesr不同。 com.mfyuan.component.MyLockUpMethod$$EnhancerBySpringCGLIB$$af152e52@e320068-com.mfyuan.model.User@1f57539 com.mfyuan.component.MyLockUpMethod$$EnhancerBySpringCGLIB$$af152e52@e320068-com.mfyuan.model.User@76f2b07d
可以看见他这里是使用的
CGLIB
代理来创建的myLockUpMethod
,通过去代理并重写getRefreshUser
,来实现。
replaced-method:方法替换,相比lockup-method
,更灵活一点。就相当于重写方法。
源码中由
parseReplacedMethodSubElements(ele, bd.getMethodOverrides())
来解析。<bean id="xxx" class="xxx"> <replaced-method name="xxx" replacer="xxx"> <arg-type match="">arg-type> replaced-method> bean>
内允许有多个
,其
name
属性为要重写的方法名称,replacer
为实现了MethodReplacer
的实现类,具体替换逻辑在reimplement
中编写。如果需要重写的方法有重载的情况的话可以通过指定类型,从而找到目标方法。
public interface MethodReplacer { /** * 重新实现给定的方法。 * @param obj 被替换方法的对象 * @param method 要替换的方法 * @param args 方法的参数 * @return 方法的返回值 */ Object reimplement(Object obj, Method method, Object[] args) throws Throwable; }
实体类
public class Execute { public void execute(){ System.out.println("execute..."); } }
配置文件
<bean id="execute" class="com.mfyuan.model.Execute">
操作类
Execute execute = applicationContext.getBean(Execute.class); System.out.println(execute); execute.execute(); // 输出 com.mfyuan.model.Execute@7a1ebcd8 execute...
实现
MethodReplacer
public class ExecuteReplacer implements MethodReplacer { @Override public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { switch (method.getName()) { case "execute": System.out.println("execute replacer..."); } return null; } }
修改配置文件
<bean id="executeReplacer" class="com.mfyuan.component.ExecuteReplacer"/> <bean id="execute" class="com.mfyuan.model.Execute"> <replaced-method name="execute" replacer="executeReplacer"/> bean>
操作类
// 输出 com.mfyuan.model.Execute$$EnhancerBySpringCGLIB$$1accf27a@5faeada1 execute replacer...
方法替换成功,发现这个类也是被
CGLIB
进行了代理。
public interface MethodReplacer {
/**
* 重新实现给定的方法。
* @param obj 被替换方法的对象
* @param method 要替换的方法
* @param args 方法的参数
* @return 方法的返回值
*/
Object reimplement(Object obj, Method method, Object[] args) throws Throwable;
}
constructor-arg:用于构造函数注入,或者是factory-bean有参数的时候等。可以通过该标签指定参数。(可以多个)。源码中由parseConstructorArgElements(ele, bd);
解析。
property(最常用的):指定bean对象的属性值。源码中由parsePropertyElements(ele, bd);
解析。
qualifier:如果当前bean
有autowire
,并且,当对应的属性找到的多个bean
时,可以通过qualifier
来指定使用哪一个。
又称工厂Bean。那
BeanFactory
呢?这两者完全不是一种东西。BeanFactory
可以把他理解为就是一个IOC
容器,而FactroyBean
的话则是一种特殊Bean
。类似里的
factory-bean
,与factory-method
。而FactoryBean
的话则不需要去指定这个,直接通过class使用即可。
FactoryBean源码
public interface FactoryBean<T> {
/**
* 工厂Bean返回的对象实例
* @return an instance of the bean (can be {@code null})
* @throws Exception in case of creation errors
* @see FactoryBeanNotInitializedException
*/
T getObject() throws Exception;
/**
* 返回对象实例的类型
* @return the type of object that this FactoryBean creates,
* or {@code null} if not known at the time of the call
* @see ListableBeanFactory#getBeansOfType
*/
Class<?> getObjectType();
/**
* 返回的实例是否单例的,
* @return whether the exposed object is a singleton
* @see #getObject()
* @see SmartFactoryBean#isPrototype()
*/
boolean isSingleton();
}
实现一个FactoryBean
@Data
public class UserFactoryBean implements FactoryBean<User> {
private String userName;
public UserFactoryBean(String userName) {
this.userName = userName;
}
@Override
public User getObject() throws Exception {
return new User(userName);
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
配置文件
<bean id="userFactoryBean" class="com.mfyuan.factory.UserFactoryBean" >
<constructor-arg index="0" value="xiaoChen" />
bean>
输出
Object userFactoryBean = applicationContext.getBean("userFactoryBean");
System.out.println(userFactoryBean);
// User(name=xiaoChen)
// 通过&可以拿到UserFactoryBean本身的这个对象。
userFactoryBean = applicationContext.getBean("&userFactoryBean");
System.out.println(userFactoryBean);
// UserFactoryBean(userName=xiaoChen)
让我们在IOC容器中去注入一个
FactoryBean
对象的时候,其实是为我们注入了两个对象,其一是getObject()
返回值,其二是FactroyBean
本身这个对象。通过&+beanName
可以取到FactoryBean
本身的这个对象。