8.2.7 Spring5新增的注解

在使用@Autowired注解执行自动装配时,该注解可指定一个required属性,该required属性默认为true这意味着该注解修饰的Fieldsetter方法必须被依赖注入,否则Spring会在初始化容器时报错。

@Autowired注解自动装配与XML中autowire=”byType”自动装配的区别

@Autowired与在XML中指定autowire="byType"的自动装配存在区别:

  • autowire="byType的自动装配如果找不到自动装配的候选Bean, 则Spring容器只是不执行注入,并不报错;
  • @Autowired的自动装配如果找不到自动装配的候选Bean, Spring容器会直接报错

如何让@Autowired自动装配在找不到候选Bean是不报错

为了让@Autowired的自动装配找不到候选Bean时不报错(只是不执行依赖注入),现在有两种解决方式:

  1. @Autowiredrequired属性指定为false
  2. 使用Spring5新增的@Nullable注解。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Nullable
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Dog.java
├─impl\
│ ├─Chinese.java
│ └─GunDog.java
└─Person.java

如下代码示范了这两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Chinese implements Person
{
private Dog dog;
@Autowired(required = false)
public void setGunDog(@Nullable Dog dog)
{
this.dog = dog;
}
public void test()
{
System.out.println("我是一个普通人,养了一条狗:" + dog.run());
}
}

上面程序同时使用了两种方式来指定setGunDog()方法找不到被装配的Bean时不报错,实际上只需要使用其中之一即可。也就是说,如果给@Autowired指定了required=false属性,就可以不使用@Nullable注解;如果使用@Nullable 注解,就可以不给@Autowired指定了required=false属性;

Spring5其他新注解

此外, Spring5还引入了如下新的注解。

注解 描述
@NonNull 该注解主要**用于修饰参数**、返回值和Field,声明它们不允许为null
@NonNullApi 该注解用于修饰,表明该包内API的参数、返回值都不应该为null。如果希望该包内某些参数、返回值可以为null则需要使用@Nullable修饰它们。
@NonNullFields 该注解也用于修饰,表明该包内的Field都不应该为null如果希望该包内某些Field可以为null,则需要使用@Nullable修饰它们。

这三个NonNullxxx注解的区别

从上面介绍不难看出,这三个注解的功能基本相似,区别只是作用范围不同:

  • @NonNull每次只能影响被修饰的参数返回值Field;
  • @NonNullApi@NonNullFields则会对整个包起作用。其中,
    • @NonNullApi的作用范围是包内所有参数+返回值;
    • @NonNullFields的作用范围是包内所有Field

@Qualifier注解

正如上面看到的,@Autowired总是采用byType的自动装配策略,在这种策略下,符合自动装配类型的候选Bean实例常常有多个,这个时候就可能引起异常(对于数组类型参数、集合类型参数则不会)。
为了实现精确的自动装配, Spring提供了@Qualifier注解,通过使用 @Qualifier,允许根据Beanid来执行自动装配
使用@Qualifier注解的意义并不大,如果程序使用@Autowired@Qualifier实现精确的自动装配,还不如直接使用@Resource注解执行依赖注入。

@Qualifier修饰实例变量

