Spring 构造器注入
从Spring3.0开始,Spring可以通过注解的方式来配置Bean。这里先介绍以传统的XML配置式来配置Bean。
用一场选秀比赛来模拟Spring装备Bean过程。
一场比赛中,需要一些参赛者来参加比赛,为此我们定义一个Performer接口:
public interface Performer {
public void perform();
}
参赛选手都实现了这个Performer接口。
以下为一个典型的Spring XML配置文件(Spring容器,Spring上下文):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<!--Bean declarations go here -->
</beans>
在元素内,我们可以配置所有Spring配置信息以及元素的声明。除此之外,Spring还自带了包括在内的10大命名空间:
命名空间 | 用途 |
---|---|
aop | 为声明切面以及将@AspectJ注解的类代理为Spring切面提供了配置元素 |
beans | 支持声明Bean和装配Bean,是Spring最核心也是最原始的命名空间 |
context | 为配置Spring应用上下文提供了配置元素,包括自动检测和自动装配Bean,注入非Spring直接管理的对象 |
jee | 提供了与Java EE API的集成,例如JNDI和EJB |
jms | 为声明消息驱动的POJO提供了配置元素 |
lang | 支持配置由Groovy,JRuby或BeanShell等脚本实现的Bean |
mvc | 启动Spring MVC的能力,例如面向注解的控制器,视图控制器和拦截器 |
oxm | 支持Spring的对象到XML映射配置 |
tx | 提供声明事务配置 |
util | 提供各种各样的工具类元素,包括把集合配置为Bean,支持属性占位符元素 |
回归比赛,第一位参赛者是一个Juggler(杂技师),声明一个Juggler Bean:
public class Juggler implements Performer{
private int beanBags=3;
public Juggler() {
}
public Juggler(int beanBags) {
this.beanBags = beanBags;
}
public void perform() {
System.out.println("throws "+beanBags+" beanBags");
}
}
Juggler实现了Performer接口,默认可以同时抛出三个豆袋子。也可以通过有参构造器改变袋子数量。
现在有请第一位叫duke的杂技师上场!在applicationContext.xml文件中配置Duke:
<bean id="duke" class="com.spring.entity.Juggler"/>
id表明他叫duke,class表明他是一个juggler。现在让duke上台表演:
public class Show {
public static void main(String[] args) {
String conf="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(conf);
Juggler duke=(Juggler)ac.getBean("duke");
duke.perform(); //扔了 3 个豆袋子
}
}
构造器注入
评委觉得这并没有什么难度,于是duke决定一次性扔15个豆袋子!修改duke的配置:
<bean id="duke" class="com.spring.entity.Juggler">
<constructor-arg value="15" />
</bean>
也可以使用c-命名空间代替标签,要使用它的话,现在XML顶部声明其模式:
xmlns:c="http://www.springframework.org/schema/c"
上面的配置可以改为:
<bean id="duke" class="com.spring.entity.Juggler" c:beanBags="15">
或者:
<bean id="duke" class="com.spring.entity.Juggler" c:_0="15">
像这种只有一个参数的话也可以写为:
<bean id="duke" class="com.spring.entity.Juggler" c:_="15">
Juggler类有两个构造器,无参和有参。当没有声明的时候,Spring将默认使用无参的构造方法。现在,我们将的value设置位15的时候,Spring使用有参构造方法来改变属性beanBags的值。
再次表演,输出:
扔了 15 个豆袋子
掌声雷动。
稍等!duke说他还会边扔袋子变朗诵诗歌!为此我们定义一个PoeticJuggler(会朗诵的杂技师):
public class PoeticJuggler extends Juggler{
//诗歌属性
private Poem poem;
public PoeticJuggler(Poem poem) {
this.poem = poem;
}
public PoeticJuggler(int beanBags,Poem poem) {
//继承豆袋子属性
super(beanBags);
this.poem = poem;
}
public void perform(){
//继承扔袋子技能
super.perform();
System.out.println("边朗诵...");
//朗诵诗歌
poem.recite();
}
}
诗歌接口:
public interface Poem {
public void recite();
}
duke最喜欢的是普希金的《假如生活欺骗了你》,为此我们定义一个DeceivedByLife类,实现Poem接口:
public class DeceivedByLife implements Poem{
private static String[] LINES = {
"假如生活欺骗了你,",
"不要悲伤,不要心急!",
"忧郁的日子里须要镇静:",
"相信吧,快乐的日子将会来临!",
"心儿永远向往着未来;",
"现在却常是忧郁。",
"一切都是瞬息,一切都将会过去;",
"而那过去了的,就会成为亲切的怀恋"};
public DeceivedByLife(){
}
//朗诵诗歌
public void recite() {
for(int i=0;i<LINES.length;i++){
System.out.println(LINES[i]);
}
}
}
在Spring容器里配置这首诗歌:
<bean id="deceivedByLife" class="com.spring.entity.DeceivedByLife"/>
现在duke是一个poeticJuggler了,为此我们修改duke的配置:
<bean id="poeticDuke" class="com.spring.entity.PoeticJuggler">
<constructor-arg value="15"/>
<constructor-arg ref="deceivedByLife"/>
</bean>
c-命名空间写法:
<bean id="poeticDuke" class="com.spring.entity.PoeticJuggler"
c:_beanBags="15"
c:_poem-ref="deceivedByLife"/>
或者:
<bean id="poeticDuke" class="com.spring.entity.PoeticJuggler"
c:_0="15"
c:_1-ref="deceivedByLife"/>
当Spring碰到deceivedByLife和poeticDuke的声明时,它所执行的逻辑本质和下面的Java代码是一样的:
Poem deceivedByLife = new DeceivedByLife();
Performer duke = new PoeticJuggler(15,deceivedByLife);
现在,duke再次进行了表演:
public class Show {
public static void main(String[] args) {
String conf="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(conf);
Juggler duke=(PoeticJuggler)ac.getBean("poeticDuke");
duke.perform();
}
}
输出:
扔了 15 个豆袋子
边朗诵...
假如生活欺骗了你,
不要悲伤,不要心急!
忧郁的日子里须要镇静:
相信吧,快乐的日子将会来临!
心儿永远向往着未来;
现在却常是忧郁。
一切都是瞬息,一切都将会过去;
而那过去了的,就会成为亲切的怀恋
掌声再次雷动。
通过工厂方法创建Bean
有时候,一个类没有公开的构造方法,这时候就不能使用构造器注入了。Spring支持元素的factory-method方法来装配Bean。
创建一个舞台类:
public class Stage {
private Stage(){
}
//延迟加载实例
public static class stageSingletonHolder{
static Stage instance=new Stage();
}
//返回实例
public static Stage getInstance(){
return stageSingletonHolder.instance;
}
public void createStage(){
System.out.println("创造一个舞台");
}
}
Stage作为一个单例类,没有公开的构造方法,相反,静态方法getInstance()每次调用都返回一个相同的Stage实例。
元素的factory-method属性允许我们调用一个静态方法,从而代替构造方法来创建一个类的实例。
在Spring上下文中配置Stage:
<bean id="stage" class="com.spring.entity.Stage" factory-method="getInstance"/>
现在从Spring容器中获取这个:
public class CreateStage {
public static void main(String[] args) {
String conf="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(conf);
Stage stage=(Stage)ac.getBean("stage");
stage.createStage();
}
}
输出:
创造一个舞台
Bean作用域
所有Spring Bean默认都是单例的。如果现在要创建一个”门票”类,每个人的门票肯定是不一样的。为了让Spring每次请求都产生一个新的实例,我们可以定义如下ticket:
<bean id="ticket" class="com.spring.entity.Ticket" scope="prototype"/>
除了prototype,Spring还提供了其他几个作用域选项:
作用域 | 定义 |
---|---|
singleton | 在每一个spring容器中,一个Bean定义只有一个对象实例(默认) |
prototype | 允许Bean的定义可以被实例化任意次(每次调用都创建一个新的实例) |
request | 在一次HTTP请求中,每个Bean定义对应一个实例。该作用域仅在基于Web的Spring上下文中才有效 |
session | 在一个HTTP Session中,每个Bean定义对应一个实例。该作用域仅在基于Web的Spring上下文中才有效 |
global-session | 在一个全局HTTP Session中,每个Bean定义对应一个实例。该作用域仅在Protlet上下文中才有效 |
Spring的单例Bean只能保证在每个应用上下文中只有一个Bean实例。没有人能够阻止你使用传统的方式实例化同一个Bean,或者你甚至可以定义几个声明来实例化同一个Bean。
初始化和销毁Bean
为Bean定义初始化和销毁操作,只需要使用init-method和destroy-method参数来配置元素。init-method属性指定了在初始化Bean时要调用的方法。destroy-method属性指定了Bean从容器移除之前要调用的方法。
现在定义一个灯光类Light:
public class Light {
void turnOnTheLight(){
System.out.println("开灯");
}
void turnOffTheLight(){
System.out.println("关灯");
}
}
在容器中配置该Bean:
<bean id="light" class="com.spring.entity.Light"
init-method="turnOnTheLight" destroy-method="turnOffTheLight"/>
加载容器,以此来实例化light:
public class LightControl {
public static void main(String[] args) {
String conf="applicationContext.xml";
//加载容器
ApplicationContext ac=new ClassPathXmlApplicationContext(conf);
//关闭容器
((ClassPathXmlApplicationContext)ac).close();
}
}
输出:
开灯
...
关灯
我们还可以定义默认的init-method和destroy-method。如果上下文中Bean定义了名字相同的初始化和销毁方法,我们可以统一配置default-init-method和default-destroy-method:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"
default-init-method="turnOnTheLight"
default-destroy-method="turnOffTheLight">
<!--Bean declarations go here -->
</beans>
为Bean定义初始化和销毁的方法是让Bean实现Spring的InitializingBean和DisposableBean接口。InitializingBean声明了一个afterPropertiesSet()方法作为初始化方法。DisposableBean声明了一个destroy()方法作为销毁方法。在Spring容器中无需任何配置。
修改Light类:
public class Light implements InitializingBean,DisposableBean{
public void afterPropertiesSet() throws Exception {
System.out.println("开灯");
}
public void destroy() throws Exception {
System.out.println("关灯");
}
}
这种方法的缺点显而易见了:实现Spring的接口意味着Bean与Spring的API产生了耦合。所以这种方法并不推荐!
源码:https://github.com/ReYe0/SpringStudy/tree/master/_23.spring-constructor-injection
转载:https://mrbird.cc/Spring-%E6%9E%84%E9%80%A0%E5%99%A8%E6%B3%A8%E5%85%A5.html