第三章 最小化SpringXml 配置

左心房为你撑大大i 提交于 2020-02-13 07:37:37

自动装配(autowiring):有助于减少甚至消除<property>元素和<constructor-arg>元素,让spring自动识别如何装配Bean的依赖关系。

自动检测(autodiscovery):比自动装配更进一步,让spring能够自动识别哪些类需要被装配成sping Bean ,从而减少对<bean>元素的使用。

3.1 自动装配Bean属性

3.1.1 4种类型的自动装配

  byName——把与Bean的属性具有相同的名字(或者ID)的其他Bean自动装配到Bean对应的属性中。如果没有跟属性的名字像匹配的Bean,则该属性不进行装配。

  byType——把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中,若果没有跟属性的类型相匹配的bean,则该属性不被装配。

  constructor——把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应参数中。

  autodetect——首先尝试使用constructor进行自动装配,如果失败,再尝试使用byType进行自动装配。

  byName自动装配:

 1 <!-- byName自动装配
 2          缺点:若是有多个音乐家需要装配instrument属性,则他们就会公用一个Saxopbone(即多个bean的属性被同一个bean赋值)
 3      -->
 4     <!-- 先在容器中装个乐器对象 -->
 5      <bean id="instrument" class="com.springinaction.springidol.Saxophone"></bean>
 6     <!--现在为音乐家kenny自动装配上面instrument的乐器-->
 7     <bean id="kenny" -->
 8         class="com.springinaction.springidol.Instrumentalist"
 9         autowire="byName">
10         <property name="song" value="演员——薛之谦"></property>    
11     </bean>
12     
13     <bean id="kenny1" 
14         class="com.springinaction.springidol.Instrumentalist"
15         autowire="byName">
16         <property name="song" value="演员1——薛之谦"></property>    
17     </bean> 
View Code

  byType自动装配,容器中若是有同个类型的多个bean,自动装配的时候,会出抛出异常(就像选择性综合征,多了不知选哪个了)NoUniqueBeanDefinitionException(不唯一bean定义异常,翻译的可能不准确):

 1 <!-- byType装配 
 2         有两个乐器:kenny bean就不知道要识别那个乐器装配给自己,
 3         会抛出异常NoUniqueBeanDefinitionException(非唯一bean定义异常-个人翻译,可能不正确),
 4         但是下面会出现提示:
 5         org.springframework.beans.factory.UnsatisfiedDependencyException: 
 6             Error creating bean with name 'kenny' defined in class path resource [spring/springbean.xml]: Unsatisfied dependency expressed through bean property 'instrument': No qualifying bean of type [com.springinaction.springidol.Instrument] is defined: expected single matching bean but found 2: com.springinaction.springidol.Saxophone#0,com.springinaction.springidol.Guitar#0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.springinaction.springidol.Instrument] is defined: expected single matching bean but found 2: com.springinaction.springidol.Saxophone#0,com.springinaction.springidol.Guitar#0
 7         Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.springinaction.springidol.Instrument] is defined: expected single matching bean but found 2: com.springinaction.springidol.Saxophone#0,com.springinaction.springidol.Guitar#0
 8     -->
 9     <!-- Saxophone类型的bean -->
10      <bean class="com.springinaction.springidol.Saxophone"></bean>
11     <!--Guitar类型的bean-->
12     <bean class="com.springinaction.springidol.Guitar"></bean>
13     
14     <bean id="kenny" 
15         class="com.springinaction.springidol.Instrumentalist"
16         autowire="byType">
17         <property name="song" value="演员——薛之谦"></property>    
18     </bean> 
View Code

  constructor自动装配:

1 <!-- constructor 自动装配
2         这个要求PoeticJuggler类中有一个构造器的参数是Sonnet29(他是--实现Poem的实现类)类型的
3      -->
4     <bean class="com.springinaction.springidol.Sonnet29"></bean>
5 
6     <bean id="duke" 
7         class="com.springinaction.springidol.PoeticJuggler"
8         autowire="constructor"/>
View Code

   autodetect混合装配,就不介绍了。

  注:在测试代码的时候,遇到了如下的SAXParseException异常:
Caused by: org.xml.sax.SAXParseException; lineNumber: 48; columnNumber: 45; 注释中不允许出现字符串 "--"。
    at ;
  这个是由于springbean.xml中的注释<!-- 这里是注释 -->除了开头和结尾可以有"--"外,里面不能有第三个"--",不如:<!-- 这里是是--注释 -->就会报上面的错误,这个一看就明白了。

