乐趣区

Spring详解3.Bean的装配

点击进入我的博客
1 Spring 容器与 Bean 配置信息
Bean 配置信息
Bean 配置信息是 Bean 的元数据信息,它由一下 4 个方面组成:

Bean 的实现类
Bean 的属性信息,如数据库的连接数、用户名、密码。
Bean 的依赖关系,Spring 根据依赖关系配置完成 Bean 之间的装配。
Bean 的行为信息,如生命周期范围及生命周期各过程的回调函数。

Bean 元数据信息
Bean 元数据信息在 Spring 容器中的内部对应物是一个个 BeanDefinition 形成的 Bean 注册表,Spring 实现了 Bean 元数据信息内部表示和外部定义之间的解耦。
Spring 支持的配置方式
Spring1.0 仅支持基于 XML 的配置,Spring2.0 新增基于注解配置的支持,Spring3.0 新增基于 Java 类配置的支持,Spring4.0 则新增给予 Groovy 动态语言配置的支持。
2 基于 XML 的配置
2.1 理解 XML 与 Schema
<?xml version=”1.0″ encoding=”utf-8″ ?>
<beans (1)xmlns=”http://www.springframework.org/schema/beans”
(2)xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:(3)context=(4)”http://www.springframework.org/schema/context”
xsi:(5)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”>
</beans>

(1) 处是默认命名空间,无命名空间前缀的元素属于默认命名空间。
(2)xsi 标准命名空间,用于指定自定义命名空间的 Schema 文件
(3) 自定义命名空间的简称,可以任意命名
(4) 自定义命名空间的全称,必须在 xsi 命名空间为其指定空间对应的 Schema 文件,可以任意命名,习惯上用文档发布机构的相关网站目录。
(5) 为每个命名空间指定 Schema 文件位置,

详解 xmlns

定义:xml namespace 的缩写,可译为“XML 命名空间”。
作用:防止 XML 文档含有相同的元素命名冲突,如 <table> 既可以表示表格,又可以表示桌子。如果增加了命名空间如 <table> 和 <t:table> 就可以使两者区分开来。
使用:xmlns:namespace-prefix=”namespaceURI”,其中 namespace-prefix 为自定义前缀,只要在这个 XML 文档中保证前缀不重复即可;namespaceURI 是这个前缀对应的 XML Namespace 的定义。

理解 xsi:schemaLocation
xsi:schemaLocation 定义了 XML Namespace 和对应的 XSD(Xml Schema Definition)文档的位置的关系。它的值由一个或多个 URI 引用对组成,两个 URI 之间以空白符分隔(空格和换行均可)。第一个 URI 是定义的 XML Namespace 的值,第二个 URI 给出 Schema 文档的位置,Schema 处理器将从这个位置读取 Schema 文档,该文档的 targetNamespace 必须与第一个 URI 相匹配。例如:
<beans
xsi:schemaLocation=”http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd”>
</beans>
这里表示 Namespace 为 http://www.springframework.or…://www.springframework.org/schema/context/spring-context.xsd。
2.2 使用 XML 装配 Bean
直接装配 Bean
<!–id 不可以配置多个: context.getBean(“myBean1, myBean2″)–>
<bean class=”com.ankeetc.spring.MyBean” id=”myBean1, myBean2″/>

<!–context.getBean(“myBean1”) == context.getBean(“myBean2″)–>
<bean class=”com.ankeetc.spring.MyBean” name=”myBean1, myBean2″/>

id:用于表示一个 Bean 的名称,在容器内不能重复;不可以配置多个 id。
name:用于表示一个 Bean 的名称,在容器内不能重复;可以配置多个名称,用, 分割;id 和 name 可以都为空,此时则可以通过获取全限定类名来获取 Bean。
class:全限定类名

静态工厂方法装配

静态工厂无需创建工厂类示例就可以调用工厂类方法。
factory-method:工厂方法

public class MyBeanFactory {
public static MyBean createMyBean() {
return new MyBean();
}
}
<bean id=”myBean” class=”com.ankeetc.spring.MyBeanFactory” factory-method=”createMyBean”/>
非静态工厂方法装配

