Spring framework(4):IoC (2) Bean 装配
Spring 装配 Bean 概述
Spring 容器启动的3个条件:
-
Spring 本身:Spring 框架的类包都已经放置在应用程序的类路径下;
-
Bean 配置信息:应用程序为 Spring 提供了完整的 Bean 配置信息;
-
Bean 实现类:Bean 实现类都已经放到应用程序的类路径下;
Bean 配置信息由以下4部分组成:
-
Bean 的实现类;
-
Bean 的属性信息;
-
Bean 的依赖关系;
- Bean 的行为配置,如生命周期范围及生命周期各过程的回调函数等;
Spring 容器,Bean配置信息,Bean实现类,应用程序4部分的关系:
Spring 中对 Bean 进行装配目前有以下4种方式:
-
通过 XML 配置文件装配(Spring 1.0 +)
-
通过注解装配(Spring 2.0+)
-
通过 JavaConfig 装配(Spring 3.0+)
-
通过 Groovy DSL 装配(Spring 4.0+)
- 通过 Kotlin DSL 装配(Spring 5.0+)
以下介绍前4种装配方式,对于大部分通用概念,以 xml 方式示例,以下的完整示例代码参见:
通过 XML 装配
依赖注入的基本配置
Spring 支持3种依赖注入方式,分别是属性注入,构造函数注入,工厂方法注入;
在实际使用中,一般使用属性注入方式会比较多,因为在配置上比较灵活,代码冗余度比较低;
1.属性注入
相关示例代码模块:site.assad.ditype/User ; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
(分别指main/java, resources, test/java下的相关模块,下不赘述)
示例中创建一个默认的Bean为User:
package site.assad.ditype;
public class User {
private String name;
private int age;
private String city;
public User() {
}
//省略 getter,setter
}
在beans.xml中对其进行相关配置:
<bean id="user1" class="site.assad.ditype.User"
p:name="Al-assad"
p:age="20"
p:city="Guangzhou" />
<!-- 或者 -->
<bean id="user1" class="site.assad.ditype.User">
<property name="name" value="Al-assad" />
<property name="age" value="20" />
<property name="city" value="Al-Guangzhou" />
</bean>
可以使用以上2种方式进行bean的属性注入,第一种是简便模式,第二种为完整模式;
对其该注入的bean的调用代码如下:
ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/ditype/beans.xml");
User user = factory.getBean("user1",User.class);
2.构造函数注入
相关示例代码模块:site.assad.ditype/User, site.assad.ditype/beans.xml,
site.assad.ditype/ DiTypeTest
构造函数注入一般用于保证一些必要的属性在Bean实例化是就得到设置,确保Bean在实例化就可以使用;
beans.xml中的相关设置如下:
<bean id="user3" class="site.assad.ditype.User">
<constructor-arg type="java.lang.String" value="Jhon" /> <!--设置构造函数参数-->
<constructor-arg type="int" value="23" />
<constructor-arg type="java.lang.String" value="BeiJing" />
</bean>
3.工厂方法注入
相关示例代码模块:site.assad.ditype/User,UserFactory
; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
Spring 除了支持DI概念中的属性注入,构造函数注入之外,还支持工厂方法注入;
示例中构建了一个 User 的工厂类 UserFactory,beans.xml 中的相关配置如下:
<!--配置工厂类bean-->
<bean id="userFactory" class="site.assad.ditype.UserFactory" />
<!--通过工厂类bean,调用不同的工厂方法,注入2个不同的 User bean-->
<bean id="user4" factory-bean="userFactory" factory-method="createUser" />
<bean id="user5" factory-bean="userFactory" factory-method="createGuangzhouUser" p:name="Jucka" p:age="34"/>
Spring 中还提供了一个 FactoryBean
工厂类接口(org.springframework.beans.factory.FactoryBean),用户可以通过继承该接口实现工厂类,以定制实例化Bean的逻辑,;
FactoryBean 工厂类定义了3个接口方法:
T getObject() | 返回 Factory 创建的 Bean 实例,如果 isSingleton() 返回true,该示例会放置到 Spring 容器的单实例缓存池中 |
boolean isSingleton() | 确定创建的 Bean 实例作用域为 singleton 或 protetype |
Class<?> getObjectType() | 返回创建的 Bean 实例类型 |
示例中 site.assad.ditype/UserFactoryBean
继承了 FactoryBean 接口实现了一个User的工厂类,如下:
public class UserFactoryBean implements FactoryBean{
//实现通过逗号分隔的形式,一次性配置所有User的properties,简化配置;
private String userInfo;
public void setUserInfo(String userInfo) { this.userInfo = userInfo; }
public Object getObject() throws Exception {
User user = new User();
String[] infos = userInfo.split(",");
user.setName(infos[0]);
user.setAge(Integer.parseInt(infos[1]));
user.setCity(infos[2]);
return user;
}
public Class<?> getObjectType() { return User.class; }
public boolean isSingleton() { return true; }
}
beans.xml
中相应的配置如***入的类为UserFactoryBean工厂类,调用时返回User类:
<bean id="user6" class="site.assad.ditype.UserFactoryBean" p:userInfo="Alex,30,San Francisco" />
调用该bean如下:
ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/ditype/beans.xml");
User user = factory.getBean("user6",User.class);
注入参数详解
1. 特殊字面量 和 null值
特殊字面量
xml文件中有5个特殊字符:&、<、>、"、‘ ,当配置文件中注入的字面量包含这些特殊字符时,处理的方式有2中:
-
使用 <![CDATA[ ]]>
包含该字面量,如字面量 Al-assad & Vancy 替换为
<![CDATA[ Al-assad & Vancy ]]>
-
使用转义序列代替特殊符号
< → < | > → > | & → & | " → " | ' → ' |
null 值
如果某个属性插入值为 null 值,应该使用<null />标签放置在该属性节点下,如下:
<property name="city"><null /></property>
2. <bean>之间的关系
引用
如果一个bean引用另一个bean,可以使用 <ref bean=“beanId”> 配置该引用bean,如下:
示例代码模块:site.assad.ditype/User,Article
; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
<!--Article 中一个属性引用一个注入的 User-->
<bean id="user1" class="site.assad.ditype.User" p:name="Al-assad" p:age="20" p:city="Guangzhou" />
<!--简略模式,配置相关属性-ref-->
<bean id="article1" class="site.assad.ditype.Article" p:title="How to Communicate with Cat"
p:user-ref="user1"/>
<!--或者-->
<bean id="article1" class="site.assad.ditype.Article">
<property name="title" value="How to Communicate with Cat" />
<property name="user">
<ref bean="user1" />
</property>
</bean>
继承
bean配置支持继承关系,利用继承关系,可以节约同类型bean的重复注入配置,如要注入多个同类型bean中含有部分相同值的属性,可以将这些相同值的属性抽象实现一个抽象bean,再继承这个抽象bean实现多个同类型bean;
<!--定义抽象Bean-->
<bean id="abstructUser" class="site.assad.ditype.User" abstruct="true"
p:age="18" p:city="Guangzhou" />
<!--继承抽象Bean-->
<bean id="user8" parent="abstructBean" p:name="Alex" />
<bean id="user9" parent="abstructBean" p:name="Tim" />
依赖
如果一个bean的实例化依赖于另一个bean的实例化,可以通过设置该bean的<depends-on>属性来实现bean之间的依赖关系;
<!--在manager实例化前,会先实例化sysInt-->
<bean id="sysInt" class="..." />
<bean id="manager" class="..." depend-on="sysInt" />
3. 内部Bean
如果bean1中引用bean2时,bean2不需要被容器中的其他bean引用,可以将bean2以内部Bean的方式注入bean1中,类似匿名类的概念;
示例代码模块:site.assad.ditype/User,Article
; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
<!--article3以内部Bean的方式引用 User-->
<bean id="article3" class="site.assad.ditype.Article" p:title="Emmmmm....">
<property name="user">
<bean class="site.assad.ditype.User"
p:name="MoFila"
p:age="12"
p:city="ChongQing"/>
</property>
</bean>
4.级联属性
Spring 支持级联属性的配置,对于上面的内部Bean引用的代码,可以使用以下级联属性的方式配置:
示例代码模块:site.assad.ditype/User,Article2
; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
<bean id="article4" class="site.assad.ditype.Article2" p:title="Are you OK?">
<property name="user.name" value="LeiBuShi"/>
<property name="user.age" value="999"/>
<property name="user.city" value="BeiJing"/>
</bean>
在使用某个bean的级联属性时,要为该级联属性申明一个初始化对象,如下:
public class Article2 {
private String title;
private User user = new User(); //在 Article2 使用 User 作为级联对象时,必须将user声明为一个初始化对象;
.....
}
5.集合类型属性
Spring 为 java.util 中的集合类 List,Set,Map,Properties 提供了相应的标签支持;
示例代码模块:site.assad.ditype/User,Article,TaskList
; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
List
<bean id="taskList1" class="site.assad.ditype.TaskList">
<property name="belongs"> <!--TaskList的belongs属性为List类型-->
<list>
<value>Al-assad</value>
<value>Vancy</value>
<value>Jone</value>
</list>
</property>
</bean>
set使用<set>标签,配置方法类似List;
Map
<bean id="taskList2" class="site.assad.ditype.TaskList">
<property name="items"> <!--TaskList的items属性为List类型-->
<map>
<entry key="9:00" value="go to work" />
<entry key="21:00" value="go back home" />
</map>
</property>
</bean>
Properties
Properties 本质上是 Map,不过他的key、value都只能是String类型
<bean id="taskList3" class="site.assad.ditype.TaskList">
<property name="places"> <!--TaskList的places属性为List类型-->
<props>
<prop key="9:00">Company</prop>
<prop key="21:00">Home maybe</prop>
</props>
</property>
</bean>
6.集合合并
Spring 支持集合合并功能,允许子<bean>继承父<bean>的同名集合元素,并将子<bean>中的集合属性值合和父<bean>中的同名属性值合并起来作为最终 Bean 的属性值;
示例代码模块:site.assad.ditype/User,Article,TaskList
; site.assad.ditype/beans.xml ; site.assad.ditype/ DiTypeTest
<bean id="taskListParent" abstract="true" class="site.assad.ditype.TaskList" > <!--被合并的父 Bean-->
<property name="belongs">
<list>
<value>Al-assad</value>
<value>Vancy</value>
</list>
</property>
</bean>
<bean id="taskListChild" parent="taskListParent" > <!--被合并的子Bean,指定父类Bean-->
<property name="belongs">
<list merge="true"> <!--和父类中的同名集合属性合并,taskListChild belongs属性中拥有 taskListParent belongs属性的所有元素值-->
<value>Tom</value>
<value>Jceke</value>
</list>
</property>
</bean>
bean的作用域
bean 含有以下的作用域,作用域会对 Bean 的生命周期和创建方式产生影响;
singleton | 默认作用域,Bean 以单例的方式存在,Spring IoC 容器中只存在一个该 Bean 的实例; |
prototype | 每次从容器中调用 Bean 时,都会返回一个新的实例,即每次都会执行与一次 new XxxBean(); |
request | 仅存在于 WebApplicationContext,每次 HTTP 请求都会创建一个新的 Bean; |
session | 仅存在于 WebApplicationContext,同一个 HTTP Session 共享一个Bean,不同 HTTP Session 使用不用的 Bean; |
globalSession | 仅存在于 WebApplicationContext,同一个全局 Session 共享一个Bean,一般适用于 Portlet 应用环境; |
可以用过bean的<scope>属性对一个bean的作用于进行配置,如下:
<bean id="user1" class="site.assad.ditype.User" p:name="Al-assad" p:age="20" p:city="Guangzhou"
scope="prototype" />
对于 WebApplicationContext 相关的作用域(reques,session,globalSession),还要对Web容器进行额外的配置,可以利用HTTP请求监听器进行配置,如下:
web.xml
<web-app>
....
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
....
</web-app>
合并多个配置文件
在一个大型的项目,可能会存在多个xml配置文件,在启动Spring容器时,可以通过一个数组来指定这些配置文件,也可以使用<import>标签将多个配置文件引入到一个文件中,在启动Spring容器时仅指定这个合并好的配置文件即可;
如在使用一个 beans.xml 将 site/assad/impt/bean1.xml, site/assad/impt/bean2.xml
这2个配置文件合并在一起,如下:
beans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:site/assad/impt/bean1.xml" />
<import resource="classpath:site/assad/impt/bean2.xml" />
</beans>
完整示例代码模块: site.assad.impt/beans.xml
,bean1.xml,bean2.xml ; site.assad.impt/ imptTest
通过 Annontation 注解装配
从Spring 2.0 开始引入了基于注解的配置方式,并在4.0时得到进一步增强,相比于基于xml配置文件的方式,基于注解的配置方式可以直接在Bean实现类上标注注解,以减少代码冗余;
完整示例代码模块:site.assad.anno/User,Article
; site.assad.anno/beans.xml ; site.assad.anno/ AnnoTest
使用注解定义Bean
@Conpoment 注解用于定义一个Bean,同时Spring还提供同以下等效的注解用于对DAO,Service,Web层的Controller进行注解:
-
@Repository:用于注解DAO层bean;
-
@Service:用于注解Service层bean;
-
@Controller:用于注解Web层的Controller实现类bean;
一般注解的形式如下:
package site.assad.anno;
// 或者使用 @Component("user") 显式定义 beanId
public class User {
.....
}
可以使用@Scope注解设置Bean的作用范围,如下:
"prototype") (
public class User {
.....
}
自动扫描注解定义的Bean
基本配置
在定义完Bean的注解后,需要配置一个xml配置文件用于扫描类包,以应用注解定义的Bean,类似如下:
beans.xml
<beans ...>
<!--扫描注解定义的组件:默认扫描site.assad.anno 包下的所有class-->
<context:component-scan base-package="site.assad.anno" />
</beans>
自定义扫描匹配
可以设置<context:component-scan>的 resource-pattern 属性进行自定义的扫描包匹配,用于匹配某个子包,或某个类型的文件,如下:
<beans ...>
<!--扫描 site.assad 包下anno子包中的所有class -->
<context:component-scan base-package="site.assad" resource-pattern="anno/*.class"/>
</beans>
添加包含或排除规则
可以使用子节点<context:include-filter>和<context:exclude-filter>
进行包含和排除操作,如下:
<beans ...>
<context:component-scan base-package="site.assad" use-deafult-filter="false" >
<context:include-filter type="regex" expression="site\.assad\.anno.*" />
<context:exclude-filter type="aspectj" expression="site.assad..*Controller+*" />
</context:component-scan>
</beans>
use-deafult-filter 属性设置是否开启默认过滤器,在没有设置为false的情况下,会默认扫描@Component、@Repository、@Service、@Controller的Bean,即使他们不在<context:include-filter>匹配的白名单中;
这两个过滤标签支持多种类型的表达式:
type | expression sample | |
annotation | site.assad.XxxAnnotation | 匹配所有标注了XxxAnnotation注解的类,如 site.assad.Repository 匹配 site.assad 包下所有标注了 @Repository 的类; |
regex | site\.assad\.anno..* | 使用正则表达时进行匹配,示例中匹配 site.assad.anno 包下的所有文件 |
assignable | site.assad.XxxService | 匹配所有继承或拓展 XxxService 的类; |
aspectj | site.assad..*Service+ | 采用AspectJ表达式进行匹配,匹配所有类名以Service结尾的类,及继承和拓展他们的类; |
custom | site.assad.XxxTypeFilter | 采用 XxxTypeFiler 代码方式实现过滤,该类必须实现 org.springframework.core.type.TypeFilter 接口 |
自动装配Bean
在代码中使用Bean时,可以通过@Autowired注解自动装配Bean,如下:
public class Article {
//自动注入User
private User user;
.....
}
也可以在方法上进行注解
public class Article {
private User user;
public Article() {
}
//在类方法上进行注解
public void setUser(User user) {
this.user = user;
}
}
使用@Qualifier注解可以指定注入Bean的名称(在存在多个同类型的Bean的情况下):
public class Article {
"user") (
private User user;
.....
}
使用@Lazy注解可以实现延迟注入,如下:
public class logDao(){
}
public class LogonService implements BeanNameAware{
public void setLogDao(LogDao logDao){ .....}
}
启动容器
启动容器部分的代码如下:
ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/anno/beans.xml");
//“site/assad/anno/beans.xml” 为自动扫描的配置文件
通过 JavaConfig 类装配
JavaConfig 是Spring 的子项目,可以通过 Java 类的方式提供 Bean 的定义信息 ;
示例代码模块:site.assad.conf,site.asad.conf1,site.asad.conf2, site.asad.conf3
使用JavaConfig类提供Bean的定义信息
使用@Configuration注解可以将一个 Java 类标注为 JavaConfig类,在 JavaConfig 类中,每一个标注了@Bean的类方法相当于提供了一个Bean的定义信息;
@Bean可以通过入参显示指定Bean名称,如:Bean(name="userDao");
package site.assad.conf1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import site.assad.conf.LogDao;
import site.assad.conf.LogonService;
import site.assad.conf.UserDao;
public class AppConfig {
public UserDao userDao(){
return new UserDao();
}
public LogDao logDao(){
return new LogDao();
}
public LogonService logonService(){
LogonService logonService = new LogonService();
logonService.setUserDao(userDao());
logonService.setLogDao(logDao());
return logonService;
}
}
可以通过@Import导入另一个配置类的配置信息,将其组合到当前的配置类中:
DaoConfig.class) //导入DaoConfig的配置信息,将其进行组合 (
public class ServiceConfig {
private DaoConfig daoConfig;
public LogonService logonService(){
LogonService logonService = new LogonService();
logonService.setLogDao(daoConfig.logDao());
logonService.setUserDao(daoConfig.userDao());
return logonService;
}
}
使用基于JavaConfig类的配置信息启动Spring容器
使用@Configuration标注类为配置类后,启动Spring容器有以下3种方式:
1. 直接通过 @Configuration 类启动 Spring 容器
示例代码模块:site.assad.conf/*.class,site.asad.conf1/*.class
; ;site.asad.conf/ConfigTest
启动代码如下:
//方式1:通过构造函数加载配置类
AnnotationConfigApplicationContext factory = new AnnotationConfigApplicationContext(AppConfig.class);
//方式2:通过编码的方式注注册配置类
AnnotationConfigApplicationContext factory = new AnnotationConfigApplicationContext();
factory.register(DaoConfig.class); //注册 DaoConfig
factory.register(ServiceConfig.class); //注册 ServiceConfig
factory.refresh(); //刷新上下文
2. 通过 XML 配置文件引用 @Configuration 的配置
示例代码模块:site.assad.conf/*.class,site.asad.conf2/*.class
;site.asad.conf/beanConf2.xml
;site.asad.conf/ConfigTest
添加一个扫描包的xml配置文件:
<beans ...>
<!--扫描site.assad.conf2.AppConfig配置类,并初始化其中定义的bean-->
<context:component-scan base-package="site.assad.conf2" resource-pattern="AppConfig.class" />
</beans>
启动容器的代码:
ApplicationContext factory = new ClassPathXmlApplicationContext("site/assad/conf/beanConf2.xml");
3. 通过 @Configuration 配置类引用 XML 的配置信息
示例代码模块:site.assad.conf/*.class,site.asad.conf3/LogonServiceConfig
;site.asad.conf/beanConf3.xml ;site.asad.conf/ConfigTest
xml配置文件信息如下:
<beans ...>
<!--定义2个bean-->
<bean id="userDao" class="site.assad.conf.UserDao" />
<bean id="logDao" class="site.assad.conf.LogDao" />
</beans>
其中在配置类添加@ImportReource标注,导入xml配置文件:
"classpath:site/assad/conf/beanConf3.xml") //引入XML配置文件 (
public class LogonServiceConfig {
public LogonService logonService(UserDao userDao, LogDao logDao){
LogonService logonService = new LogonService();
logonService.setLogDao(logDao);
logonService.setUserDao(userDao);
return logonService;
}
}
通过 Groovy DSL 装配
在 Spring 4.0 引入额使用 Groovy DSL 脚本配置bean的方式,并提供了 GenericGroovyApplicationContext 用于启动 Spring 容器;
示例代码模块:site.assad.groovy/*.class
;site.asad.groovy/spring-context.groovy;site.asad.groovy/GroovyTest
使用 Groovy DSL 提供Bean的定义信息
示例中用于定义bean信息的 groovy脚本 spring-context.groovy 如下:
import site.assad.groovy.DbUserDao
import site.assad.groovy.XmlUserDao
import site.assad.groovy.LogDao
import site.assad.groovy.LogonService
import org.springframework.core.io.ClassPathResource
beans {
//声明 context 命名空间
xmlns context: "http://www.springframework.org/schema/context"
//与注解混合使用,定义注解Bean的扫描包
context.'component-scan'('base-package': "com.smart.groovy") {
'exclude-filter'('type': "aspectj", 'expression': "com.smart.xml.*") //排除不需要的包
}
//读取 app-conf.properties 配置文件
def stream
def config = new Properties()
try{
stream = new ClassPathResource('conf/app-conf.properties').inputStream
config.load(stream)
}finally{
if(stream != null)
stream.close()
}
//根据条件注入Bean
if(config.get('dataProvider') == 'db'){
userDao(DbUserDao)
}else{
userDao(XmlUserDao)
}
//配置无参构造函数注入Bean
logDao(LogDao){
bean ->
bean.scope = "prototype"
bean.initMethod="init"
bean.destroyMethod="destory"
bean.lazyInit = true
}
//配置由参数构造函数注入Bean,对应 LogonService 以 UserDao作为参数的构造函数
logonService(LogonService,userDao){
logDao = ref('logDao') //配置属性注入,引用Groovy定义的Bean
}
}
使用 GenericGroovyApplicationContext 启动Spring容器
//加载 Groovy Bean 配置文件
ApplicationContext cxt = new GenericGroovyApplicationContext("classpath:site/assad/groovy/spring-context.groovy");
4 种装配方式的适用场景
- 基于XML的配置方式
① Bean 实现类来源于第三方类库,如:DataSource,JdbcTemplate等(无法在类中使用注解标注);② 命名空间的配置,如aop,context等,只能采用基于xml的配置;
-
基于注解的配置方式
Bean 的实现类是当前项目开发的,可以直接在 Java 类中使用基于注解的配置;
-
基于JavaConfig类的配置方式
优势在于可以通过代码的方法控制 Bean 初始化的整体逻辑,如果实例化 Bean 比较复杂,使用 JavaConfig 会比较方便;
-
基于Groovy DSL的配置方式
优势在于可以同构 groovy 脚本灵活地控制 Bean 初始化的过程;
一般项目开发采用地比较多地是“xml+注解”的方式,能用注解的地方尽量使用注解,以减少代码冗余;