3.1.2 默认自动装配

  若果需要为Spring应用上下文中的额每一个Bean(或者其中大多数)配置相同的autowire属性,那么可以要求spring为它所创建的所有Bean引用相同的自动装配策略来简化配置

3.2 使用注解装配

  使用注解方式允许更细粒度的自动装配,我们可以选择性标注某一个属性来对其应用自动装配。spring容器默认禁用注解装配,所以,在使用基于注解的自动装配,我们需要在spring配置中启用它。最简单的启用方式是使用spring的context命名空间配置中的<context:annotation-config>元素:

  spring的context命名空间: xmlns:context="http://www.springframework.org/schema/context";

   xsi:schemaLocation的值中要加:http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd;这两个东西(好像是约束,不知是啥)

  spring3支持几种不同的用于自动装配的注解:

  • spring自带的@Autowierd注解;
  • JSR-330的@Inject注解;
  • JSR-250的@Resource注解;

3.2.1 使用@Autowired

@Autowired是:org.springframework.beans.factory.annotation包下的Autowired接口如下:

  

  @Autowired的装配代码,比如,在Instrumentalist类型的setInstrument上标注@Autowired:

1         //注入乐器
2     @Autowired
3     public void setInstrument(Instrument instrument) {
4         this.instrument = instrument;
5     }    
View Code

  在springbean.xml中:

1 <!-- Saxophone类型的bean -->
2     <!-- <bean class="com.springinaction.springidol.Saxophone"></bean> -->
3     <!--Guitar类型的bean(Instrument 乐器)-->
4     <bean class="com.springinaction.springidol.Guitar"></bean> 
5     
6     <bean id="kenny" 
7         class="com.springinaction.springidol.Instrumentalist">
8         <property name="song" value="演员——薛之谦"></property>    
9     </bean>
View Code

  测试结果,这时容器中只有一个乐器类型的bean:

1 //测试注解@Autowired
2     @Test
3     public void testAutowired() throws Exception {
4         
5         Instrumentalist kenny = (Instrumentalist) ac.getBean("kenny");
6         kenny.perform();
7         kenny.getInstrument().play();
8         
9     }
View Code

   当springbean.xml中有两个乐器类型的bean时,会抛异常BeanCreationException,还是bean不唯一的问题:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'kenny': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.springinaction.springidol.Instrumentalist.setInstrument(com.springinaction.springidol.Instrument); nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.springinaction.springidol.Instrument] is defined: expected single matching bean but found 2: com.springinaction.springidol.Saxophone#0,com.springinaction.springidol.Guitar#0:

1 <!-- Saxophone类型的bean,下面两个bean同时存在的时候,会抛异常NoUniqueBeanDefinitionException -->
2     <bean class="com.springinaction.springidol.Saxophone"></bean>
3     <!--Guitar类型的bean(Instrument 乐器)-->
4     <bean class="com.springinaction.springidol.Guitar"></bean> 
5     
6     <bean id="kenny" 
7         class="com.springinaction.springidol.Instrumentalist">
8         <property name="song" value="演员——薛之谦"></property>    
9     </bean>
View Code

  上面就抛出异常;

  @Autowired可以在构造器上面和属性上面(这里标注了就可以把setter方法删掉了)都可以标注;

  当容器中没有自动装配的bean时,会抛出 NoSuchBeanDefinitionException(没有这样的bean定义异常):
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.springinaction.springidol.Instrumentalist.setInstrument(com.springinaction.springidol.Instrument); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.springinaction.springidol.Instrument] found for
  这时候@Autowired(required=false)这样装配,自动装配可选,容器中没有改类型的bean,instrument值会是空:
   @Autowired(required=false)
     private Instrument instrument;

  这时候抛异常会抛NullPointException;

  若是@Autowired注解用在构造器上时,只有一个构造器上required设置为true,其他使用@Autowired注解所标注的构造器只能将required属性设置为false。

为解决上述问题,可以使用注解@Qualifier("guitar"),配置如下,这种配置和第二章中的<property name='instrument' ref='guitar'>原理其实应该差不多(个人觉得):