非静态工厂方法必须首先定义一个工厂类的 Bean,然后通过 factory-bean 引用工厂类实例。
factory-bean:指向定义好的工厂 Bean

public class MyBeanFactory {
public MyBean createMyBean() {
return new MyBean();
}
}
<bean id=”myBeanFactory” class=”com.ankeetc.spring.MyBeanFactory”/>
<bean id=”myBean” factory-bean=”myBeanFactory” factory-method=”createMyBean”/>
Bean 的继承和依赖

parent:通过设置 <bean> 标签的 parent 属性,子 <bean> 将自动继承父 <bean> 的配置信息。
depends-on:通过设置 <bean> 标签的 depends-on 属性,Spring 允许显示的设置当前 Bean 前置依赖的 Bean,确保前置依赖的 Bean 在当前 Bean 实例化之前已经创建好。

自动装配 autowire
<beans> 元素提供了一个 default-autowire 属性可以全局自动匹配,默认为 no。<bean> 元素提供了一个指定自动装配类型的 autowire 属性,可以覆盖 <beans> 元素的 default-autowire 属性,该属性有如下选项:

自动装配类型
说明

no
显式指定不使用自动装配。

byName
如果存在一个和当前属性名字一致的 Bean,则使用该 Bean 进行注入。如果名称匹配但是类型不匹配,则抛出异常。如果没有匹配的类型,则什么也不做。

byType
如果存在一个和当前属性类型一致的 Bean (相同类型或者子类型),则使用该 Bean 进行注入。byType 能够识别工厂方法,即能够识别 factory-method 的返回类型。如果存在多个类型一致的 Bean,则抛出异常。如果没有匹配的类型,则什么也不做。

constructor
与 byType 类似,只不过它是针对构造函数注入而言的。如果当前没有与构造函数的参数类型匹配的 Bean,则抛出异常。使用该种装配模式时,优先匹配参数最多的构造函数。

default
根据 Bean 的自省机制决定采用 byType 还是 constructor 进行自动装配。如果 Bean 提供了默认的构造函数,则采用 byType;否则采用 constructor 进行自动装配。

通过 util 命名空间配置集合类型的 Bean
<util:list></util:list>
<util:set></util:set>
<util:map></util:map>
2.3 使用 XML 依赖注入
属性配置

Bean 有一个无参数的构造器
属性有对应的 Setter 函数
属性命名满足 JavaBean 的属性命名规范

<bean class=”com.ankeetc.spring.MyBean” id=”myBean”>
<property name=”prop” value=”prop”/>
</bean>
构造方法

constructor-arg 中的 type 和 index 可以没有,只要能保证可以唯一的确定对应的构造方法即可
type 中基本数据类型和对应的包装类不能通用

循环依赖:如果两个 Bean 都采用构造方法注入,而且都通过构造方法入参引用对方,就会造成循环依赖导致死锁。

<bean class=”com.ankeetc.spring.MyBean” id=”myBean”>
<constructor-arg type=”java.lang.String” index=”0″ value=”abc”/>
<constructor-arg type=”int” index=”1″ value=”10″/>
</bean>
2.4 注入参数
字面值

基本数据类型及其封装类、String 都可以采取字面值注入。
特殊字符可以使用 <![CDATA[]]> 节或者转义序列

引用其他 Bean
<ref> 元素可以通过以下三个属性引用容器中的其他 Bean:

bean:通过该属性可以引用同一容器或父容器的 Bean,这是最常见的形式。
local:通过该属性只能引用同一配置文件中定义的 Bean,它可以利用 XML 解析器自动检验引用的合法性,以便在开发编写配置时能够及时发现并纠正配置的错误。
parent:引用父容器中的 Bean,如 <ref parent=”car”> 的配置说明 car 的 Bean 是父容器中的 Bean。

内部 Bean

内部 Bean 只会被当前 Bean 引用,不会被容器中其他的 Bean 引用
内部 Bean 即使提供了 id、name、scope 也会被忽略,Scope 默认为 prototype 类型。

<bean id=”prop” class=”com.ankeetc.spring.Prop”>
<property name=”value” value=”1314″/>
</bean>

