10 装配SpringBean
10.1 依赖注入的3种方式
-
Spring主要使用依赖注入的方式来完成IoC
-
依赖注入可以分为3种方式
- 构造器注入
setter注入- 接口注入
10.1 构造器注入
-
简介
- 构造器注入依赖于构造方法的实现
- 为了让
Spring完成对应的构造注入,我们有必要去描述具体的类、构造方法并设置对应的参数,这样Spring就可以通过对应的信息用反射的形式创建对象
-
实例: 构造器注入
-
创建一个只有有参构造器的
POJO1
2
3
4
5
6
7
8
9
10
11
12public class Role {
private Long id;
private String roleName;
private String note;
public Role(String roleName, String note) {
this.roleName = roleName;
this.note = note;
}
/*getter and setter*/
} -
在
spring-config.xml中配置对象1
2
3
4<bean id="role1" class="Role">
<constructor-arg index="0" value="总经理"/>
<constructor-arg index="1" value="公司管理者"/>
</bean>
-
-
构造器配置
constructor-arg元素用于定义类构造方法的参数index用于定义参数的位置value则是设置值- 通过这样的定义Spring便知道使用
Role(String, String)这样的构造方法去创建对象
10.1.2 使用setter注入
-
简介
setter注入是Spring中最主流的注入方式,它利用Java Bean规范所定义的setter方法来完成注入,灵活且可读性高- 通过Java反射技术,Spring会调用没有参数的构造方法来生成对象,同时通过反射对应的
setter注入配置的值
-
实例:配置
setter注入1
2
3
4<bean id="role2" class="Role">
<property name="roleName" value="高级工程师"/>
<property name="note" value="重要人员"/>
</bean>
10.1.3 接口注入
- 简介
- 有些时候资源并非来自于自身系统,而是来自于外界
- 比如数据库连接资源完全可以在Tomcat下配置,然后通过JNDI的方式去获取它
10.2 装配Bean概述
-
本节需要学习的是如何将自己开发的
Bean装配到Spring IoC容器中 -
Spring提供了3种方法进行配置
- 在XML中显式配置
- 在Java的接口和类中实现配置
- 隐式Bean的发现机制和自动装配原则
-
3种方法的优先级
- 基于约定优于配置的原则,最优先的应该是通过隐式Bean的发现机制和自动装配的原则。这样的好处是减少程序开发者的决定权,简单而不失灵活
- 在没有办法使用自动装配原则的情况下应该优先考虑Java接口和类中实现配置,这样的好处是避免XML配置的泛滥
- 在上述方法都无法使用的情况下,只能选择XML去配置Spring IoC容器
10.3 通过XML配置装配Bean
-
首先给出XML文件的模板
1
2
3
4
5
6
<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">
<!-- Spring Bean 配置代码 -->
</beans>- 在上述代码中引入了一个
beans的定义,它是一个根元素,而XSD文件也被引入了
- 在上述代码中引入了一个
10.3.1 装配简易值
-
示例1: 简易XML装配
bean1
2
3
4
5<bean id="role2" class="Role">
<property name="id" value="1"/>
<property name="roleName" value="高级工程师"/>
<property name="note" value="重要人员"/>
</bean>id属性是Spring对于这个Bean的编号,如果没有显式指定id,则Spring将采用全限定名#{number}的格式生成编号class是一个类的全限定名property元素是定义类的属性,其中name属性定义的是属性名称,而value是属性值
-
示例2: 有时需要向类A中注入自定义的类B
1
2
3
4
5
6
7
8
9<bean id="source" class="Source">
<property name="fruit" value="橙汁"/>
<property name="sugar" value="少糖"/>
<property name="size" value="大杯"/>
</bean>
<bean id="juiceMaker" class="JuiceMaker">
<property name="beverageShop" value="贡茶"/>
<property name="source" ref="source"/>
</bean>- 可以通过
ref属性来引用对应的Bean
- 可以通过
10.3.2 装配集合
-
Spring主要使用下列标签完成对集合的装配
List标签为对应的<list>元素进行装配,然后通过多个<value>元素设值Map属性为对应的<map>元素进行装配,然后通过多个<entry>元素设值,只是entry包含一个键(key)和一个值(value)的设置。Properties属性,为对应的<properties>元素进行装配,通过多个<property>元素设置,只是prop元素有一个必填属性key,然后可以设置值。Set属性为对应的<set>元素进行装配,然后通过多个<value>元素设值。- 对于数组而言,可以使用
<array>设置值,然后通过多个<value>元素设值。
-
示例: 装配集合类
-
首先定义一个复杂的类
1
2
3
4
5
6
7
8
9public class ComplexAssembly {
private Long id;
private List<String> list;
private Map<String, String> map;
private Properties prop;
private Set<String> set;
private String[] array;
}
/*getter and setter*/ -
装配这个集合类
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
31
32
33
34
35
36
37
38<bean id="complexAssembly" class="ComplexAssembly">
<property name="id" value="1"/>
<property name="list">
<list>
<value>value_list_1</value>
<value>value_list_2</value>
<value>value_list_3</value>
</list>
</property>
<property name="map">
<map>
<entry key="key1" value="value_key_1"/>
<entry key="key2" value="value_key_2"/>
<entry key="key3" value="value_key_3"/>
</map>
</property>
<property name="prop">
<props>
<prop key="prop1">value_prop_1</prop>
<prop key="prop2">value_prop_2</prop>
<prop key="prop3">value_prop_3</prop>
</props>
</property>
<property name="set">
<set>
<value>value_set_1</value>
<value>value_set_2</value>
<value>value_set_3</value>
</set>
</property>
<property name="array">
<array>
<value>value_array_1</value>
<value>value_array_2</value>
<value>value_array_3</value>
</array>
</property>
</bean>
-
10.3.3 命名空间装配
10.4 通过注解装配Bean
-
简介: 注解功能更加强大,它既可以实现
XML的功能,也提供了自动装配的功能 -
在
Spring中,它提供了两种方式来让Spring IoC容器发现Bean- 组件扫描: 通过定义资源的方式,让
Spring IoC容器扫描对应的包,从而把Bean装配进来 - 自动装配: 通过注解定义,使得一些依赖关系可以通过注解完成
- 组件扫描: 通过定义资源的方式,让
-
通过扫描和自动装配,大部分的工程都可以用
Java配置完成,而不是XML,这样可以有效减少配置和引入大量XML
10.4.1 使用@Component装配Bean
-
示例1: 使用注解装配简单的
Bean-
定义
POJO1
2
3
4
5
6
7
8
9
10
11
12
13package ComponentDemo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
(value = "role")
public class Role {
("1")
private Long id;
("role_name_1")
private String roleName;
("role_note_1")
private String note;- 注解
@Component代表Spring IoC会把这个类扫描生成Bean实例,其中的value属性代表这个类在Spring中的id - 注解
@Value代表的是值的注入,这里只是注入了一些简单值
- 注解
-
定义
Java Config类1
2
3
4
5
6
7package ComponentDemo;
import org.springframework.context.annotation.ComponentScan;
public class PojoConfig {
}- 这个类的作用是告诉
Spring IoC去哪里扫描对象 - 注解
@ComponentScan代表进行扫描,默认是扫描当前包的路径,POJO的包名和它保持一致时才能扫描
- 这个类的作用是告诉
-
使用注解生成
Spring IoC容器1
2
3
4
5
6
7
8
9
10
11
12
13package ComponentDemo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(PojoConfig.class);
Role role = ctx.getBean(Role.class);
System.out.println(role.getNote());
((AnnotationConfigApplicationContext) ctx).close();
}
}
-
-
@ComponentScan存在着两个配置项basePackages: 它可以配置一个Java包的数组,Spring会根据它的配置扫描对应的包和子包,将配置好的Bean装配进来basePackageClasses: 它可以配置多个类,Spring会根据配置的类所在的包,为包和子包进行扫描装配对应的配置Bean- 如果采用多个
@ComponentScan去定义对应的包,但是每定义一个@ComponentScan,Spring就会为所定义的类产生一个新的对象 - 对于
basePackages和basePackageClasses来说,前一种可读性更好,所以会在项目中优先选择前一种
10.4.2 自动装配—@Autowired
-
自动装配简介
Spring一般先完成Bean的定义和生成,然后再寻找需要注入的资源- 当
Spring生成所有的Bean后,如果发现这个注解,它就会再Bean中查找,然后找到对应的类型,将其注入进来,这样就完成了依赖注入 - 所谓自动装配技术是一种由
Spring自己发现对应的Bean,自动完成装配工作的方式,它会应用到一个十分常见的注解@Autowired
-
示例: 一个简单的自动装配例子
-
定义
POJO: 如前文的Role类 -
定义
RoleService接口1
2
3
4
5package ComponentDemo;
public interface RoleService {
public void printRoleInfo();
} -
定义
RoleServiceImpl实现类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package ComponentDemo;
import org.springframework.stereotype.Component;
(value = "roleServiceImpl")
public class RoleServiceImpl implements RoleService {
private Role role = null;
public void printRoleInfo() {
System.out.println("id = " + role.getId());
System.out.println("roleName = " + role.getRoleName());
System.out.println("note = " + role.getNote());
}
} -
定义
Java Config类:略 -
测试:略
-
-
其他
@Autowired的注解表示在Spring IoC定位所有的Bean后,这个字段需要按类型注入,这样IoC容器会寻找资源,然后将其注入- IoC容器有时候会寻找失败,在默认的情况下寻找失败它就会抛出异常
- 通过
@Autowired(required=false)配置,可以在找不到资源时,不注入,但这样会产生空指针问题
- 通过
10.4.3 自动装配的歧义性(@Primary和@Quaifier)
-
背景: 在java中一个接口可能有多个实现类,这个时候
@Autowired注解无法判断应该把哪个实现类的对象注入进来,从而抛出异常 -
方案1: 注解
@Primary- 这个注解会告诉
Spring IoC容器,优先使用拥有此注解的类注入 - 例如
1
2
3
4import org.springframework.context.annotation.Primary
("roleService3")
public class RoleServiceImpl3 implements RoleService{} - 这个注解会告诉
-
方案2:注解
@Qualifier- 这个注解会使用名称查找而不是类型查找的方式
- 例如
1
2
3
4
5
6public class RoleController{
("roleService3")
private RoleService roleService = null;
//...
}
10.4.4 装载带有参数的构造方法类
-
背景: 某些时候构造方法是有参数的,对于带参数的构造方法,我们也可以通过注解进行注入
-
方案: 使用
@Autowired或者@Qualifer来修饰参数 -
例如
1
2
3public RoleController(@Autowired RoleService roleService){
this.roleService = roleService;
}
10.4.5 使用@Bean装配Bean
-
背景: 对于java来说,大部分开发都需要引入第三方的包,而且往往没有这些包的源码,这时候无法为这些包的类加入
@Component注解,让它们成为开发环境中的Bean -
方案:这个时候Spring给予一个注解
@Bean,它可以注解到方法上,并且将方法返回的对象作为Spring的Bean,存放到IoC容器中 -
例如: 通过注解
@Bean装配DataSource的Bean1
2
3
4
5
6
7
8
9
10
11(name="dataSource")
public DataSource getDataSource(){
Properties props = new Properties();
props.setProperty("driver","com.mysql.jdbc.Driver");
DataSource dataSource = null;
try{
dataSource = BasicDataSourceFactory.createDataSource(props);
}catch(Exception e){
e.printStackTrace();
}
} -
这里还配置了
@Bean的name选项为dataSource,这就意味着Spring生成该Bean的时候就会使用dataSource作为其BeanName。和其他Bean一样,它也可以通过@Autowired或者@Qualifier等注解注入到别的Bean中
10.5 装配的混合使用
Spring同时支持XML和注解两种装配方式,无论采用哪种,本质都是将Bean装配到Spring IoC容器中
建议
- 在自己的工程中所开发的类尽量使用注解方法,因为使用它更为简单
- 对于引入第三方或者服务的类,尽量使用
XML方式,这样可以尽量减少对第三方包细节的理解,更清晰
如何将xml定义的bean引入java配置中?
- 可以使用注解
@ImportResourse,它可以配置多个XML配置文件,将这些文件中定义的bean全部引入 - 例如
1
2
3
4
5
6package com.ssm.annotation.config
import org.springframework.context.anotation.ComponentScan;
import org.springframework.context.anotation.ImportResource;
(basePackages={"com.ssm.chapter10.annotation"})
({"classpath:spring-dataSource.xml"})
public class ApplicationConfig{}
10.6 使用Profile
背景: 在软件开发中,可能开发人员使用一套环境,而测试人员使用另一套环境,这就有了在不同的环境中进行切换的需求。Spring也对这样的场景进行了支持,在Spring中可以定义Bean的Profile,方式有两种
- 使用注解
@Profile配置 - 使用
xml定义Profile
10.6.1 使用注解@Profile配置
例子如下
1 | package com.ssm.chapter10.profile; |
10.6.2 使用xml定义Profile
例子如下
1 |
|
10.6.3 启动Profile
当启动Java配置或者XML配置Profile时,可以发现这两个Bean并不会被加载到SpringIoC容器中,需要自行激活Profile。激活Profile的方法有5种
- 在使用
SpringMVC的情况下可以配置web上下文参数,或者DispatchServlet参数 - 作为
JNDI条目 - 配置环境变量
- 配置
JVM启动参数。 - 在集成测试环境中使用
@ActiveProfiles
10.7 加载属性(properties)文件
略
10.8 条件化装配Bean
背景: 在某些条件下需要进行条件判断,判断是否需要装配Bean。
方案: 这时Spring提供了注解@Conditional去配置,通过它可以配置一个或多个实现了Condition接口的类
例子:
-
如何放置条件判断
1
2
3
4
5(name="dataSource")
({DataSourceCondition.class})
public DataSource getDataSource(){
//.....
} -
条件判断类的实现
1
2
3
4
5
6public class DataSourceCondition implements Condition{
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
//....
}
}- 这里要求实现接口
Condition的matches方法。如果此方法返回true则Spring会创建对应的Bean
- 这里要求实现接口
10.9 Bean的作用域
在默认情况下,SpringIoc容器只会对一个Bean创建一个实例,有时候我们希望容器可以产生多实例,就需要修改Spring的作用域
Spring提供4种作用域
- 单例(singleton): 默认选项,在整个应用中,
Spring只会为其生成一个Bean实例 - 原型(prototype): 当每次通过容器获取
Bean时都会创建一个实例 - 会话(session): 在会话过程中
spring只创建一个实例 - 请求(request): 在一个请求中
spring只创建一个实例
10.10 使用Spring表达式(SpringEL)
SpringEL的主要功能
- 使用
Bean的id来引用Bean - 使用指定对象的方法和访问对象的属性
- 进行运算
- 提供正则表达式进行匹配
- 集合配置
也就是为注解提供一定的逻辑运算功能,具体使用方式略