1         @Autowired(required=false)
2     @Qualifier("guitar")
3     private Instrument instrument;    
View Code
1 <bean id="saxophone" class="com.springinaction.springidol.Saxophone"></bean> 
2     <!--Guitar类型的bean(Instrument 乐器)-->
3     <bean id="guitar" class="com.springinaction.springidol.Guitar"></bean> 
4     
5     <bean id="kenny" 
6         class="com.springinaction.springidol.Instrumentalist">
7         <property name="song" value="演员——薛之谦"></property>    
8     </bean>
View Code

  还可以如下进行配置:

1 @Autowired(required=false)
2 //@Qualifier("guitar")
3 private Instrument instrument;
View Code
 1 package com.springinaction.springidol;
 2 
 3 import org.springframework.beans.factory.annotation.Qualifier;
 4 
 5 /**
 6  * 
 7  * @ClassName: Guitar 
 8  * @Description: 乐器:吉他
 9  * @author mao
10  * @date 2017年3月19日 下午8:15:44 
11  *
12  */
13 @Qualifier("stringed")
14 public class Guitar implements Instrument {
15     
16     public Guitar(){
17         
18     }
19     
20     public void play() {
21         System.out.println("guitar guitar guitar");
22     }
23 
24 }
View Code
1 <bean class="com.springinaction.springidol.Guitar">
2     <qualifier value="stringed"></qualifier>
3 </bean> 
4     
5 <bean id="kenny" 
6     class="com.springinaction.springidol.Instrumentalist">
7     <property name="song" value="演员——薛之谦"></property>    
8 </bean>
View Code

测试了一下<qualifier value="stringed"></qualifier>这个有没有都可以哎,我用的spring版本是spring4.2.9的,难道版本高了,功能也自动升级了,不是很明白:

 创建自定义的限定器(Qualifiler)(这个感觉好吊的样子,因为看不懂,感觉像是自己创建了个自定义的注解)

 首先创建一个接口:

 1 package com.springinaction.springidol;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 import org.springframework.beans.factory.annotation.Qualifier;
 9 
10 @Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
11 @Retention(RetentionPolicy.RUNTIME)
12 @Qualifier
13 public @interface StringedInstrument {
14 
15 }
View Code
 1 package com.springinaction.springidol;
 2 
 3 
 4 /**
 5  * 
 6  * @ClassName: Guitar 
 7  * @Description: 乐器:吉他
 8  * @author mao
 9  * @date 2017年3月19日 下午8:15:44 
10  *
11  */
12 @StringedInstrument//好嘛?这应该是自定义的注解吧
13 public class Guitar implements Instrument {
14     
15     public Guitar(){
16         
17     }
18     
19     public void play() {
20         System.out.println("guitar guitar guitar");
21     }
22 
23 }
View Code
1 @Autowired
2 //@Qualifier("guitar")
3 @StringedInstrument
4 private Instrument instrument;
View Code
1 <bean class="com.springinaction.springidol.Guitar">
2     <!-- <qualifier value="stringed"></qualifier> 这一行有没有不影响 -->
3 </bean> 
4     
5 <bean id="kenny" 
6     class="com.springinaction.springidol.Instrumentalist">
7     <property name="song" value="演员——薛之谦"></property>    
8 </bean>
View Code
1 //测试注解@Autowired @StringedInstrument
2     @Test
3     public void testAutowired() throws Exception {
4         
5         Instrumentalist kenny = (Instrumentalist) ac.getBean("kenny");
6         kenny.perform();
7         kenny.getInstrument().play();
8         
9     }
View Code

如果有多个乐器类被@StringedInstrument标注了,还需要再进行细粒度的控制,感觉这个太麻烦!要自定义很多个限定器!

3.2.2 借助@Inject实现基于标准的自动装配

JSR-330是一种依赖注入规范,更常见的叫法at inject

这个要用到新的jar文件,javax.inject,所以pom.xml文件中需要引入该jar依赖;

1 <!-- JSR-330的标准注解 -->
2  <dependency>
3       <groupId>javax.inject</groupId>
4       <artifactId>javax.inject</artifactId>
5       <version>1</version>
6   </dependency>
View Code

这个jar内容如下,总共就这些类:

 

@Inject注解和@Autowired一样,可以用来自动装配属性、方法和构造器;与@Autowired不同的是,@Inject没有required属性。

@Inject他也可以限定,用@Name,@Autowired用的@Qualifier:

1 @Inject
2 @Named("guitar")
3 private Instrument instrument;
View Code

基本用法和@Autowired差不多,@Name和@Qualifier区别:前者是通过Bean的ID来表示可选择的Bean,后者是帮助我们缩小匹配Bean的选择范围(目前没有感觉到太大的差一性);