<bean id=”myBean” class=”com.ankeetc.spring.MyBean”>
<property name=”prop”>
<!– 内部 Bean 即使提供了 id、name、scope 也会被忽略 –>
<bean id=”prop” class=”com.ankeetc.spring.Prop”>
<property name=”value” value=”520″/>
</bean>
</property>
</bean>
null 值
使用 <null/> 代表 null 值
级联属性

Spring 支持级联属性如 prop.value,而且支持多层级联属性
级联属性必须有初始值,否则会抛出 NullValueInNestedPathException

public class MyBean {
// 必须初始化
private Prop prop = new Prop();

public Prop getProp() {
return prop;
}

public void setProp(Prop prop) {
this.prop = prop;
}
}
<bean id=”myBean” class=”com.ankeetc.spring.MyBean”>
<property name=”prop.value” value=”1314″/>
</bean>
集合类型属性

List、Set、Map:通过 <list><set><map><entry> 等标签可以设置 List、Set、Map 的属性

Properties:可以通过 <props><prop> 等标签设置 Properties 的属性,Properties 属性的键值都只能是字符串。

集合合并:子 Bean 可以继承父 Bean 的同名属性集合元素,并且使用 merge 属性选择是否合并,默认不合并。

<bean id=”parentBean” class=”com.ankeetc.spring.MyBean”>
<property name=”list”>
<list>
<value>1314</value>
</list>
</property>
</bean>

<bean id=”myBean” class=”com.ankeetc.spring.MyBean” parent=”parentBean”>
<property name=”list”>
<list merge=”true”>
<value>520</value>
</list>
</property>
</bean>
2.5 多配置文件整合

可以通过 ApplicationContext 加载多个配置文件,此时多个配置文件中的 <bean> 是可以互相访问的。
可以通过 XML 中的 <import> 将多个配置文件引入到一个文件中,这样只需要加载一个配置文件即可。

2.6 Bean 的作用域

类型
说明

singleton
在 Spring IoC 容器中仅存在一个 Bean 实例,Bean 以单实例的方式存在

prototype
每次从容器中调用 Bean 时,都返回一个新的实例

request
每次 HTTP 请求都会创建一个新的 Bean,该作用域仅适用于 WebApplicationContext 环境

session
同一个 HTTP session 共享一个 Bean,不同的 HTTP session 使用不同的 Bean,该作用域仅适用于 WebApplicationContext 环境

globalSession
同一个全局 Session 共享一个 Bean,一般用于 Portlet 环境,该作用域仅适用于 WebApplicationContext 环境

singleton 作用域

无状态或者状态不可变的类适合使用单例模式
如果用户不希望在容器启动时提前实例化 singleton 的 Bean,可以使用 lazy-init 属性进行控制
如果该 Bean 被其他需要提前实例化的 Bean 所引用,那么 Spring 将会忽略 lazy-init 的设置

prototype 作用域

设置为 scope=”prototype” 之后,每次调用 getBean() 都会返回一个新的实例
默认情况下,容器在启动时不会实例化 prototype 的 Bean
Spring 容器将 prototype 的 Bean 交给调用者后就不再管理它的生命周期

Web 应用环境相关的 Bean 作用域
见后续章节
作用域依赖的问题
见后续章节
3 FactoryBean
由于实例化 Bean 的过程比较负责,可能需要大量的配置,这是采用编码的方式可能是更好的选择。Spring 提供了 FactoryBean 工厂类接口,用户可以实现该接口定制实例化 Bean 的逻辑。当配置文件中 <bean> 的 class 属性配置的是 FactoryBean 的子类时,通过 getBean() 返回的不是 FactoryBean 本身,而是 getObject() 方法所返回的对象,相当于是 FactoryBean#getObject() 代理了 getBean() 方法。

T getObject() throws Exception;:返回由 FactoryBean 创建的 Bean 实例,如果 isSingleton() 返回的是 true,该实例会放到 Spring 容器的实例缓存池中。

Class<?> getObjectType();:返回该 FactoryBean 创建的 Bean 的类型

boolean isSingleton();:创建的 Bean 是 singleton 的还是 prototype

