深入了解Spring中的容器
1.创建Bean的3种方式
1.1使用构造器创建bean实例
这是最常见的方式,如果不采用构造注入,bean类需要有默认构造函数。如果采用构造注入,则需要配置xml文件的<constructor-arg>
1.2使用静态工厂方法创建bean
最典型的工厂方法如
package spi; public class PersonFactory {
public static Person getPerson(String arg) {
if (arg.equalsIgnoreCase("Chinese")) {
return new Chinese();
} else {
return new American();
}
}
}
如果在Spring容器中配置一个bean,bean的实例希望由上的静态工厂方法反回,则可以在bean中使用 factory-method来指定工厂方法,并用<constructor-arg>指定参数。
<bean id="chinese" class="spi.PersonFactory" factory-method="getPerson">
<constructor-arg value="chinese" />
</bean>
1.3实例化工厂方法创建bean
这与上面的使用静态工厂方法创建bean高度相似,区别是这里是需要先创建工厂的实例。工厂方法如下
package spi; public class PersonFactory {
public Person getPerson(String arg) {
if (arg.equalsIgnoreCase("Chinese")) {
return new Chinese();
} else {
return new American();
}
}
}
在xml配置中,除了使用factory-method来指定bean的实例化工厂方法外,还需要使用factory-bean指定工厂实例的bean
<bean id="chinese" class="spi.PersonFactory" factory-method="getPerson" factory-bean="personFactory">
<constructor-arg value="chinese" />
</bean>
<bean id="personFactory" class="spi.PersonFactory" />
2.bean的继承
可以在Spring中使用abstract属性将一个bean定义一个模板配置,这叫做一个抽象bean。抽象bean不能被实例化,只是为了降低配置文件的冗余而存在的,只能由子bean继承,子类则使用parent属性指定父类bean。下面是一个例子,
<bean id="personTemplate" abstract="true">
<property name="name" value="zhangsan" />
<property name="axe" value="stoneAxe" />
</bean> <bean id="chinesePerson" parent="personTemplate">
<property name="axe" value="steelAxe" />
</bean>
这个例子中,子类bean chinesePerson将会从父类继承name属性,但是会覆盖父类的axe属性。
注意的是并非父类所有属性子类都能继承,depends-on, autowire, singleton, scope, lazy-ini 这些属性只能从子类本身获取或采用默认值。
并且Spring容器bean的继承可以在不同类型的bean之间存在。
3.工厂bean: FactoryBean接口
Spring容器会检查所有bean,如果发现某个bean实现了FactoryBean接口,就会调用接口的getObject(),其返回值才会作为真正的bean。
开发人员可以重写getObject()方法供Spring调用,例如下面,
package spi; import java.lang.reflect.Field; import org.springframework.beans.factory.FactoryBean; public class GetField implements FactoryBean<Object>{
private String targetClass;
private String targetField; public String getTargetClass() {
return targetClass;
} public void setTargetClass(String targetClass) {
this.targetClass = targetClass;
} public String getTargetField() {
return targetField;
} public void setTargetField(String targetField) {
this.targetField = targetField;
} @Override
public Object getObject() throws Exception {
// TODO Auto-generated method stub
Class<?> clazz = Class.forName(targetClass);
Field field = clazz.getField(targetField);
return field.get(null);
} @Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Object.class;
} @Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
} }
这个类实现了FactoryBean接口,可以返回任何我们需要的对象,功能非常强大,在Spring中可以做如下配置,
<bean id="getField" class="spi.GetField">
<property name="targetClass" value="spi.Chinese" />
<property name="targetField" value="axe" />
</bean>
测试代码如下,
public static void test6() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
System.out.println(ctx.getBean("getField"));
}
程序输出:North
Spring提供的工厂Bean,大多以FactoryBean结尾,用于生产指定类型的bean,工厂bean是Spring的重要工具之一。
4.获取Bean本身id
就像Bean希望获取所在的ApplicationContext实例一样,有时候Bean还希望获取XML中配置的id名称,这种情况需要bean实现BeanNameAware接口,过程与实现ApplicationContextAware类似。并且实现BeanNameAware接口的setBeanName()方法供Spring调用,例如下面,
public class Chinese implements Person, BeanNameAware {
private String beanName;
@Override
public void setBeanName(String arg0) {
this.beanName = beanName; }
...
这样容器创建bean时候就会调用这个setBeanName()方法,bean就能获取自己的id名称了。例如还可以在Chinese类中添加下面的方法,
public void info(){
System.out.println("我在XML中的id名称为:"+beanName);
}
5.强制初始化bean
某些情况下,容器中的依赖并不是非常直接,初始化bean时候,如果依赖的bean尚未初始化,有可能会出错,这种情况下我们希望先初始化依赖的bean。
我们可以使用depebds-on属性来强制容器先初始化依赖额bean,例如
<bean id="chinese" class="spi.Chinese" depends-on="steelAxe" />
<bean id="steelAxe" class="spi.SteelAxe" />
6.容器中Bean的生命周期
对于singleton的bean,Spring可以精确控制生命周期,因此可以在bean的各个阶段指定各种行为。
例如在bean依赖注入之后,或者销毁之前,可以插入指定动作。
6.1依赖关系注入之后的行为
有两种方式可以在Bean属性设置成功之后执行特定行为。
- 一是使用init-method属性
这种方式要求在bean中定义一个回调方法供Spring调用,并在XML中配置init-method属性,例如
<bean id="chinese" class="spi.Chinese" init-method="init">
只要在bean中实现init()方法,就能在bean被设置完属性之后调用,执行特定方法。
- 二是让bean实现InitialializingBean接口,并实现接口的 afterPropertiesSet()方法
这种方式不要求使用init-method属性,但是这对bean类具有侵入性,不推荐。
6.2Bean销毁之前的行为
要让Bean被销毁之前执行特定行为,也可以类似上面那样用两种方式实现,
一是使用destroy-method属性
二是实现DisposableBean接口,并实现destroy()方法
另外,对于非web应用,如果希望在关闭应用前先关闭容器实例(即ApplicationContext实例),则可以先在JVM中注册一个钩子,程序退出时就会执行回调函数,优雅地关闭容器实例,例如,
public static void test5() {
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Person p = ctx.getBean("chinese", Person.class);
p.useAxe();
ctx.registerShutdownHook();
}
6.3协调作用域不同步的Bean
如果在一个singleton类型的bean中注入一个prototype类型的bean时候,会发现有个问题,在整个应用程序生命周期,singleton bean只会被初始化一次,然而prototype bean每次都会重新创建并初始化,那么意味着singleton中的依赖注入并不是最新的。
要解决这个问题,Spring使用“方法注入”,具体方式为:
将调用bean(通常是singleton)定义为抽象类,定义一个抽象方法,用来返回一个对象,这个对象将用来对前面的bean注入值
在调用bean中使用 <lookup-method>子元素替代<property>元素,可以使得每次调用bean时候,都会进行一次注入。
bean定义如下
class abstract class Chinese implements Person {
private Dog dog;
public abstract Dog getDog{}
...
}
XML配置
<bean id="chinese" class="spi.Chinese">
<lookup-method name="getDog" bean="gunDog" />
</bean>
<bean id="gunDog" class="spi.GunDog" scope="prototype" />
对于上面的配置,Spring会做负责生成Chinese抽象类的子类,并重写getDog()方法,通常会这样写,
public Dog getDog(){
//获取ctx
...
return ctx.getBean("gunDog");
}
这样就强行要求singleton类型的bean,每次获取Dog属性时候,都进行一次依赖注入。
7.获取其他bean的属性值
获取bean属性值,即调用getter方法。Spring提供一个工厂Bean : PropertyPathFactoryBean专门用来调用getter方法获取属性值。
Spring的工厂Bean很强大,大多以*FactoryBean名称结尾,每一种工厂Bean可以生产特定产品,PropertyPathFactoryBean则是专门用来获取属性值的。
要使用PropertyPathFactoryBean,直接将它配置成一个XML中的bean即可,同时需要配置bean的targetBeanName和propertyPath属性,下面是一个例子,
<bean id="chinese" class="spi.Chinese" destroy-method="close">
<property name="age" value="30" />
<property name="son">
<bean class="spi.Son">
<property name="age" value="11" />
</bean>
</property>
</bean>
<!-- 将指定Bean实例的getter方法返回值定义成son1 bean -->
<bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<!-- 指定son1 bean来自哪个目标bean的getter方法 -->
<property name="targetBeanName" value="chinese" />
<!-- 指定目标bean的哪个getter方法, axe代表getSon() -->
<property name="propertyPath" value="son" />
</bean>
targetBeanName属性用来指定将要获取哪个bean的属性,propertyPath用来指定要执行目标bean的哪个getter方法。
上面涉及一个Son类代码如下,
package spi; public class Son {
private int age; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
}
public String toString(){
return "Son[age="+this.age+"]";
}
}
测试代码如下,
System.out.println("系统获取son1: "+ctx.getBean("son1"));
将会得到如下结果,
系统获取son1: Son[age=11]
获取到的bean属性值还可以用来注入另一个bean,下面是一个例子,
<bean id="son2" class="spi.Son">
<property name="age">
<!-- 使用嵌套bean为setAge()方法指定参数 -->
<!-- 以下是访问指定bean的getter方法的简单方式,
chinese.son.age代表获取chinese.getSon().getAge(), 也就是前面的son1-->
<bean id="chinese.son.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
</property>
</bean>
测试代码,
System.out.println("系统获取son2: "+ctx.getBean("son2"));
执行结果,
系统获取son2: Son[age=11]
另外,propertyPath所指定的getter方法,也可以是一个复合方法(即属性的属性),像下面这样,
<bean id="theAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetBeanName" value="chinese" />
<!-- 这里的son.age是一个复合属性,表示chinese.getSon().getAge() -->
<property name="propertyPath" value="son.age" />
</bean>
测试代码,
System.out.println("系统获取theAge: "+ctx.getBean("theAge"));
执行结果,
系统获取theAge: 11
同时,目标bean也可以是嵌入的一个bean,不过这时候不是使用targetBeanName属性来指定,而是targetObject,例如这样,
<bean id="theAge2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetObject">
<bean class="spi.American">
<property name="age" value="30" />
</bean>
</property>
<property name="propertyPath" value="age" />
</bean>
测试代码,
System.out.println("系统获取theAge2: "+ctx.getBean("theAge2"));
执行结果,
系统获取theAge2: 30
另外,对于获取其他bean属性的工厂Bean配置,还有一种简化配置如下,
<util:property-path id="son1" path="person.son" />
不过这种配置需要导入util:命名空间。
8.获取Field值
获取Field分为两种情况,第一种是静态Field,第二种是对象实例的Field值。
Spring定义了专门的工厂Bean:FieldRetrievingFactoryBean来获取Field值。在XML中配置一个FieldRetrievingFactoryBean类型的bean即可。
对于静态Field情形,需要在XML中设置FieldRetrievingFactoryBean bean的 targetClass(即目标类)和targetFiled(即目标字段)。
对于实例对象的Field,需要指定targetObject(目标对象)和targetField(目标字段)。
对于第二种情况,其实编程中意义不大,因为通常定义在类中的对象实例都是private的,FieldRetrievingFactoryBean无法直接获取,我们使用PropertyPathFactoryBean调用public的getter方法即可。
对于第一种情况,典型用法如下,
<bean id="theAge3" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="targetClass" value="java.sql.Connection" />
<property name="targetField" value="TRANSACTION_SERIALIZABLE" />
</bean>
FieldRetrievingFactoryBean有个setStaticField()方法用来指定目标类和目标Field,因此上面的配置又可以简写为,
<bean id="theAge4" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE" />
</bean>
FieldRetrievingFactoryBean返回的bean也可以用来注入其他bean,
<bean id="son3" class="spi.Son">
<property name="age">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
上面三段配置的测试代码如下,
System.out.println("系统获取theAge3: "+ctx.getBean("theAge3"));
System.out.println("系统获取theAge4: "+ctx.getBean("theAge4"));
System.out.println("系统获取son3: "+ctx.getBean("son3"));
测试结果,
系统获取theAge3: 8
系统获取theAge4: 8
系统获取son3: Son[age=8]
FieldRetrievingFactoryBean也支持命名空间的简写,即
<util:constrant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE" />
9.获取方法返回值
Spring使用工厂Bean:MethodInvokingFactoryBean来执行bean中的普通方法,如果有返回值,将被赋值给xml中定义的bean
执行普通方法也分两种情况,一种是静态类的普通方法,一种是普通对象的方法,
对于两种情况,都将MethodInvokingFactoryBean定义为XML中一个普通bean,且设置相似的参数。
targetClass/targetObject 用来设置目标类或者目标对象
targetMethod用来设置目标方法
arguments用来设置执行普通方法所需要的参数
一个典型的用法如下,
<!-- 配置
JFrame win = new JFrame("我的窗口");
win.setVisible(true);
-->
<bean id="win" class="javax.swing.JFrame">
<constructor-arg value="我的窗口" type="java.lang.String" />
<property name="visible" value="true" />
</bean>
<!-- 配置
JTextArea jta = JTextArea(7,40);
-->
<bean id="jta" class="javax.swing.JTextArea">
<constructor-arg value="7" type="int" />
<constructor-arg value="40" type="int" />
</bean> <!-- 配置
win.add(new JScrollPane(jta));
使用MethodInvokingFactoryBean驱动Spring调用普通方法
-->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="win" />
<property name="targetMethod" value="add" />
<property name="arguments">
<list>
<bean class="javax.swing.JScrollPane">
<constructor-arg ref="jta" />
</bean>
</list>
</property>
</bean>
上面使用纯XML配置的方法,相当于执行了下面的java代码,
JFrame win = new JFrame("我的窗口");
win.setVisible(true);
JTextArea jta = JTextArea(7,40);
win.add(new JScrollPane(jta));
通过上面的几点发现,Spring框架就是通过XML配置来执行java代码,因此几乎可以把所有java代码都放在Spring配置中管理,归纳一下:
- 调用构造器创建对象,用<bean ..>元素
- 调用setter方法,用<property..>元素
- 调用getter方法,用工厂Bean PropertyPathFactoryBean
- 调用普通方法,用工厂Bean MethodInvokingFactoryBean
- 获取Field的值,用工厂Bean FieldRetrievingFactoryBean
深入了解Spring中的容器的更多相关文章
- Spring 中 IoC 容器简介
IoC 是一种通过描述来生成或者获取对象的技术,可以说 Spring 是一种基于 IoC 容器编程的框架 在一个系统中可以生成各种对象,并且这些对象都需要进行管理.为了描述这些对象关系,我们需要一个容 ...
- spring中IOC容器注册和获取bean的实例
spring中常用的功能主要的是ioc和aop,此处主要说明下,实例注册和使用的方法,此为学习后的笔记记录总结 1.使用xml文件配置 在idea中创建maven工程,然后创建实例Person,然后在 ...
- Spring中的容器
1.Spring容器 Spring容器最基本的接口就是BeanFactory, 负责配置,创建和管理bean.我们通常不直接使用BeanFactory接口,而是使用其子接口ApplicationCon ...
- spring中获取容器中的Bean为什么前转成接口而不是实现类
简单介绍一下上下文,userService是服务层接口有一个save方法,userServiceImpl是该接口的实现类重写了save方法. applicationContext.xml如图: 后台代 ...
- 半夜思考之查漏补缺, Spring 中的容器后处理器
之前学 Spring 的时候 , 还没听过容器后处理器 , 但是一旦写出来 , 就会觉得似曾相识 . 容器配置器通常用于对 Spring 容器进行处理 , 并且总是在容器实例化任何其他 Bean 之前 ...
- Spring中Ioc容器的注入方式
1 通过setter方法注入 bean类: package com.test; public class UserServiceImplement implements IUserService { ...
- 挖坟之Spring.NET IOC容器初始化
因查找ht项目中一个久未解决spring内部异常,翻了一段时间源码.以此文总结springIOC,容器初始化过程. 语言背景是C#.网上有一些基于java的spring源码分析文档,大而乱,乱而不全, ...
- Spring之IOC容器
在前面博客中介绍什么是依赖注入时有提到:依赖注入是组件之间依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中.那什么是容器?既然Spring框架实现了IOC,那Spring中的容器 ...
- Spring中常见的设计模式——单例模式
一.单例模式的应用场景 单例模式(singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点.J2EE中的ServletContext,ServletCon ...
随机推荐
- POJ 3608
1.计算P上y坐标值最小的顶点(称为 yminP )和Q上y坐标值最大的顶点(称为 ymaxQ). 2.为多边形在 yminP 和 ymaxQ 处构造两条切线 LP 和 LQ 使得他们对应的多边形位于 ...
- [Cypress] Stub Network Requests in a Cypress Test
To keep our tests fast and easily repeatable, it makes sense to create many integration tests and fe ...
- 使用makeself创建安装文件
Makeself.sh是一个小的Shell脚本.用于从一个文件夹中生成自解压的tar.gz压缩包. 结果文件以一个shell脚本显示(大多数以.run作为后缀名).能够自己主动执行.该文档会解压自己到 ...
- 2015多校联合训练赛 hdu 5308 I Wanna Become A 24-Point Master 2015 Multi-University Training Contest 2 构造题
I Wanna Become A 24-Point Master Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 ...
- MyBatis中sqlSession操作数据库,不报错但无法实现数据修改(增、改、删)
public void addCustomerTest() throws Exception { SqlSession sqlSession = MyBatisUtils.getSession(); ...
- 监控sql运行时剩余时间
--监控sql执行时剩余时间 你知道正在执行的sql大概须要多长时间么? 你知道正在执行的sql大概完毕了百分之几么? 你知道正在执行的sql大概还要多长时间完毕么? V$SESSION_LONGOP ...
- hdu2430 Beans 单调队列
// hdu2430 Beans 单调队列 // // 题目意思: // 求一个sum%p<=k的max(sum/p) // // 结题报告: // 技巧,先求出前缀和,并记录前i项对p取余的值 ...
- DevExpress14.1.2 xe XE6 高速安装
之前在在网上下载的DevExpress14.1.2 xe-XE6都是一个个包文件.须要一个个去查找编译安装,并且须要有一定的顺序要求. 所下面载了好久了都没有安装. 近期在网上找了个旧版的安装方法.以 ...
- 为了世界的和平~一起上caioj~~~!
打Call~打Call~打Call~~~!!! 世界毁灭了你在哪???不要犹豫,快去caioj!!! 无比优质的oj,未来大牛的明智之选----就是caioj~~~
- 几款jQuery右键菜单插件介绍
在网页中使用自定义右键菜单,实现上皆为使用javascript禁用浏览器默认的右键菜单,然后在网页中响应鼠标右键事件,弹出自定义的菜单. 类似右键菜单的组件网上很多.一般而言,改变浏览器的默认菜单应当 ...