3.2.3 在注解注入中使用表达式

Spring3.0引入了@Value,可以装配String类型的值和基本类型的值,例如。

@Value("Eruption")

private String song;

@Value与SpEL表达式配合,才能显示他的魔力(第二章中的SpEL表达式);

3.3 自动检测Bean

  <context:annotation-config/>需要显示定义<bean>,用<context:annotation-scan>允许spring自动检测Bean和定义Bean。为了配置Spring自动检测,需要使用<context:conponent-scan>元素代替<context:annotation-config>元素,元素会扫描指定的包及其所有子包,并查处自动注册的Spring Bean的类。

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:p="http://www.springframework.org/schema/p"
 6     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 7         http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 8         http://www.springframework.org/schema/context
 9         http://www.springframework.org/schema/context/spring-context-4.0.xsd">
10      <context:component-scan 
11         base-package="com.springinaction.springidol">            </context:component-scan>
12 </beans>
View Code

3.3.1 为自动检测标注Bean

  <context:componment-scan>查找使用构造型(stereotype)注解所标注的类:

  • @Component——通用的构造型注解,标识该类为Spring组件
  • @Constroller——标识将该类定义为SpringMVC controller
  • @Repository——标识将该类定义为数据仓库
  • @Service——标识将该类定义为服务
 1 package com.springinaction.springidol;
 2 
 3 import org.springframework.stereotype.Component;
 4 
 5 
 6 /**
 7  * 
 8  * @ClassName: Guitar 
 9  * @Description: 乐器:吉他
10  * @author mao
11  * @date 2017年3月19日 下午8:15:44 
12  *
13  */
14 //自动将该类注册为Spring Bean。Bean的ID默认无限定类名。在这种场景下,Guitar Bean的ID为guitar
15 @Component
16 public class Guitar implements Instrument {
17     
18     public Guitar(){
19         
20     }
21     
22     public void play() {
23         System.out.println("guitar guitar guitar");
24     }
25 
26 }
View Code
 1 package com.springinaction.springidol;
 2 
 3 import javax.inject.Inject;
 4 import javax.inject.Named;
 5 
 6 import org.springframework.beans.factory.annotation.Value;
 7 import org.springframework.stereotype.Component;
 8 
 9 
10 
11 /**
12  * 
13 * @ClassName: Instrumentalist 
14 * @Description: 一个有天赋的音乐家
15 * @author mao
16 * @date 2017年3月19日 下午7:15:17 
17 *
18  */
19 //将该类注册到Spring容器中,显示的为其命名为eddie。即ID为eddie
20 @Component("eddie")
21 public class Instrumentalist implements Performer {
22     
23     @Value("演员--薛之谦")
24     private String song;
25     
26     @Inject
27     @Named("guitar")
28     private Instrument instrument;
29     
30     //注入乐器
31     public void setInstrument(Instrument instrument) {
32         this.instrument = instrument;
33     }
34     public Instrument getInstrument() {
35         return instrument;
36     }
37     //注入歌曲
38     public void setSong(String song) {
39         this.song = song;
40     }
41     public String getSong() {
42         return song;
43     }
44     
45     public Instrumentalist(){
46         
47     }
48 
49     public void perform() throws Exception {
50         System.out.println("Playing "+song+": ");
51     }
52 
53 }
View Code
1 //测试注解@Component
2 @Test
3 public void testComponent() throws Exception {
4         
5     Instrumentalist eddie = (Instrumentalist) ac.getBean("eddie");
6     eddie.perform();
7     eddie.getInstrument().play();
8         
9 }
View Code

测试结果:

3.4 使用spring基于java的配置

3.4.1 创建基于Java的配置

  即使Spring的java配置可以使用XML就可以编写大多数的Spring配置,但是我们仍然需要极少量的XML来启用Java配置:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:p="http://www.springframework.org/schema/p"
 6     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 7         http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 8         http://www.springframework.org/schema/context
 9         http://www.springframework.org/schema/context/spring-context-4.0.xsd">
10     <context:component-scan 
11         base-package="com.springinaction.springidol*"></context:component-scan>
12 
13 </beans>
View Code

  首先创建一个类,用@Configuration标注该类,使用它标注后,这个类就相当于<beans>容器了:

 1 package com.springinaction.springidol;
 2 
 3 import org.springframework.context.annotation.Bean;
 4 import org.springframework.context.annotation.Configuration;
 5 
 6 //在基于java的配置里使用@Configuration注解java类,就等价于XML配置中的<beans>
 7 //@Configuration注解会作为一个标识告知Spring:这个类将包含一个多个SpringBean的定义。
 8 //这些Bean的定义是使用@Bean注解所标注的方法。
 9 @Configuration
