目录
- Spring bean标签
- 1.了解Spring Xml配置文件
- 2.bean标签的Attrbute
- 3.bean的子标签
- 扩展
- FactoryBean
Spring bean标签
在创建
IOC容器的时候,是如何把配置文件解析成我们的BeanDefinition。本文针对其<bean/>标签中的属性及其子标签进行说明。
1.了解Spring Xml配置文件
当我们需要去创建一个
Spring配置文件的时候我们需要用到这个<beans/>跟根标签,只有在beans下面定义的信息才可以被Spring解析并保存到BeanDefinition中。
先了解一下大致结构。外层是根节点<beans/>,内层是一个一个的<bean/>标签。
<?xml version="1.0" encoding="UTF-8" ?>
<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>
2.bean标签的Attrbute
源码中是通过这一个方法来解析的。
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时,与单例不同的是他会创建一个新的实例去被依赖。<!--scope改为prototype后--> <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:通过属性的名称来自动装配,可以免去使用
<property/>。它会自动根据属性名帮忙找到同样名称的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:采取父级标签(
<beans />)的default-autowire。
-
-
dependency-check:依赖检查。有以下几种:
- none:即使不注入全部bean属性,也可以将bean创建成功。
- simple:检查基本对象或者集合类<基本对象>没有注入则会报错。
- objects:检查引用类型没有注入则会报错。
- all:检查全部属性是否注入成功。
-
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; }<!--创建了两个MessageService--> <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])<!--将其中一个改为false--> <bean id="messageService1" class="com.mfyuan.service.MessageServiceImpl" autowire-candidate="false"/>// 输出一个 NotifyListUtil(messageServiceList=[com.mfyuan.service.MessageServiceImpl@e25b2fe])<!--autowire改为byName--> <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"/> <!--使用factory-method 来创建这个bean对象,而不是创建一个class的bean对象--> <bean id="myFactoryMethodStr" class="com.mfyuan.factory.MyFactoryMethod" factory-method="getFactoryMethod"/><!--如果这个静态方法有参数的话,也可以通过constructor-arg 来指定参数--> <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即可。
3.bean的子标签
子标签,也就是
xml中bean这个dom元素的子节点;例如
<bean><description>hello description</description><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; }配置文件
<!--我们定义了一个User,他的scope是prototype 也就是在依赖注入的时候每次都注入的一个新的对象--> <bean id="mfyuan" class="com.mfyuan.model.User" scope="prototype"><property name="name" value="mfYuan" /> </bean> <!-- 将mfyuan这个bean注入到myLockUpMethod中--> <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容器中取的是一个对象。其属性也自然想同。修改配置文件
<!-- 将myLockUpMethod的scope也改原形的--> <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><bean/>内允许有多个<replaced-method/>,其name属性为要重写的方法名称,replacer为实现了MethodReplacer的实现类,具体替换逻辑在reimplement中编写。如果需要重写的方法有重载的情况的话可以通过<arg-type/>指定类型,从而找到目标方法。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...实现
MethodReplacerpublic class ExecuteReplacer implements MethodReplacer {@Overridepublic Object reimplement(Object obj, Method method, Object[] args) throws Throwable {switch (method.getName()) {case "execute":System.out.println("execute replacer...");}return null;} }修改配置文件
<!-- 将ExecuteReplacer注入IOC容器。 --> <bean id="executeReplacer" class="com.mfyuan.component.ExecuteReplacer"/><bean id="execute" class="com.mfyuan.model.Execute"><!-- 替换execute方法,由executeReplacer来实现。 --><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来指定使用哪一个。
扩展
FactoryBean
又称工厂Bean。那
BeanFactory呢?这两者完全不是一种东西。BeanFactory可以把他理解为就是一个IOC容器,而FactroyBean的话则是一种特殊Bean。类似<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;}@Overridepublic User getObject() throws Exception {return new User(userName);}@Overridepublic Class<?> getObjectType() {return User.class;}@Overridepublic 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本身的这个对象。