8.2 Spring的 零配置 支持 8.2.1 搜索Bean类

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>