系统学习Spring之Spring in action(五)
每日一叨:
原本想这个星期六和星期天把依赖注入和面向切面编程写完的,结果小姨来南京玩,写博客的计划泡汤了.只能拖到今天写了,一定要找时间弥补上....
文章导读:
1.通过XML实现Bean的自动装配
2.通过注解装配bean
知识点:
到现在为止,我们学习了如何用<constructor-arg>和<properties>向bean中注入值.在小一点的应用程序中,这种做法是个不错的选择.如果bean比较多,
注入的值也多,如果你还要用之前的方法配置会很繁琐。还好Spring提供了一种可以减少配置说是消除<constructor-arg>和<properties>的技术.
1.通过XML实现Bean的自动装配
Spring提供了5种类型的自动装配.分别是byName,byType,constructor,autodetect,default
1)byName:匹配<beans>标签中所有bean的name或者id与当前自动装配bean中properties名字相同的bean,并且自动注入到自动装配的bean中,
若没有匹配的name或者id,则匹配失败
2)byType:匹配<beans>标签中所有bean的类型与当前自动装配bean中properties中值的类型相同的bean,并且自动注入到自动装配的bean中,
若没有匹配的类型,则匹配失败.
3)constructor:匹配<beans>标签中所有bean的构造方法的参数类型与当前自动装配相匹配的bean,若没有匹配类型,则匹配失败.
4)autodetect:autodetect的匹配策略是先通过constroctor进行匹配,若匹配失败,则进行byType匹配,若仍然没有匹配成功,则匹配失败.
5)default:dafault是根据Spring configuration配置而来,可以是上面的任意一种.
byName事例:
public interface Game { //获取游戏名称 public void getGameName(); }
public class War3 implements Game { @Override public void getGameName() { // TODO Auto-generated method stub System.out.println("大家好,我的名字叫魔兽争霸,相信你们很多人都已经认识我了."); } }
public class Dota implements Game { //Dota游戏作者 private String author; //Dota玩家同样会玩War3 private War3 war3; public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public War3 getWar3() { return war3; } public void setWar3(War3 war3) { this.war3 = war3; } @Override public void getGameName() { // TODO Auto-generated method stub System.out.println("大家好,我们名字叫Dota,来自 " + this.author + " 之手"); this.war3.getGameName(); } }
这是手动注入的配置:
<bean id="war3" class="com.ricky.zero.pojo.War3"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota"> <property name="author" value="冰蛙"></property> <property name="war3" ref="war3"></property> </bean>
这是自动注入的配置:
<bean id="war3" class="com.ricky.zero.pojo.War3"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota" autowire="byName">
<property name="author" value="冰蛙"></property>
</bean>
data实例中有一个com.ricky.zero.pojo.War3类型的属性,该属性的变量名为war3,所以bean被加载的时候会通过autowire的byName策略自动寻找
id或者name为war3的bean,完成自动注入.
事例测试:
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); Dota dota = (Dota)ctx.getBean("dota"); dota.getGameName();
测试结果:
大家好,我们名字叫Dota,来自 冰蛙 之手
大家好,我的名字叫魔兽争霸,相信你们很多人都已经认识我了.
不管有多少个properties通过ref属性去注入对象都不再去重新配置XML文件了,其优势不言而喻。
byType事例:
只需要修改Spring配置文件即可.
<bean id="war3" class="com.ricky.zero.pojo.War3"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota" autowire="byType"> <property name="author" value="冰蛙"></property> </bean>
data实例中有一个com.ricky.zero.pojo.War3类型的属性,所以bean被加载的时候会通过autowire的byType策略自动寻找类型为com.ricky.zero.pojo.War3
类型的bean,完成自动注入
直接运行测试并得到运行结果为:
大家好,我们名字叫Dota,来自 冰蛙 之手
大家好,我的名字叫魔兽争霸,相信你们很多人都已经认识我了.
byType有一个问题就是同一类型的bean不能同时出现.如下示例:
<bean id="war4" class="com.ricky.zero.pojo.War3"></bean> <bean id="war3" class="com.ricky.zero.pojo.War3"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota" autowire="byType">
若都是同一个类型com.ricky.zero.pojo.War3,只是id不同,在autowire的byType策略下会抛异常,异常如下:
expected single matching bean but found 2: war4,war3
在autowire的byType策略下,出现两个相同类型Spring不会去猜你想要的是哪个,而是直接抛错,让你自己来解决,目前只能先确保一个类型在Spring context中
只有一个对应的bean.若同一类型必须要出现多个bean而且还要运用byType策略,Spring提供2种方法解决上述问题,一种是通过识别出重要的候选者,
另一种是消除byType搜索的候选者.
识别重要的候选者事例:
修改Spring configuration配置文件:
<bean id="war4" class="com.ricky.zero.pojo.War3"></bean> <bean id="war3" class="com.ricky.zero.pojo.War3" primary="true"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota" autowire="byType"> <property name="author" value="冰蛙"></property> </bean>
若在一个bean中加上parmary="true"属性,byType策略会首先注入当前bean.同类中,只能有一个primary="true"属性,否则也会抛异常.
还有一种方法是消除byType搜索的候选者:
<bean id="war4" class="com.ricky.zero.pojo.War3" ></bean> <bean id="war3" class="com.ricky.zero.pojo.War3" autowire-candidate="false"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota" autowire="byType"> <property name="author" value="冰蛙"></property> </bean>
若哪个bean后面有autowire-candidate="false"属性时,byType搜索的时候将会跳过有autowire-candidate="false"属性的bean.
两个事例的测试结果为:
大家好,我们名字叫Dota,来自 冰蛙 之手
大家好,我的名字叫魔兽争霸,相信你们很多人都已经认识我了.
constructor事例:
public class War3 implements Game { @Override public void getGameName() { // TODO Auto-generated method stub System.out.println("大家好,我的名字叫魔兽争霸,相信你们很多人都已经认识我了."); } }
public class DotaPlayer implements Player { //已杀敌人总数 private int currentKill = 0; private War3 war3; //没有参数的构造方法 public DotaPlayer(){} //以基本类型为参数的构造方法 public DotaPlayer(int currentKill){ this.currentKill = currentKill; }; //以对象类型为参数的构造方法 public DotaPlayer(War3 war3){ this.war3 = war3; }; //重写Player类中的play()方法 @Override public void play() { // TODO Auto-generated method stub System.out.println("DOTA玩家已经杀了"+ this.currentKill +"个敌人."); war3.getGameName(); } }
Spring Configuration配置文件如下:
<bean id="war3" class="com.ricky.zero.pojo.War3"></bean> <bean id="dotaPlayer" class="com.ricky.zero.pojo.DotaPlayer" autowire="constructor"> </bean>
autowire="constructor"作用是搜寻与构造方法参数列表中参数变量的名称与bean的name相同的bean,然后注入构造方法中.
运行测试
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml"); DotaPlayer dotaPlayer = (DotaPlayer)ctx.getBean("dotaPlayer"); dotaPlayer.play();
测试结果:
DOTA玩家已经杀了0个敌人.
大家好,我的名字叫魔兽争霸,相信你们很多人都已经认识我了.
default:默认的自动装配策略是空,可以自定义默认的策略.
在你使用自动装配策略的时候,仍然可以使用手动指定装配.手动指定装配将会overriding自动装配,这样的混合装配在constructor-autowiring中是不允许的.
Spring必须装配所有的arguments.
2.通过注解装配bean
从Spring的2.5版本,Spring就支持用注解的方式去自动装载bean的properties.用注解的方式和用XML配置文件的方式大同小异.但是用注解可以更细粒度的
装配bean.注解在Spring configuration中默认是关闭的,如果要想使用注解,首先要打开注解.最简单的方法是在<beans>标签下添加如下语句.
<context:annotation-config/>
这个标签告诉Spring你打算使用注解去装配bean,当你使用注解的时候,注解可以装配值到properties,method,constructors中.
Spring支持3种自动装配注解:@Autowired,@Inject,@Resource.
@Autowired事例:
当你想用@Autowired去装配bean的id为dota的properties为wa3的bean的时候.(感觉不好理解就看下面的例子,一看就懂了,可能我的表达能力不有,慢慢提高哈.)
@Autowire注解自动装配的策略是byType
可以在properties的setter方法上加这个注解:
@Autowired public void setWar3(War3 war3) { this.war3 = war3; }
Spring configuration配置文件:
//用于打开Spring注解 <context:annotation-config /> <bean id="war3" class="com.ricky.zero.pojo.War3"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota"> <property name="author" value="冰蛙"></property> </bean>
在setter方法上加@Autowired相当于<properties name="变量名" ref="已经实例的对象"></properties> 这个标签的功能.@Autowired比XML配置
要简单,精巧,
@Autowired也可以用在构造方法上.
@Autowired public Dota(War3 war3) { this.war3 = war3; }
运行两次使用@Autowired的测试,并得到结果如下:
大家好,我们名字叫Dota,来自 冰蛙 之手
大家好,我的名字叫魔兽争霸,相信你们很多人都已经认识我了.
@Autowired也可以直接在properties上使用,而不用在setter方法上就可以完成注入.用注解以后就废除setter方法,直接在属性上注解.
例:
@Autowired private War3 war3;
运行测试结果如上.
当@Autowired没有搜索到匹配的bean,或者多个bean同时被注入的时候,Spring将会抛异常.
当没有搜索到匹配的bean的时候会抛异常:
expected at least 1 bean which qualifies as autowire candidate for this dependency
因为@Autowired有一个缺省值@Autowired(required=true),当required为true的时候,若没有匹配到bean则会抛异常.若把required设置成false,
则不会抛出上述异常,但是当你用到该属性的时候,会抛NullPointerException异常,因为你注入失败,属性值为null.
当有多个bean同时匹配时,将会抛出异常如下:
<bean id="war3" class="com.ricky.zero.pojo.War3"></bean> <bean id="war4" class="com.ricky.zero.pojo.War3"></bean> <bean id="dota" class="com.ricky.zero.pojo.Dota"> <property name="author" value="冰蛙"></property> </bean>
expected single matching bean but found 2: war3,war4
为了找这个异常的时候,发现一个有趣的事情,本来书上写@Autowired是按byType策略去注入的.当我在
//Dota玩家同样会玩War3 @Autowired private War3 war3;
当我的就是名为war3 ,与Spring configuration中的一个bean的id名称相同时,运行测试不会抛异常.若把变量名改成与两个bean不一样,则会抛上述异常.
该问题大家也考虑下,现在没有时间研究,有时间来探究下.
这种异常可以通过Spring的又一个注解来解决@Qualifier.
@Qualifier是用于通过ID来选择你想要的bean.简单来说就是@Autowired搜索出所有相同类型的候选bean,@Qualifier(value="要选择bean的id")
在这些候选bean中选择id与@Qualifier的value相同的bean.
//Dota玩家同样会玩War3 @Autowired @Qualifier(value="war3") private War3 war3;
这样就解决上述的异常问题了.
--------------------------------申明----------------------------
本文可以免费阅读以及转载,转载时请注明出处.
本人邮箱:Ricky_LS@163.com
Thank you for your corporation.