/**
* 实现, 分割的方式配置 KFCCombo 属性
*/
public class KFCFactoryBean implements FactoryBean<KFCCombo> {
private String prop;

public String getProp() {
return prop;
}

// 接受, 分割的属性设置信息
public void setProp(String prop) {
this.prop = prop;
}

// 实例化 KFCCombo
public KFCCombo getObject() throws Exception {
KFCCombo combo = new KFCCombo();
String[] props = prop.split(“,”);
combo.setBurger(props[0]);
combo.setDrink(props[1]);
return combo;
}

public Class<?> getObjectType() {
return KFCCombo.class;
}

// true 则放进容器缓存池,false 则每次都调用 getObject() 方法返回新的对象
public boolean isSingleton() {
return false;
}
}
<bean id=”combo” class=”com.ankeetc.spring.KFCFactoryBean”>
<property name=”prop” value=”ZingerBurger, PepsiCola”/>
</bean>
4 基于注解的配置
4.1 支持的注解
@Component:在 Bean 的实现类上直接标注,可以被 Spring 容器识别 @Repository:用于对 DAO 实现类进行标柱 @Service:用于对 Service 实现类进行标注 @Controller:用于对 Controller 实现类进行标注
4.2 扫描注解定义对 Bean
Spring 提供了一个 context 命名空间,用于扫描以注解定义 Bean 的类。
<!– 生命 context 命名空间 –>
<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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd”>

<context:component-scan base-package=”com.ankeetc.spring”/>
</beans>
base-package 属性
指定一个需要扫描的基类包,Spring 容器会扫描这个包下的所有类,并提取标注了相关注解的 Bean。
resource-pattern 属性

