Spring框架 - IOC和DI
Spring概述
Spring是一个开源框架
Spring为简化企业级开发而生,使用Spring,JavaBean就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB中要通过繁琐的配置和复杂的代码才能够实现,而在Spring中却非常的优雅和简洁。
Spring是一个IOC(DI)和AOP容器框架。
Spring的优良特性
① 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
② 依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。
③ 面向切面编程:Aspect Oriented Programming——AOP
④ 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
⑤ 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
Spring模块
安装Spring插件【了解】
- eclipse可以选中在线安装Spring Tool Suite 这个插件。Help —> Eclipse Marketplce…之后搜索spring tool suite
- IDEA : File ——》Settings ——》Plugins ——》Spring Assistant。
搭建Spring运行时环境
加入JAR包
① Spring自身JAR包:spring-framework-4.0.0.RELEASE\libs目录下
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
② commons-logging-1.1.1.jar
在Spring Tool Suite工具中通过如下步骤创建Spring的配置文件
① File->New->Spring Bean Configuration File
② 为文件取名字 例如:
applicationContext.xml
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
<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-4.0.xsd">
<!-- 配置bean
配置方式: 基于xml的方式 ,使用的全类名的方式.
<bean>: 受Spring管理的一个javaBean对象.
id: <bean>的唯一标识. 在整个IOC容器中唯一不重复.
class: 指定javaBean的全类名. 目的是通过反射创建对象。
Class cls = Class.forName("top.fulsun.spring.helloWorld.Person");
Object obj = cls.newInstance(); 必须提供无参数构造器.
<property>: 给对象的属性赋值
name: 指定属性名 , 指定set风格的属性名.
value:指定属性值
-->
<bean id="person" class="top.fulsun.spring.helloWorld.Person">
<property name="name2" value="HanMeiMei"></property>
</bean>
<bean id="person1" class="top.fulsun.spring.helloWorld.Person">
<property name="name2" value="LiLei"></property>
</bean>
</beans>
HelloWorld
目标:使用Spring创建对象,为属性赋值
创建Student类
创建Spring配置文件
1
2
3
4
5
6
7
8
9
10<!-- 使用bean元素定义一个由IOC容器创建的对象 -->
<!-- class属性指定用于创建bean的全类名 -->
<!-- id属性指定用于引用bean实例的标识 -->
<bean id="student" class="top.fulsun.helloworld.bean.Student">
<!-- 使用property子元素为bean的属性赋值 -->
<property name="studentId" value="1001"/>
<property name="stuName" value="Tom2015"/>
<property name="age" value="20"/>
</bean>测试:通过Spring的IOC容器创建Student类实例
1
2
3
4
5
6
7//1.创建IOC容器对象
ApplicationContext iocContainer =
new ClassPathXmlApplicationContext("helloworld.xml");
//2.根据id值获取bean实例对象
Student student = (Student) iocContainer.getBean("student");
//3.打印bean
System.out.println(student);
IOC和DI
IOC(Inversion of Control):反转控制
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
DI(Dependency Injection):依赖注入
IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
IOC 描述的是一种思想,而DI 是对IOC思想的具体实现.
IOC容器在Spring中的实现
在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
Spring提供了IOC容器的两种实现方式
① BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
② ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
ApplicationContext的主要实现类
- ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
- FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
- 默认在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。
ConfigurableApplicationContext
- 是ApplicationContext的子接口,包含一些扩展方法
- refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。
WebApplicationContext
- 专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
通过类型获取bean
从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的。
HelloWorld helloWorld = cxt.getBean(HelloWorld.class);
或者可以使用另外一个重载的方法,同时指定bean的id值和类型
HelloWorld helloWorld = cxt.getBean(“helloWorld”,HelloWorld.class);
给bean的属性赋值
依赖注入的方式
通过bean的setXxx()方法赋值
Hello World中使用 <property name="studentId" value="1001"/>
就是这种方式
通过bean的构造器赋值
Spring自动匹配合适的构造器
1
2
3
4
5
6<bean id="book" class="top.fulsun.spring.bean.Book" >
<constructor-arg value= "10010"/>
<constructor-arg value= "Book01"/>
<constructor-arg value= "Author01"/>
<constructor-arg value= "20.2"/>
</bean >通过索引值指定参数位置
1
2
3
4
5
6<bean id="book" class="top.fulsun.spring.bean.Book" >
<constructor-arg value= "10010" index ="0"/>
<constructor-arg value= "Book01" index ="1"/>
<constructor-arg value= "Author01" index ="2"/>
<constructor-arg value= "20.2" index ="3"/>
</bean >通过类型区分重载的构造器
反射获取构造器的时候,有get和set方法的时候,构造器遍历从下到上,没有则相反。遍历过程中如果数据能够类型能够赋值就会使用当前的构造器1
2
3
4
5
6<bean id="book" class="top.fulsun.spring.bean.Book" >
<constructor-arg value= "10010" index ="0" type="java.lang.Integer" />
<constructor-arg value= "Book01" index ="1" type="java.lang.String" />
<constructor-arg value= "Author01" index ="2" type="java.lang.String" />
<constructor-arg value= "20.2" index ="3" type="java.lang.Double" />
</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
31
32
33
34
35
36
37
38
39
40package top.fulsun.spring.di;
public class Car {
private String brand ; // 品牌
private String crop ; // 厂商
private Double price ; // 价格
private Integer speed ; // 速度
public Car() {
}
public Car(String brand, String crop, Double price) {
super();
this.brand = brand;
this.crop = crop;
this.price = price;
}
public Car(String brand, String crop, Integer speed) {
super();
this.brand = brand;
this.crop = crop;
this.speed = speed;
}
// GetXXX 和 SetXXX 方法
public String toString() {
return "Car [brand=" + brand + ", crop=" + crop + ", price=" + price + ", speed=" + speed + "]";
}
}1
2
3
4
5
6
7
8
9
10
11
12<bean id="car1" class="top.fulsun.spring.di.Car">
<constructor-arg value="宝马" index="0"></constructor-arg>
<constructor-arg value="450000" index="2" type="java.lang.Double"></constructor-arg>
<constructor-arg value="华晨" index="1"></constructor-arg>
</bean>
<bean id="car2" class="top.fulsun.spring.di.Car">
<constructor-arg value="奔驰"></constructor-arg>
<constructor-arg value="梅赛德斯"></constructor-arg>
<constructor-arg value="300"></constructor-arg>
</bean>- 如果第一个bean不指定类型,那么speed 就会被赋值为450000
p名称空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>
元素属性的方式配置Bean 的属性。
使用p命名空间后,基于XML的配置方式将进一步简化。
1 | xmlns:p="http://www.springframework.org/schema/p" |
1 | <bean id="studentSuper" class="top.fulsun.helloworld.bean.Student" |
赋值的类型
字面量
- 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
- 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
- 若字面值中包含特殊字符,可以使用
<![CDATA[]]>
把字面值包裹起来
null值
<property>
的标签中填充<null/>
- 不赋值就是默认值
1 | <bean class="top.fulsun.spring.bean.Book" id="bookNull" > |
给bean的级联属性赋值
- 如果当前bean下有个对象,可以用点的形势进行级联属性赋值
1 | <bean id="action" class="top.fulsun.spring.ref.Action"> |
外部已声明的bean
使用ref指定声明的bean的id
1 | <bean id="shop" class="top.fulsun.spring.bean.Shop" > |
内部bean
当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean。
内部bean声明直接包含在<property>
或<constructor-arg>
元素里,不需要设置任何id或name属性,就当前bean使用,内部bean不能使用在任何其他地方
1 | <bean id="shop2" class="top.fulsun.spring.bean.Shop" > |
复杂属性注入
在Spring中可以通过一组内置的XML标签来配置集合属性,例如:<list>
,<set>
或<map>
。
数组和List
配置java.util.List类型的属性,需要指定<list>
标签,在标签里包含一些元素。这些标签可以通过<value>
指定简单的常量值,通过<ref>
指定对其他Bean的引用。通过<bean>
指定内置bean定义。通过<null/>
指定空元素。甚至可以内嵌其他集合。
数组的定义和List一样,都使用<list>
元素。
配置java.util.Set需要使用<set>
标签,定义的方法与List一样。
1 | package top.fulsun.spring.di; |
1 | <!-- List集合 --> |
Map
Java.util.Map通过<map>
标签定义,<map>
标签里可以使用多个<entry>
作为子标签。每个条目包含一个键和一个值。必须在<key>
标签里定义键。因为键和值的类型没有限制,所以可以自由地为它们指定<value>
、<ref>
、<bean>
或<null/>
元素。
可以将Map的键和值作为<entry>
的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义。
1 | package top.fulsun.spring.di; |
1 | <!-- Map集合 --> |
集合类型的bean
如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。
配置集合类型的bean需要引入util名称空间
1 | <!-- 集合Bean --> |
Properties 注入
1 | <!-- private Properties info --> |
FactoryBean
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。工厂bean必须实现org.springframework.beans.factory.FactoryBean
接口。
1 | package org.springframework.beans.factory; |
Spring FactoryBean和BeanFactory 区别
BeanFactory 是ioc容器的底层实现接口,是ApplicationContext 顶级接口spring不允许我们直接操作 BeanFactory bean工厂,所以为我们提供了ApplicationContext 这个接口 此接口集成BeanFactory 接口,ApplicationContext包含BeanFactory的所有功能,同时还进行更多的扩展。
FactoryBean 接口提供三个方法,用来创建对象,
FactoryBean 具体返回的对象是由getObject 方法决定的。
1 | package top.fulsun.spring.factorybean; |
1 | <bean id="car" class="top.fulsun.spring.factorybean.CarFactoryBean"></bean> |
1 | public class Main { |
bean的高级配置
配置信息的继承
背景
查看下面两个Employee的配置,其中city属性是重复的。
1 | <bean id="address1" abstract="true"> |
配置信息的继承
设置parent属性的值,Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean,子bean从父bean中继承配置,包括bean的属性配置,子bean也可以覆盖从父bean继承过来的配置。
1 | <bean id="address1" class="top.fulsun.spring.relation.Address"> |
1 | package top.fulsun.spring.relation; |
补充说明
父bean可以作为配置模板,也可以作为bean实例。
若只想把父bean作为模板,可以设置
<bean>
的abstract
属性为true
,这样Spring将不会实例化这个bean,如果一个bean的class属性没有指定,则必须是抽象bean。
并不是
<bean>
元素里的所有属性都会被继承。比如:autowire,abstract等。也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同的属性配置。 但此时abstract必须设为true。
1 | <bean id="address1" abstract="true"> |
bean之间的依赖
有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。
例如:要求创建Employee对象的时候必须创建Department。这里需要注意的是依赖关系不等于引用关系,Employee即使依赖Department也可以不引用它。
1 | <!-- 依赖关系 --> |
1 |
|
bean的作用域★
在Spring中,可以在<bean>
元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
- 当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。后续每次通过getBean()方法获取bean对象时,返回的都是同一个对象.
- 而当bean的作用域为prototype时,在整个IOC容器中可有多个bean的对象。 在IOC容器对象被创建时, 不会创建原型的bean的对象,而是等到没次通过getBean()方法获取bean对象时,才会创建一个新的bean对象返回.
- request: 一次请求对应一个bean对象
- session: 一次会话对应一个bean对象
1 | <bean id="car" class="top.fulsun.spring.scope.Car" scope="prototype"> |
1 | public class Car { |
1 |
|
bean的生命周期
Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
Spring IOC容器对bean的生命周期进行管理的过程:
① 通过构造器或工厂方法创建bean实例
② 为bean的属性设置值和对其他bean的引用
③ 调用bean的初始化方法
④ bean可以使用了
⑤ 当容器关闭时,调用bean的销毁方法
在配置bean时,通过
init-method
和destroy-method
属性为bean指定初始化和销毁方法bean的后置处理器
① bean后置处理器允许在调用初始化方法前后对bean进行额外的处理
② bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。
其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。
③ bean后置处理器时需要实现接口:
org.springframework.beans.factory.config.BeanPostProcessor
。 在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
postProcessBeforeInitialization(Object, String)
postProcessAfterInitialization(Object, String)
添加bean后置处理器后bean的生命周期
①通过构造器或工厂方法创建bean实例
②为bean的属性设置值和对其他bean的引用
③将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
④调用bean的初始化方法
⑤将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
⑥bean可以使用了
⑦当容器关闭时调用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
31
32
33
34
35
36
37package top.fulsun.spring.lifecycle;
public class Car {
private String brand ;
private Double price ;
public Car() {
System.out.println("===>1. 调用构造器创建bean对象 ");
}
/**
* 初始化方法
* 需要通过 init-method来指定初始化方法
*/
public void init() {
System.out.println("===>3. 调用初始化方法");
}
/**
* 销毁方法: IOC容器关闭, bean对象被销毁.
*/
public void destroy() {
System.out.println("===>5. 调用销毁方法");
}
//GetXXX 和 SetXXX 方法省略
public String toString() {
return "Car [brand=" + brand + ", price=" + price + "]";
}
}bean的后置处理器 : 对IOC容器中所有的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
31package top.fulsun.spring.lifecycle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* bean的后置处理器 : 对IOC容器中所有的bean都起作用.
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在bean的生命周期的初始化方法之前执行
* Object bean: 正在被创建的bean对象.
* String beanName: bena对象的id值.
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization");
return bean;
}
/**
* 在bean的生命周期的初始化方法之后执行
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization");
return bean;
}
}指定初始化和销毁方法
1
2
3
4
5
6
7
8<bean id="car" class="top.fulsun.spring.lifecycle.Car"
init-method="init" destroy-method="destroy">
<property name="brand" value="宝马"></property>
<property name="price" value="450000"></property>
</bean>
<!-- 配置后置处理器 : Spring能自动识别是一个后置处理器 -->
<bean class="top.fulsun.spring.lifecycle.MyBeanPostProcessor"></bean>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void testLifeCycle() {
ConfigurableApplicationContext ctx =
new ClassPathXmlApplicationContext("spring-lifecycle.xml");
Car car = ctx.getBean("car",Car.class);
System.out.println("===>4. 使用bean对象" + car);
//关闭容器
ctx.close();
}
===>1. 调用构造器创建bean对象
===>2. 调用set方法给对象的属性赋值
postProcessBeforeInitialization
===>3. 调用初始化方法
postProcessAfterInitialization
===>4. 使用bean对象Car [brand=宝马, price=450000.0]
===>5. 调用销毁方法
自动装配
自动装配的概述
手动装配:以value或ref的方式明确指定属性值都是手动装配。
自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。
装配模式
根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配
根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同
通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。
1 | <!-- Person : 演示自动装配 |
1 |
|
选用建议
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。
通过注解配置bean
概述
相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。
使用注解标识组件
- 普通组件:
@Component
标识一个受Spring IOC容器管理的组件
持久化层组件:
@Repository
标识一个受Spring IOC容器管理的持久化层组件
业务逻辑层组件:
@Service
标识一个受Spring IOC容器管理的业务逻辑层组件
表述层控制器组件:
@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
组件命名规则
① 默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id
② 使用组件注解的value属性指定bean的id
注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误,所以 @Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色。
扫描组件
组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到。
指定被扫描的package
1
2
3
4<!-- 组件扫描: 扫描加了注解的类,并管理到IOC容器中
base-package: 基包. Spring会扫描指定包以及子包下所有的类,将带有注解的类管理到IOC容器中
-->
<context:component-scan base-package="top.fulsun.spring.annotation" use-default-filters="true">详细说明
base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。
当需要扫描多个包时可以使用逗号分隔。
如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,示例:
1
2
3<context:component-scan
base-package="top.fulsun.component"
resource-pattern="autowire/*.class"/>
包含与排除
<context:include-filter>
子节点表示要包含的目标类
注意:通常需要与
use-default-filters
属性配合使用才能够达到“仅包含某些 组件”这样的效果。即:通过将use-default-filters属性设置为false禁用默认过滤器,然后扫描的就只是include-filter中的规则指定的组件了。
<context:exclude-filter>
子节点表示要排除在外的目标类component-scan下可以拥有若干个include-filter和exclude-filter子节点
过滤表达式
类别 示例 说明 annotation top.fulsun.XxxAnnotation 过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤。 assignable top.fulsun.BaseXxx 过滤所有BaseXxx类的子类。这个规则根据目标组件是否是指定类型的子类的方式进行过滤。 aspectj top.fulsun.*Service+ 所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。 regex top\.fulsun\.anno\.*
所有top.fulsun.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤。 custom top.fulsun.XxxTypeFilter 使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口 1
2
3
4
5
6
7
8
9
10
11<context:component-scan base-package="top.fulsun.spring.annotation" use-default-filters="true">
<!-- 指定扫描 必须 设置use-default-filters="false"
排除扫描 use-default-filters="true" -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="assignable" expression="top.fulsun.spring.annotation.controller.UserController"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="assignable" expression="top.fulsun.spring.annotation.controller.UserController"/>
</context:component-scan>
JAR包
必须在原有JAR包组合的基础上再导入一个:spring-aop-4.0.0.RELEASE.jar
1
2
3
4
5<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
组件装配
需求
Controller组件中往往需要用到Service组件的实例,Service组件中往往需要用到 Repository组件的实例。Spring可以通过注解的方式帮我们实现属性的装配。
实现依据
在指定要扫描的包时,
<context:component-scan>
元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor
的实例。该后置处理器可以自动装配标记 了@Autowired
、@Resource
或@Inject
注解的属性。@Autowired注解
默认优先ByType进行自动装配,当发现装配类型于spring容器中存在两个及以上实例时,会采用ByName的方式继续寻找对应的实例进行装配。
根据类型实现自动装配。
构造器、普通字段(即使是非public)、一切具有参数的方法都可以应用@Autowired 注解
默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的bean装配属性时,会抛出异常。
若某一属性允许不被设置,可以设置
@Autowired
注解的required
属性为 false默认情况下,当IOC容器里存在多个类型兼容的bean时
- Spring会尝试匹配bean的id值是否与变量名相同,如果相同则进行装配。如果bean的id值不相同,通过类型的自动装配将无法工作。
- 此时可以在@Qualifier注解里提供bean的名称。
- Spring甚至允许在方法的形参上标注@Qualifiter注解以指定注入bean的名称。
@Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的bean进行自动装配。
@Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean。
@Autowired注解用在java.util.Map上时,若该Map的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。
- @Resource
@Resource
注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为bean的名称。
- @Inject
@Inject
和@Autowired
注解一样也是按类型注入匹配的bean,但没有reqired
属性。
示例
controller层
1 | package top.fulsun.spring.annotation.controller; |
service层
1 | public interface UserService { |
Dao层
1 | package top.fulsun.spring.annotation.dao; |
配置文件
1 | <context:component-scan base-package="top.fulsun.spring.annotation" use-default-filters="true"> |
测试
1 |
|
配置类方式
- 编写如下代码
1 |
|
将AppConfig这个配置文件传递给AnnotationConfigApplicationContext的构造函数, 这样就能够读取配置,将配置里面的Bean装配到IOC容器中
1
2
3
4
5
6
7public class IocTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.getBean(User.class);
log.info(user.toString());
}
}
扫描装配Bean
如果每一个Bean都使用@Bean注入到IOC 容器中是一件麻烦的事情。好在Spring还允许我们使用扫描装配Bean到Ioc容器。
@Component: 是标明哪个类被扫描进入 spring IOC 容器
@ComponentScan: 标明采用何种策略去扫描装配 Bean
示例
新建一个配置文件User.java
- 这里的@Component表明这个类将被 Spring IOC 容器扫描装配,其中“user”是作为Bean的名称,默认把类名的第一个字母小写,其他不变作为Bean名称放入IOC容器中。
- @Value 用来指定具体的值,使得 spring IOC 给予对应的属性注入对应的值
1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {
private Long id;
private String username;
private String note;
}为了让容器装配这个类,重写AppConfig.java配置文件
1
2
3
4
5
6
7
8
9
public class AppConfig {
/*@Bean(name = "fulaing")
public User initUser() {
User user = new User(1L, "fulsun", "shanghai");
return user;
}*/
}
- 加入了@ComponentScan,意味着它会进行扫描,但是只会扫描 AppConfig 所在的当前包和子包,这样就可以删除之前使用@Bean 标注的创建对象的方法。
不同包下的扫描
为了使得 User 类能够被扫描,将它迁移到了本不该放置的配置包,这样就显得不太合理。
@ComponentScan 还允许我们自定义扫描的的包
将上面的User.java放到pojo包下,修改AppConfig的注解为下面三种的任何一种
1
2
3
4
5
6
7
//@ComponentScan("pers.sfl.chapter3.pojo.*")
//@ComponentScan(basePackageClasses ={User.class})
public class AppConfig {
}
排除某个包下的Bean
假设有目录结构如下
1
2
3
4- pers.sfl.chapter3
- service
- pojo
- ...如果service包下的UserService.java 使用 @Service标注,使用
@ComponentScan("pers.sfl.chapter3.*")
service 和 pojo 二个包都会被扫描装配到IOC容器中,为了不装配service注解,需要修改扫描策略:
1
2
3由于加入了 excludeFilters 的配置,是标注了@Service 的类将不能够被 IOC 容器扫描注入,这样就可以把 UserService 类排除到 Spring IOC 容器中。
第三方Bean
开发中往往需要引入许多来自第三方的包,并且很有可能希望把第三方包的类对象也放到 IOC 容器中,这个时候使用@Bean 注解就可以了。
引入DBCP数据源
我们先在pom.xml上加入项目所需要的DBCP包和数据库MySQL 驱动程序的依赖
1
2
3
4
5
6
7
8<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>生成数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
import java.util.Properties;
public class DataSourceConfig {
public DataSource getDataSource(){
Properties properties = new Properties();
properties.setProperty("driver","com.mysql.jdbc.Driver");
properties.setProperty("url","jdbc:mysql://localhost:3306/test");
properties.setProperty("username","root");
properties.setProperty("password","123456");
DataSource datasource=null;
try {
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
return datasource;
}
}
依赖注入
- 上面只讨论了如何将 Bean 装配到 IOC 容器,对于如何获取,还有一个作用没有谈及,就是 Bean 之间的依赖,在Spring Ioc的概念中,我们称为依赖注入 (Dependency Injection, DI)
- 例如人有时候利用一些动物完成一些事情,比方说狗用来看门,猫用来抓老鼠…于是做一些事情就依赖于动物了。
自动装配相关的注解
- @Resource和@Inject都是Java平台提供的注解,主要用于JavaEE,而之所以能在Spring中生效是由于Spring实现了相关的规范
- 而@Autowired是Spring提供注解,使用上只要明白其注入顺序,正确注入使用即可,没有过多的区别,安装自己的使用习惯或者团队的风格选择使用即可。
@Resource
- JDK默认提供的注解,属于JSR-250规范的一部分(其他的还有@PostConstruce/@PreDestroy等),可以标记在属性或者Setter上,Spring通过CommonAnnotationBeanPostProcessor来处理该注解,在实现依赖注入的时候的匹配顺序是:
- 基于名称
- 基于类型
- 基于@Qualifier
- 不支持spring的@Primary注解优先注入
@Inject
属于JSR-330提供的注解,该规范主要提供Java注入相关的注解,需要手动引入:
1
2
3
4
5<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>Spring通过AutowiredAnnotationBeanPostProcessor来处理该注解,处理顺序是:
- 基于类型
- 基于@Qualifier
- 基于名称和@Named
- 支持spring的@Primary注解优先注入
@Autowired
- Spring提供的注解,功能与@Inject相似,也是通过AutowiredAnnotationBeanPostProcessor处理,处理的顺序同样是:
- 基于类型
- 基于@Qualifier
- 基于名称
例子
定义人的接口
1
2
3
4
5
6
7
8public interface Person {
// 使用动物服务
public void service();
// 使用动物服务
public void setAnimal(Animal animal);
}实现类
- 这里的使用了注解@Autowired,它会根据属性的类型(by type)找到对应的Bean进行注入
- 下面定义的 Cat 类是 Animal 的一种(实现类),所以IOC容器会把 Cat 的实例注入到 BussinessPerson 中,这样通过 IOC 容器获取 BussinessPerson 实例的时候就能够使用 Cat 实例来提供服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BussinessPerson implements Person {
private Animal animal=null;
public void service() {
this.animal.use();
}
public void setAnimal(Animal animal) {
this.animal=animal;
}
}定义动物的接口
1
2
3public interface Animal {
public void use();
}动物的实现类
1
2
3
4
5
6
7
8
public class Cat implements Animal {
public void use() {
System.out.println("猫:["+Cat.class.getSimpleName()+"] 是捉老鼠的。");
}
}测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// AppConfig配置包扫描
public class IocTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new
//加载AppConfig的配置问文件
AnnotationConfigApplicationContext(AppConfig.class);
// User user = ctx.getBean(User.class);
BussinessPerson person = ctx.getBean(BussinessPerson.class);
person.service();
}
}
日志
16:31:53.848 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
16:31:53.865 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bussinessPerson'
16:31:53.888 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cat'
16:31:53.892 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
猫:[Cat] 是捉老鼠的。
@Autowired注入问题
- 通过 IOC 容器的顶级接口 BeanFactory 可以得知 IOC 容器是通过 getBean() 方法获取对应的 Bean,而 getBean 又支持根据类型(by type)或者根据名称(by name)。
创建一个狗的类
1
2
3
4
5
6
7
8
public class Dog implements Animal {
public void use() {
System.out.println("狗:["+ Dog.class.getSimpleName()+"] 是看门的。");
}
}继续使用 BussinessPerson 类,麻烦来了:这个类只是定义了一个动物属性(Animal),而我们容器中有二个动物猫和狗,那么如何注入了?
继续测试发现抛出异常:根据错误日志判断: IOC并不知道需要注入什么对象给 BussinessPerson 对象,进而导致错误产生。
1
2
3Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'pers.sfl.chapter3.pojo.definiton.Animal' available:
expected single matching bean but found 2: cat,dog
解决方法1 - 修改属性名
现在有二个动物,假设我们需要的是 Dog 提供服务,我们可以将属性名称转化为 dog
1
2
3
4
5
6
7
private Animal animal=null;
// 这里我们只是将属性名称从 animal 改成 dog,再次测试的时候,发现是采用狗提供服务
private Animal dog=null;
@Autowired 规则
首先根据类型找到对应的Bean,如果对应类型的 Bean 不是唯一的,那么会根据其属性名称和 Bean 名称进行匹配,如果匹配成功就使用,匹配不上就抛出异常。
还要注意: @Autowwired 是一个默认必须找到对应Bean的注解,如果不能确定其标注的属性一定存在且允许这个被标注的属性为null,那么可以设置@Autowwired 的属性 required 为 false,如:
@Autowwired(required = false)
同样,它除了可以标注属性外,还可以标注方法和方法的参数上,如 setAnimal 方法,如下
1
2
3
4
5
public void setAnimal(Animal animal) {
this.dog=animal;
}
解决方法2 - @Primary
上面发现有猫有狗的时候,为了使 @Autowwired 能够继续使用,我们修改了 Animal 的属性名称为 dog。明明是个动物,为什么被定义成了狗。
产生失败的问题根本是根据类型(by type)查找,而动物可以有多种类型,这样会造成Spring IOC 容器注入的困扰。我们把这样的一个问题定义为歧义性。
首先是一个注解@primary ,这是一个修改优先权的注解,假设这次需要用,那么只需要在猫类的定义上加入注解@primary 就可以了,类似下面:
1
2
3
4
5
6
7
8
9
public class Cat implements Animal {
public void use() {
System.out.println("猫:["+Cat.class.getSimpleName()+"] 是捉老鼠的。");
}
}这里的@Primary 的含义是当发现有多个同样类型的 Bean 时,请优先使用我进行注入,于是再进行测试会发现将采用猫进行服务。因为当 Spring 进行注入的时候虽然发现存在多个动物,但是因为 Cat 标注了@Primary,所以优先采用 Cat 的实例进行注入,这样就通过优先级变换使得 IOC 容器知道注入那个具体的实例来满足依赖注入。
有时候,@Primary 也可以使用在类上,也许猫和狗都同时使用了@Primary 注解,结果 IOC 容器还是无法区分采用那个 Bean 的实例进行注入,我们需要更灵活的机制来实现。
解决办法3 - @Qualifier
@Quelifier 可以满足这个要求,它的配置项 value 需要一个字符串去定义,它将于@Autowired 组合在一起,通过类型和名称一起找到 Bean。
我们知道 Bean 的名称在 Spring IOC 容器中是唯一的标识。通过这个就能够消除歧义性。
BeanFactory接口中有一个方法
1
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
通过它就能够根据名称和类型结合找到对象了,假设猫已经标注了@Primary,而我们需要使用狗提供服务,因此需要修改 BussinessPerson的属性 animal 的标注已适合我们的需要,如下:
1
2
3
4
private Animal animal =null;一旦这样声明,Spring IOC 容器将会以类型和名称去寻找对应的 Bean 进行注入,根据类型和名称,显然也只能找到狗为我们提供服务。
有参构造类的装配
在上面,我们都基于一个默认的情况,那就是不带参数的构造方法下实现依赖注入。
但事实上,有些类只有带有参数的构造方法,于是上述的方法都不能再使用了。
为了满足这个功能,我们可以 使用@Autowired注解对构造方法的参数进行注入
例如,修改类BussinessPerson来满足这个功能,
1
2
3
4
5
6
7
8public class BussinessPerson implements Person {
private Animal animal=null;
public BussinessPerson({ Animal animal)
this.animal=animal;
}
}可以看到,代码中取消了@Autowired对属性和方法的标注。,在参数上加入了 @Autowired和@Qualifier 注解,使得它能够注入进来。这里使用@Qualifier是为了避免歧义性。当然如果你的环境中不是有猫有狗,则可以完全不使用©Qualifier,而单单使用@Autowired就可以了。
spring获取bean
Bean工厂(com.springframework.beans.factory.BeanFactory)是Spring框架最核心的接口,它提供了高级IoC的配置机制。
BeanFactory使管理不同类型的Java对象成为可能,应用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。
我们一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。但有时为了行文方便,我们也将ApplicationContext称为Spring容器。
对于两者的用途,我们可以进行简单划分:
- BeanFactory是Spring框架的基础设施,面向Spring本身;
- ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合我们都直接使用ApplicationContext而非底层的BeanFactory。
ApplicationContext的初始化和BeanFactory有一个重大的区别:
- BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean;
- 而ApplicationContext则在初始化应用上下文时就实例化所有单实例的Bean。因此ApplicationContext的初始化时间会比BeanFactory稍长一些
要获取配置的Bean,最关键的是获取.springframework.context.ApplicationContext,以下是总结的几种方法
方法一:在初始化时保存ApplicationContext对象
- 说明:这种方式适用于采用Spring框架的独立应用程序,需要程序通过配置文件手工初始化Spring的情况。
1
2ApplicationContext ac = new FileSystemXmlApplicationContext("applicationContext.xml");
ac.getBean("beanId");方法二:通过Spring提供的utils类获取ApplicationContext对象
- 这种方式适合于采用Spring框架的B/S系统,通过ServletContext对象获取ApplicationContext对象,然后在通过它获取需要的类实例。
- 两个工具方式的区别是,前者在获取失败时抛出异常,后者返回null。
1
2
3
4ApplicationContext ac1 = WebApplicationContextUtils.getRequiredWebApplicationContext(ServletContext sc);
ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);
ac1.getBean("beanId");
ac2.getBean("beanId");方法三:继承自抽象类ApplicationObjectSupport
- 说明:抽象类ApplicationObjectSupport提供getApplicationContext()方法,可以方便的获取ApplicationContext。
- Spring初始化时,会通过该抽象类的setApplicationContext(ApplicationContext context)方法将ApplicationContext 对象注入。
1
2WebApplicationContext wac = (WebApplicationContext)servletContext.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);方法四:继承自抽象类WebApplicationObjectSupport
- 说明:类似上面方法,调用getWebApplicationContext()获取WebApplicationContext
方法五:实现接口ApplicationContextAware
- 说明:实现该接口的setApplicationContext(ApplicationContext context)方法,并保存ApplicationContext 对象。
- Spring初始化时,会通过该方法将ApplicationContext对象注入。
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
28public class SpringContextUtil implements ApplicationContextAware {
// Spring应用上下文环境
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的回调方法,设置上下文环境
*
* @param applicationContext
*/
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* @return ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取对象
*
* @param name
* @return Object
* @throws BeansException
*/
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
}方法六:通过Spring提供的ContextLoader
- 最后提供一种不依赖于servlet,不需要注入的方式。
- 但是需要注意一点,在服务器启动时,Spring容器初始化时,不能通过以下方法获取Spring 容器,细节可以查看spring源码org.springframework.web.context.ContextLoader。
1
2WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
wac.getBean(beanID);