@Qualifier通常可用于修饰实例变量,如下面的代码所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class Chinese implements Person
{
@Autowired
@Qualifier("steelAxe")
private Axe axe;
// // axe的setter方法
// @Autowired
// public void setAxe(@Qualifier("stoneAxe") Axe axe)
// {
// this.axe = axe;
// }
// 实现Person接口的useAxe()方法
public void useAxe()
{
// 调用axe的chop()方法,
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}

上面的程序中指定了axe实例变量将使用自动装配,且精确指定了被装配的Bean实例名称是steelAxe,这意味着Spring将会搜索容器中名为steelAxeAxe实例,并将该实例设为该axe实例变量的值。

@Qualifier修饰方法的形式参数

除此之外, Spring还允许使用@Qualifier标注方法的形参,如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class Chinese implements Person
{
// @Autowired
// @Qualifier("steelAxe")
private Axe axe;
// axe的setter方法
@Autowired
public void setAxe(@Qualifier("stoneAxe") Axe axe)
{
this.axe = axe;
}
// 实现Person接口的useAxe()方法
public void useAxe()
{
// 调用axe的chop()方法,
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}

上面代码中的粗体字注解指明Spring应该搜索容器中idsteelAxeAxe实例,并将该实例作为setAxe()方法的参数传入。

@Primary注解

Spring提供了一个@Primary注解,该注解用于将指定的候选Bean设置为主候选者Bean

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\Primary
└─src\
├─beans.xml
├─lee\
│ └─SpringTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Dog.java
├─impl\
│ ├─Chinese.java
│ ├─GunDog.java
│ └─PetDog.java
└─Person.java

例如如下Chinese类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Chinese implements Person
{
private Dog dog;
@Autowired
public void setGunDog(Dog dog)
{
this.dog = dog;
}
public void test()
{
System.out.println("我是一个普通人,养了一条狗:" + dog.run());
}
}

上面Chinese类使用@Autowired修饰了setGunDog(Dog dog)方法,这意味着Spring会从容器中寻找类型为DogBean来完成依赖注入,如果容器中有两个类型为DogBean, Spring容器就会引发异常。
此时可通过@Primary注解修饰特定Bean类,将它设置为主候选者,这样Spring将会直接注入有@Primary修饰的Bean,不会理会其他符合类型的Bean。例如如下PetDog类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
@Primary
public class PetDog implements Dog
{
@Value("小花")
private String name;
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public String run()
{
return "我是一只叫" + getName() + "的宠物狗,只能慢慢地跑...";
}
}

上面程序使用@Primary修饰了PetDog类,因此该Bean将会作为主候选者Bean,所以Spring将会把该Bean作为参数注入所有需要Dog实例的setter方法、构造器中。

8.2.6 自动装配和精确装配

@Autowired注解

Spring提供了@Autowired注解来指定自动装配,@Autowired可以修饰setter方法、普通方法实例变量构造器等。

@Autowired修饰setter时

当使用@Autowired标注setter方法时,默认采用by Type自动装配策略
例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Chinese implements Person
{
...
@Autowired
public void setAxe(Axe axe)
{
this.axe = axe;
}
...
}

上面的代码中使用了@Autowired指定对setAxe()方法进行自动装配, Spring将会自动搜索容器中类型为AxeBean实例,并将该Bean实例作为setAxe()方法的参数传入。

  • 如果正好在容器中找到一个类型为AxeBean, Spring就会以该Bean为参数来执行setAxe()方法;
  • 如果在容器中找到多个类型为AxeBean, Spring会引发异常;
  • 如果在容器中没有找到多个类型为AxeBean, Spring什么都不执行,也不会引发异常

@Autowired修饰普通方法时

Spring还允许使用@Autowired来标注多个参数的普通方法,如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class Chinese implements Person
{
...
@Autowired
public void prepare(Axe axe, Dog dog)
{
this.axe=axe;
this.dog=dog;
}
...
}

当使用@Autowired修饰带多个参数的普通方法时, Spring会自动到容器中寻找类型匹配的Bean,如果恰好为每个参数都找到一个类型匹配的Bean, 则Spring会自动以这些Bean作为参数来调用该方法。
以上面的prepare(Axe axe, Dog dog)方法为例, Spring会自动寻找容器中类型为AxeBean以及类型为DogBean,如果在容器中恰好找到一个类型为Axe和一个类型为DogBean, Spring就会以这两个Bean作为参数来调用prepare方法。

@Autowired也可用于修饰构造器和实例变量,如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Chinese implements Person
{
...
@Autowired
private Axe axe;
@Autowired
public Chinese(Axe axe, Dog dog)
{
this.axe=axe;
this.dog=dog;
}
...
}

@Autowired修饰实例变量时

当使用@Autowired修饰一个实例变量时, Spring将会把容器中与该实例变量类型匹配的Bean设置为该实例变量的值。
例如,程序中使用@Autowired标注了private Axe axe这个实例变量,则Spring会自动搜索容器中类型为AxeBean

  • 如果恰好找到一个该类型的Bean, 那么Spring就会将该Bean设置成axe实例变量的值;
  • 如果容器中包含多于一个的Axe实例,则Spring容器会抛出BeanCreateException异常。

@Autowired修饰构造时

@Autowired修饰构造时的注入规则与修饰普通方法的规则相同。

@Autowired修饰数组类型的成员变量时

@Autowired甚至可以用于修饰数组类型的成员变量,如下面的代码所示:

1
2
3
4
5
6
7
@Component
public class Chinese implements Person
{
@Autowired
private Axe[] axes;
...
}

正如在上面的程序中看到的,被@Autowired修饰的axes实例变量的类型是Axe数组,在这种情况下, Spring会自动搜索容器中的所有Axe实例,并以这些Axe实例作为数组元素来创建数组,最后将该数组赋给上面Chinese实例的axes实例变量。

@Autowired修饰集合类型的实例变量时

与此类似的是,@Autowired也可标注集合类型的实例变量,或标注形参类型是集合的方法, Spring对这种集合属性集合形参的处理与前面对数组类型的处理是完全相同的。例如如下代码:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class Chinese implements Person
{
private Set<Axe> axes;
@Autowired
public void setAxes(Set<Axe> axes)
{
this.axes=axes;
}
...
}

@Autowired修饰集合类型的实例变量时 必须使用泛型

对于这种集合类型的参数而言,程序代码中必须使用泛型,正如上面程序中的代码所示,程序指定了该方法参数是Set<Axe>类型,这表明Spring会自动搜索容器中的所有Axe实例,并将这些实例注入到axes实例变量中。
如果程序没有使用泛型来指明集合元素的类型,则Spring不知道搜索那些类型的实例来注入集合。

@Autowired主机默认使用byType策略来自动装配

由于@Autowired默认使用byType策略来完成自动装配,系统可能出现有多个匹配类型的候选组件,此时就会导致异常。

@Autowired根据泛型进行自动装配

Spring 4.0增强后的@Autowired注解还可以根据泛型进行自动装配。例如,项目中定义了如下Dao组件(后文会介绍,Dao组件是Java EE应用中最重要的一类组件,用于执行数据库访问),本示例的基础Dao组件代码如下。

1
2
3
4
5
6
7
8
9
10
11
package org.crazyit.app.dao.impl;

import org.crazyit.app.dao.*;

public class BaseDaoImpl<T> implements BaseDao<T>
{
public void save(T e)
{
System.out.println("程序保存对象:" + e);
}
}

BaseDaolmpl类中定义了所有Dao组件都应该实现的通用方法,而应用的其他Dao组件则只要继承BaseDaolmpl,并指定不同泛型参数即可。例如如下UserDaolmplItemDaoImpl

1
2
3
4
@Component("itemDao")
public class ItemDaoImpl extends BaseDaoImpl<Item> implements ItemDao
{
}
1
2
3
4
@Component("userDao")
public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao
{
}

接下来程序希望定义两个Service组件: UserServicelmplItemServicelmp,而UserServicelmpl需要依赖于UserDaolmpl组件, ItemServicelmpl需要依赖于ItemDaolmpl组件,传统的做法可能需要为UserServicelmplItemServicelmpl分别定义成员变量,并配置依赖注入。
考虑到UserDaolmplItemServicelmpl依赖的都是BaseDaolmpl组件的子类,只是泛型参数不同而程序可以直接定义一个BaseServicelmpl,该组件依赖于BaseDaolmpl即可。例如如下代码:

1
2
3
4
5
6
7
8
9
public class BaseServiceImpl<T> implements BaseService<T>
{
@Autowired
private BaseDao<T> dao;
public void addEntity(T entity)
{
System.out.println("调用" + dao + "保存实体:" + entity);
}
}

上面程序中两行粗体字代码指定Spring应该寻找容器中类型为Basedao<T>Bean,并将该Bean设置为dao实例变量的值。注意到BaseDao<T>类型中的泛型参数T, Spring不仅会根据Basedao类型进行搜索,还会严格匹配泛型参数T
接下来程序只要定义如下UserServicelmpl即可

1
2
3
4
5
@Component("userService")
public class UserServiceImpl extends BaseServiceImpl<User>
implements UserService
{
}

UserServiceImpl继承了BaseServiceImpl<User>,这就相当于指定了上面BaseDao<T>类型中T的类型为User,因此Spring会在容器中寻找类型为BaseDao<User>Bean—此时会找到UserDaoImpl组件,从而实现将UserDaolmpl注入UserServiceImpl组件的功能。
ItemServiceImpl的处理方法也与此类似,这样就可以很方便地将ItemDaolmpl注入ItemServiceImpl组件——而程序只要在UserServicelmplItemServiceImpl的基类中定义成员变量,并配置依赖注入即可,这就是Spring从4.0开始增强的自动装配。
该示例的主程序如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BeanTest
{
public static void main(String[] args) throws Exception
{
// 创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
UserService us = ctx.getBean("userService", UserService.class);
us.addEntity(new User());
ItemService is = ctx.getBean("itemService", ItemService.class);
is.addEntity(new Item());
}
}

该主程序只是获取容器中的userServiceitemService两个Bean,并调用它们的方法。编译、运行该示例,可以看到如下输出:

1
2
调用org.crazyit.app.dao.impl.UserDaoImpl@133e16fd保存实体:org.crazyit.app.domain.User@51b279c9
调用org.crazyit.app.dao.impl.ItemDaoImpl@1ad282e0保存实体:org.crazyit.app.domain.Item@7f416310

从上面输出可以看出,@Autowired可以精确地利用泛型执行自动装配,这样即可实现将UserDaolmpl注入UserServiceImpl组件,将ItemDaolmpl注入Item Servicelmpl组件。

8.2.5 使用@DependsOn和@Lazy改变初始化行为

  • @DependsOn用于强制初始化其他Bean;
  • @Lazy用于指定该Bean是否取消预初始化。

@DependsOn注解介绍

@DependsOn可以修饰Bean类或方法,使用该注解时可以指定一个字符串数组作为参数,每个数组元素对应于一个要强制初始化的Beanid。如以下代码所示:

1
2
3
4
5
6
@DependsOn({"steelAxe", "abc"})
@Component
public class Chinese implements Person
{
...
}

上面的代码使用了@DependsOn修饰Chinese类,这就指定在初始化idchinese这个Bean之前,会强制初始化idsteelAxeabc的这两个Bean.

@Lazy注解介绍

@Lazy修饰Spring Bean类用于指定该Bean的预初始化行为,使用该注解时可指定一个boolean型的value属性,该属性决定是否要预初始化该Bean。如果valuetrue则表示懒加载,也就是不会预初始化。
例如如下代码:

1
2
3
4
5
6
@Lazy(true)
@Component
public class Chinese implements Person
{
...
}

上面的粗体字注解指定当Spring容器初始化时,不会预初始化chinese这个bean

8.2.4 使用@PostConstruct和@PreDestroy定制生命周期行为

@PostConstruct@PreDestroy同样位于Javax.annotation包下,也是来自Java EE规范的两个注解,Spring直接借鉴了它们,用于定制Spring容器中Bean的生命周期行为。

XML配置如何指定生命周期方法

前面介绍Spring生命周期时提供了<bean>元素可以指定init-methoddestroy-method两个属性。

bean元素的属性 描述
init-method属性 用于指定Bean的初始化方法,Spring容器将会在Bean的依赖关系注入完成后回调该方法。
destroy-method属性 用于指定Bean销毁之前的方法,Spring容器将会在销毁该Bean之前回调该方法。

使用注解如何指定生命周期方法

@PostConstruct@PreDestroy两个注解的作用大致与此相似,它们都用于修饰方法,无须任何属性。
其中

  • @PostConstruct修饰的方法是Bean的初始化方法;
  • @PreDestroy修饰的方法是Bean销毁之前的要调用方法。

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\lifecycle
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
├─impl\
│ ├─Chinese.java
│ └─SteelAxe.java
└─Person.java

例如如下Bean实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package org.crazyit.app.service.impl;

import org.springframework.stereotype.*;
import javax.annotation.*;
import org.crazyit.app.service.*;

@Component
public class Chinese implements Person
{
// 执行Field注入
@Resource(name = "steelAxe")
private Axe axe;
// 实现Person接口的useAxe()方法
public void useAxe()
{
// 调用axe的chop()方法,
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
@PostConstruct
public void init()
{
System.out.println("正在执行初始化的init方法...");
}
@PreDestroy
public void close()
{
System.out.println("正在执行销毁之前的close方法...");
}
}

上面的Chinese类中使用了@PostConstruct修饰init()方法,这就让Spring在该Bean的依赖关系注入完成之后回调该方法;使用了@PreDestroy修饰close()方法,这就让Spring在销毁该Bean之前回调该方法。

8.2.3 使用@Resource和@Value配置依赖

@Resource位于Javax.annotation包下,是来自Java EE规范的一个注解, Spring直接借鉴了该注解,通过使用该注解为目标Bean指定协作者Bean

使用@Resource来注入其他Bean

@Resource有一个name属性,在默认情况下, Spring将这个name属性的值解释为需要被注入的Bean实例的id,换句话说,使用@Resource<property>元素的ref属性有相同的效果

使用@Value来注入直接量

@Value则相当于<property>元素的value属性,用于为Bean的标量属性配置属性值。@Value注解还可使用表达式。

@Resource @Value直接修饰实例变量

@Resource@Value不仅可以修饰setter方法,也可以直接修饰实例变量。如果使用@Resource@Value修饰实例变量将会更加简单,此时Spring将会直接使用Java EE规范的Field注入,此时连setter方法都可以不要。

例如如下的Bean类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.crazyit.app.service.impl;

import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;
import javax.annotation.*;
import org.crazyit.app.service.*;

@Component
public class Chinese implements Person
{
@Value("#{T(Math).PI}")
private String name;
private Axe axe;
// axe的setter方法
@Resource(name = "stoneAxe")
public void setAxe(Axe axe)
{
this.axe = axe;
}
// 实现Person接口的useAxe()方法
public void useAxe()
{
System.out.println(name + "正在砍柴");
// 调用axe的chop()方法,
// 表明Person对象依赖于axe对象
System.out.println(axe.chop());
}
}

上面的Chinese类中:

  • 使用了@Value注解为name成员变量设置值,且该值使用了SpEL表达式;
  • 定义了一个@Resource注解,该注解将容器中idstoneAxe的另一个Bean作为setAxe方法的参数

省略name属性时@Resource注解如何确定要注入哪个Bean

Spring允许使用@Resource时省略name属性,当使用省略name属性的@Resource修饰setter方法时,name属性值默认为该setter方法去掉前面的set子串、首字母小写后得到的子串。

使用setter方法名称来确定要注入的Bean

  • 例如,使用@Resource标注setName()方法,则Spring默认会注入容器中名为name的组件;当使用省略name属性的@Resource修饰实例变量时,name属性值默认与该实例变量同名。

使用示例变量的名称来确定要注入的Bean

  • 例如,使用@Resource标注name实例变量,则Spring默认会注入容器中名为name的组件。

8.2.2 指定Bean的作用域

XML方式如何指定Bean的作用域

当使用XML配置方式来配置Bean实例时,可以通过在<bean>元素上设置scope属性来指定Bean实例的作用域,没有指定scope属性的Bean实例的作用域默认是singleton.

通过@Scope指定作用域

当采用零配置方式来管理Bean实例时,可使用@Scope注解,只要在该注解中提供作用域的名称即可。例如可以定义如下Java

1
2
3
4
5
6
7
// 指定该bean的作用域为prototyep
@Scope("prototype")
@Component("axe")
public class SteelAxe implements Axe
{
...
}

自定义作用域

在一些极端的情况下,如果不想使用基于注解的方式来指定作用域,而是希望提供自定义的作用域解析器,让自定义的解析器实现ScopeMetadataResolver接口,并提供自定义的作用域解析策略,然后在配置扫描器时指定自定义的作用域解析器的全限定类名即可。看如下配置片段:

1
2
3
4
5
6
7
8
<beans ...>
...
// 指定自定义的作用域解析器的全限定类名
<context: component-scan
base-package="org.crazyit.app"
scope-resolver="org. crazyit.app.util.MyScopeResolver"/>
...
</beans>

新增的简写的作用域注解

此外,从Spring4.3开始还新增了@ApplicationScope@SessionScope@RequestScope这3个注解,它们分别对应于@Scope("application")@Scope("session")@Scope("request"),且proxyMode属性被设置为ScopedProxyMode.TARGET_CLASS

8.2 Spring的”零配置”支持

以前的Java框架大多采用XML作为配置文件,时至今日,也许是受Rails框架的启发,几乎所有的主流Java框架都打算支持"零配置"特性。现在要介绍的Spring,都开始支持使用注解来代替XML配置文件。

8.2.1 搜索Bean类

既然不再使用Spring配置文件来配置任何Bean实例,那么只能希望Spring会自动搜索某些路径下的Java类,并将这些Java类注册成Bean实例。

Rails框架的做法

Rails框架的处理比较简单,它采用一种所谓的”约定优于配置”的方式,它要求将不同组件放在不同路径下,而**Rails框架中是加载固定路径下的所有组件**。

Spring框架的做法

Spring没有采用”约定优于配置”的策略,** Spring依然要求程序员显式指定搜索哪些路径下的Java类**, **Spring将会把合适的Java类全部注册成Spring Bean**。

标注Bean类的注解

那现在的问题是:Spring怎么知道应该把哪些Java类当成Bean类处理呢?这就需要使用注解了, Spring通过使用一些特殊的注解来标注Bean类。Spring提供了如下几个注解来标注Spring Bean

注解 描述
@Component 标注一个普通的Spring Bean类。
@Controller 标注一个控制器组件类。
@Service 标注一个业务逻辑组件类。
@Repository 标注一个DAO组件类。

如果需要定义一个普通的Spring Bean,则直接使用@Component标注即可。但如果用@Reepository@Service@Controller来标注这些Bean类,这些Bean类将被作为特殊的Java EE组件对待,也许能更好地被工具处理,或与切面进行关联。例如,这些典型化的注解可以成为理想的切入点目标。
@Controller@Service@Repository能携带更多语义,因此,如果需要在Java EE应用中使用这些标注时,应尽量考虑使用@Controller@Service@Repository来代替通用的 @Component标注。

使用context Schema指定搜索路径

指定了某些类可作为Spring Bean类使用后,最后还需要让Spring搜索指定路径,此时需要在Spring配置文件中导入 context Schema,并指定一个简单的搜索路径。

下面示例定义了一系列Java类,并使用@Component来标注它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.crazyit.app.service.impl;

import org.springframework.stereotype.*;
import org.crazyit.app.service.*;

@Component
public class Chinese implements Person
{
private Axe axe;
// axe的setter方法
public void setAxe(Axe axe)
{
this.axe = axe;
}
// 实现Person接口的useAxe()方法
public void useAxe()
{
System.out.println(axe.chop());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.crazyit.app.service.impl;

import org.springframework.stereotype.*;
import org.crazyit.app.service.*;

@Component
public class SteelAxe implements Axe
{
public String chop()
{
return "钢斧砍柴真快";
}
}

这些Java类与前面介绍的Bean类没有太大区别,只是每个Java类都使用了@Component标注,这表明这些Java类都将作为SpringBean类。
接下来需要在Spring配置文件中指定搜索路径, Spring将会自动搜索该路径下的所有Java类,并根据这些Java类来创建Bean实例。本应用的配置文件如下。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="GBK"?>
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 自动扫描指定包及其子包下的所有Bean类 -->
<context:component-scan
base-package="org.crazyit.app.service" />
</beans>

上面的配置文件中最后一行代码指定Spring将会把org.crazyit.app.service包及其子包下的所有Java类都当成Spring Bean来处理,并为每个Java类创建对应的Bean实例。经过上面的步骤, Spring容器中自动就会增加三个Bean实例(前面定义的三个类都是位于org.crazyit.app.service.impl包下的)。
主程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package lee;

import org.springframework.context.*;
import org.springframework.context.support.*;

public class BeanTest
{
public static void main(String[] args)
{
// 创建Spring容器
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"beans.xml");
// 获取Spring容器中的所有Bean实例的名
String[] BeanDefinitionNames = ctx.getBeanDefinitionNames();
for (int i = 0; i < BeanDefinitionNames.length; i++)
{
System.out.println(BeanDefinitionNames[i]);
}
}
}

上面程序中输出了Spring容器中所有Bean实例的名称,运行上面的程序,将看到如下输出结果:

1
2
3
4
5
6
7
8
chinese
steelAxe
stoneAxe
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

使用注解方式时将类名首字母变小写得到Bean的名称

从上面的运行结果可以看出, Spring容器中三个Bean实例的名称分别为chinesesteelAxestoneAxe,那么这些名称是从哪里来的呢?

  • 在基于XML配置方式下,每个Bean实例的名称都由其id属性指定的;
  • 在基于注解的方式下, Spring通过将该Bean类的类名的首字母变成小写,其他字母不变的规则得到Bean的名称。

使用注解设置Bean的名称

当然Spring也允许在使用@Component标注时指定Bean实例的名称,例如如下代码片段:

1
2
3
4
5
6
7
8
@Component("axe")
public class SteelAxe implements Axe
{
public String chop()
{
return "钢斧砍柴真快";
}
}

上面程序中的粗体字代码指定该Bean实例的名称为axe.

在默认情况下, Spring会自动搜索所有以@Component@Controller@Service@Repository标注的Java类,并将它们当成Spring Bean来处理。

@Lookup注解执行方法注入

Spring还提供了@Lookup注解来执行lookup方法注入,其实 @Lookup注解的作用完全等同于<lookup-method>元素。

lookup-method元素复习

使用<lookup-method>元素。需要指定如下两个属性

lookup-method元素属性 描述
name 指定要执行lookup方法注入的方法名。
bean 指定lookup方法要注入的Beanid

@Lookup注解通过value属性指定要注入的Bean的id

@Lookup注解则直接修饰需要执行lookup方法注入的方法,因此不需要指定name属性,该@Lookup注解要指定一个value属性, value属性就等同于<lookup-method>元素的bean属性,也就是用来指定lookup方法要注入Beanid

扫描bean的时候对bean进行过滤

除此之外,还可通过为<component-scan>元素添加<include-filter><exclude-filter>子元素来指定Spring Bean类,其中:

  • <include-filter>用于强制Spring处理某些Bean类,即使这些类没有使用Spring注解修饰;
  • <exclude-filter>则用于强制将某些Spring注解修饰的类排除在外。

include-filte元素

<include-filter>元素用于指定满足该规则的Java类会被当成Bean类处理,<exclude-filter>指定满足该规则的Java类不会被当成Bean类处理。使用这两个元素时都要求指定如下两个属性:

属性 描述
type 指定过滤器类型。
expression 指定过滤器所需要的表达式。

Spring内建支持如下4种过滤器。

过滤器 描述
annotation 注解过滤器,该过滤器需要指定一个注解名,如lee.AnnotationTest
assignable 类名过滤器,该过滤器直接指定一个Java类。
regex 正则表达式过滤器,该过滤器指定一个正则表达式,匹配该正则表达式的Java类将满足该过滤规则,如org\.example\.Default.*
aspectj Aspectj过滤器,如org.example..* Service+

程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\FilterScan
└─src\
├─beans.xml
├─lee\
│ └─BeanTest.java
└─org\
└─crazyit\
└─app\
└─service\
├─Axe.java
├─impl\
│ ├─Chinese.java
│ ├─SteelAxe.java
│ └─StoneAxe.java
└─Person.java

例如,下面配置文件指定所有以Chinese结尾的类、以Axe结尾的类都将被当成Spring Bean处理。

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="GBK"?>
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 自动扫描指定包及其子包下的所有Bean类 -->
<context:component-scan
base-package="org.crazyit.app.service">
<!-- 只将以Chinese、Axe结尾的类当成Spring容器中的Bean -->
<context:include-filter type="regex"
expression=".*Chinese" />
<context:include-filter type="regex"
expression=".*Axe" />
</context:component-scan>
</beans>

8.1.5 重写占位符配置器

PropertyOverrideConfigurerSpring提供的另一个容器后处理器,这个后处理器的作用比上面那个容器后处理器的功能更加强大。
PropertyOverrideConfigurer的属性文件指定的信息可以直接覆盖Spring配置文件中的元数据。
如果PropertyOverrideConfigurer的属性文件指定了一些配置的元数据,则这些配置的元数据将会覆盖原配置文件里相应的数据。在这种情况下,可以认为Spring配置信息是XML配置文件和属性文件的总和,当XML配置文件和属性文件指定的元数据不一致时,以属性文件的信息为准。

使用PropertyOverrideConfigurer的属性文件,每条属性应保持如下的格式:
beanId.property=value
beanId是属性占位符试图覆盖的Beanid, property是试图覆盖的属性名(对应于调用setter方法)。

程序示例

1
2
3
4
5
6
7
E:\workspace_QingLiangJiJavaEEQiYeYingYongShiZhang5\PropertyOverrideConfigurer
├─data.sql
└─src\
├─beans.xml
├─dbconn.properties
└─lee\
└─BeanTest.java

beans.xml

看如下配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="GBK"?>
<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.xsd">
<!-- PropertyOverrideConfigurer是一个容器后处理器,它会读取
属性文件信息,并用这些信息设置覆盖Spring配置文件的数据 -->
<bean class=
"org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 如果有多个属性文件,依次在下面列出来 -->
</list>
</property>
</bean>
<!-- 定义数据源Bean,使用C3P0数据源实现,
配置该Bean时没有指定任何信息,但Properties文件里的
信息将会直接覆盖该Bean的属性值 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"/>
</beans>

上面的配置文件中配置数据源Bean时,没有指定任何属性值,很明显配置数据源Bean时不指定有效信息是无法连接到数据库服务的。
但因为Spring容器中部署了一个PropertyOverrideConfigurer容器后处理器,而且Spring容器使用ApplicationContext作为容器,它会自动检测容器中的容器后处理器,并使用该容器后处理器来处理Spring容器。
PropertyOverride Configurer后处理器读取dbconn.properties文件中的属性,用于覆盖目标Bean的属性。因此,如果属性文件中有dataSourceBean属性的设置,则可在配置文件中为该Bean指定属性值,这些属性值将会覆盖dataSourceBean的各属性值。
dbconn.properties属性文件如下:

1
2
3
4
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/spring?useSSL=true
dataSource.user=root
dataSource.password=root

属性文件里每条属性的格式必须是:
beanId.property=value
也就是说, dataSource必须是容器中真实存在的Beanid,否则程序将出错。
程序无法知道BeanFactory定义是否被覆盖。仅仅通过查看XML配置文件,无法知道配置文件的配置信息是否被覆盖。如有多个PorpertyOverrideConfigurer对同一Bean属性进行了覆盖,最后一次覆盖将会获胜。
对于采用基于XML Schema的配置文件而言,如果导入了contextSchema,则可采用如下方式来配置这种重写占位符。

使用context:property-override元素简化配置

1
<context: property-override location=classpath:db.properties"/>

也就是说,<context:property-override>元素是PorpertyOverrideConfigurer的简化配置。