如果不希望扫描 base-package 下的所有类,可以使用该属性提供过滤
该属性默认是 **/*.class,即基包下的所有类

<context:exclude-filter> 与 <context:include-filter>

<context:exclude-filter>:表示要排除的目标类
<context:include-filter>:表示要包含的目标类
<context:component-scan> 可以有多个上述两个子元素;首先根据 exclude-filter 列出需要排除的黑名单,然后再根据 include-filter 流出需要包含的白名单。

类别
示例
说明

annotation
com.ankeetc.XxxAnnotation
所有标注了 XxxAnnotation 的类。该类型采用目标类是否标志了某个注解进行过滤。

assignable
com.ankeetc.XxService
所有继承或扩展 XXXService 的类。该类型采用目标类是否继承或者扩展了某个特定类进行过滤

aspectj
com.ankeetc..*Service+
所有类名以 Service 结束的类及继承或者扩展他们的类。该类型采用 AspectJ 表达式进行过滤

regex
com.ankeetc.auto..*
所有 com.ankeetc.auto 类包下的类。该类型采用正则表达式根据目标类的类名进行过滤

custom
com.ankeetc.XxxTypeFilter
采用 XxxTypeFilter 代码方式实现过滤规则,该类必须实现 org.springframework.core.type.TypeFilter 接口

use-default-filters 属性

use-default-filters 属性默认值为 true,表示会对标注 @Component、@Controller、@Service、@Reposity 的 Bean 进行扫描。
如果想仅扫描一部分的注解,需要将该属性设置为 false。

<!– 仅扫描标注了 @Controller 注解的类 –>
<context:component-scan base-package=”com.ankeetc.spring” use-default-filters=”false”>
<context:exclude-filter type=”annotation” expression=”org.springframework.stereotype.Controller”/>
</context:component-scan>
4.3 自动装配
@Component
public class KFCCombo {
@Autowired
private PepsiCola cola;

@Autowired
private Map<String, Cola> colaMap;

@Autowired
private List<ZingerBurger> burgerList;

private ZingerBurger burger;
@Autowired
public void setBurger(@Qualifier(value = “zingerBurger”) ZingerBurger burger) {
this.burger = burger;
}

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{“classpath:/beans.xml”});

KFCCombo combo = context.getBean(KFCCombo.class);
}
}

interface Cola {}

@Order(value = 1)
@Component
class CocaCola implements Cola {}

@Order(value = 2)
@Component
class PepsiCola implements Cola {}

@Component(value = “zingerBurger”)
class ZingerBurger {
}
@Autowired 注解

使用该注解可以按类型自动装配对应的 Bean
没有找到对应的 Bean 则会抛出 NoSuchBeanDefinitionException 异常
使用 required=false 属性可以设置即使找不到对应的 Bean(即为 null)也不会抛出异常
@Autowired 可以对类成员变量及方法入参进行标注

@Quaifiler

如果容器中有一个以上匹配的 Bean 时,可以按照 Bean 名字查找对应的 Bean
@Quaifiler 需要与 @Autowired 配合使用

对集合类进行标注

可以使用 @Autowired 对集合类进行标注,Spring 会讲容器中按类型匹配对所有 Bean 注入进来
可以使用 @Order 指定加载顺序,值越小的越先加载

@Lazy 延迟加载
可以使用 @Lazy 实现延迟加载,不会立即注入属性值,而是延迟到调用此属性对时候才会注入属性值。
@Resource 和 @Inject

Spring 支持 JSR-250 中 @Resource 注解和 JSR-330 的 @Inject 注解
@Resource 采用的是按照名称加载的方式,它要求提供一个 Bean 名称的属性,如果属性为空,则自动采用标注处的变量名或方法名作为 Bean 的名称。
@Inject 是按照类型匹配注入 Bean 的。
由于这两个注解功能没有 @Autowired 功能强大,一般不需要使用。

4.4 Bean 作用范围及生命周期

注解配置的 Bean 默认作用范围为 singleton,可以使用 @Scope 显示指定作用范围
可以使用 @PostConstruct 和 @PreDestroy 注解来达到 init-method 和 destroy-method 属性的功能。
@PostConstruct 和 @PreDestroy 注解可以有多个

5 基于 Java 类的配置
5.1 @Configuration 注解

JavaConfig 是 Spring 的一个子项目,旨在通过 Java 类的方式提供 Bean 的定义信息。
普通的 POJO 标注了 @Configuration 注解,就可以被 Spring 容器提供 Bean 定义信息。
@Configuration 注解本身已经标注了 @Component 注解,所以任何标注了 @Configuration 的类都可以作为普通的 Bean。

5.2 @Bean 注解

@Bean 标注在方法上,用于产生一个 Bean
Bean 的类型由方法的返回值的类型确定,Bean 名称默认与方法名相同,也可以显示指定 Bean 的名称。
可以使用 @Scope 来控制 Bean 的作用范围。

5.3 启动 Spring 容器
通过 @Configuration 类启动 Spring 容器

可以直接设置容器启动要注册的类
可以向容器中注册新的类,注册了新的类要记得 refresh
可以通过 @Import 将多个配置类组装称一个配置类

public class Main {
public static void main(String[] args) {
// (1) 可以直接设置容器启动要加载的类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(DaoConfig.class);
// (2) 可以向容器中注册新的类
((AnnotationConfigApplicationContext) applicationContext).register(ServiceConfig.class);
// (3) 注册了新的类要记得 refresh
((AnnotationConfigApplicationContext) applicationContext).refresh();
}
}

@Configuration
class DaoConfig {
@Bean
public String getStr() {
return “1314”;
}
}

@Configuration
@Import(DaoConfig.class)
// (4) 可以通过 @Import 将多个配置类组装称一个配置类
class ServiceConfig {
}
通过 XML 配置文件引用 @Configuration 的配置
标注了 @Configureation 的配置类本身也是一个 bean,它可以被 Spring 的 <context:component-scan> 扫描到。如果希望将此配置类组装到 XML 配置文件中,通过 XML 配置文件启动 Spring 容器,仅在 XML 文件中通过 <context:component-scan> 扫描到相应的配置类即可。
<context:component-scan base-package=”com.ankeetc.spring” resource-pattern=”Config.class”/>
通过 @Configuration 配置类引用 XML 配置信息
在标注了 @Configuration 的配置类中,可以通过 @ImportResource 引入 XML 配置文件。
@Configuration
@ImportResource(“classpath:beans.xml”)
public class Config {
}

退出移动版