10 public class SpingIdolConfig {
11     
12     /*
13      * 它等价于使用XML所配置的<bean>元素。@Bean告知Sping这个方法返回一个对象,
14      * 该对象应该被注册为Spring应用上下文中的一个Bean。方法名将作为该Bean的ID
15      */
16     @Bean
17     public Performer duke(){
18         return new Juggler();
19     }
20     
21     @Bean
22     public Performer kenny(){
23         
24         Instrumentalist kenny = new Instrumentalist();
25         kenny.setSong("认真的雪Bean----薛之谦");
26         kenny.setInstrument(guitar());
27         return kenny;
28         
29     }
30     
31     @Bean
32     public Instrument guitar(){
33         return new Guitar();
34     }
35     
36 }
View Code

  此时Instrumentalist类是这样的,两个属性的注解被注释了:

 1 package com.springinaction.springidol;
 2 
 3 import javax.inject.Inject;
 4 import javax.inject.Named;
 5 
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.beans.factory.annotation.Qualifier;
 8 import org.springframework.beans.factory.annotation.Value;
 9 import org.springframework.stereotype.Component;
10 
11 
12 
13 /**
14  * 
15 * @ClassName: Instrumentalist 
16 * @Description: 一个有天赋的音乐家
17 * @author mao
18 * @date 2017年3月19日 下午7:15:17 
19 *
20  */
21 //将该类注册到Spring容器中,显示的为其命名为eddie。即ID为eddie
22 
23 public class Instrumentalist implements Performer {
24     
25 //    @Value("演员---薛之谦")
26     private String song;
27     
28 //    @Autowired
29 //    @Qualifier("saxophone")
30     private Instrument instrument;
31     
32     //注入乐器
33     public void setInstrument(Instrument instrument) {
34         this.instrument = instrument;
35     }
36     public Instrument getInstrument() {
37         return instrument;
38     }
39     //注入歌曲
40     public void setSong(String song) {
41         this.song = song;
42     }
43     public String getSong() {
44         return song;
45     }
46     
47     public Instrumentalist(){
48         
49     }
50 
51     public void perform() throws Exception {
52         System.out.println("Playing "+song+": ");
53     }
54 
55 }
View Code

  测试代码:

1 //测试基于java的配置
2     @Test
3     public void testJava() throws Exception {
4         
5         Instrumentalist eddie = (Instrumentalist) ac.getBean("kenny");
6         eddie.perform();
7         eddie.getInstrument().play();
8         
9     }
View Code

  结果:

  这个基于java的注解,若是将上述的两个注释的注解解封:

 1 package com.springinaction.springidol;
 2 
 3 import javax.inject.Inject;
 4 import javax.inject.Named;
 5 
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.beans.factory.annotation.Qualifier;
 8 import org.springframework.beans.factory.annotation.Value;
 9 import org.springframework.stereotype.Component;
10 
11 
12 
13 /**
14  * 
15 * @ClassName: Instrumentalist 
16 * @Description: 一个有天赋的音乐家
17 * @author mao
18 * @date 2017年3月19日 下午7:15:17 
19 *
20  */
21 //将该类注册到Spring容器中,显示的为其命名为eddie。即ID为eddie
22 
23 public class Instrumentalist implements Performer {
24     
25     @Value("演员---薛之谦")
26     private String song;
27     
28     @Autowired
29     @Qualifier("saxophone")
30     private Instrument instrument;
31     
32     //注入乐器
33     public void setInstrument(Instrument instrument) {
34         this.instrument = instrument;
35     }
36     public Instrument getInstrument() {
37         return instrument;
38     }
39     //注入歌曲
40     public void setSong(String song) {
41         this.song = song;
42     }
43     public String getSong() {
44         return song;
45     }
46     
47     public Instrumentalist(){
48         
49     }
50 
51     public void perform() throws Exception {
52         System.out.println("Playing "+song+": ");
53     }
54 
55 }
View Code

  结果就是:

  这个感觉像是就近原则啊(xml的注解在属性上方),要么是先是执行了基于java的配置,后又执行了基于xml的配置,xml的配置覆盖了前者的结果。

 源码下载地址

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!