13-Spring(1)
框架:具有约束性的去支撑我们完成各种功能的半成品的项目
1. 简述
- Spring 是一个 IOC(DI) 和 AOP 容器框架。
- 为简化企业级开发而生,使用 Spring,JavaBean 就可以实现很多以前要靠 EJB 才能实现的功能。同样的功能,在 EJB 中要通过繁琐的配置和复杂的代码才能够实现,而在 Spring 中却非常的优雅和简洁。
- Spring 的优良特性:
- 非侵入式:基于 Spring 开发的应用中的对象可以不依赖于 Spring 的 API
- 依赖注入:DI(Dependency Injection),反转控制(IOC) 最经典的实现 // 思想[IOC]和实现[DI]的关系
- 面向切面编程:AOP(Aspect Oriented Programming),AOP 是为了对 OOP 进行补充
- 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象
- 一站式: 在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表述层的 SpringMVC 和持久层的 Spring JDBC)
- Spring 模块
2. 搭建 Spring 运行时环境
2.1 STS 配置
2.2 导包、创建配置文件
- 加入 jar 包
- 在 STS 中通过如下步骤创建 Spring 的配置文件
- [项目]右键 → New → Source Folder → 取名[conf]
- [conf]右键 → New → Spring Bean Configuration File → 取名[applicationContext.xml]
2.3 HelloWorld
-
创建 Person 类
public class Person { private Integer id; private String name; public Person() {} // getter and setter // toString }
-
配置到 applicationContext.xml 中
<?xml version="1.0" encoding="UTF-8"?> <!-- 使用 <bean> 定义一个由 IOC 容器创建的对象 --> <!-- class 属性指定用于创建 bean 的全类名 --> <!-- id 属性指定用于引用 bean 实例的唯一标识 --> <beans ...> <bean id="person" class="cn.edu.nuist.spring.bean.Person"> <!-- 使用 <property> 为 bean 的属性赋值 --> <property name="id" value="1101"></property> <property name="name" value="TREE"></property> </bean> </beans>
-
测试
@Test public void test() { // [1] 初始化容器 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); // [2.1] 根据 id 属性值获取 bean 实例对象 Person p1 = (Person) ac.getBean("person"); // [2.2] 根据 class 属性值获取 bean 实例对象 Person p2 = ac.getBean(Person.class); // [2.3] 根据 id & class 属性值获取 bean 实例对象 Person p3 = ac.getBean("person", Person.class); // [3] 打印 bean System.out.println(p1); System.out.println(p2); System.out.println(p3); }
-
Tips
- 当使用
T getBean(Class<T>)
方式获取对象实例时,要求 applicationContext.xml 中管理的同一个类型的<bean>
必须唯一;若同时配置多个,则在获取时会抛出NoUniqueBeanDefinitionException
。 - getBean 实质上就是使用反射创建的对象,所以配置的
<bean>
所对应的 Java 类必须要有空参构造器;否则会抛出NoSuchMethodException: cn.edu.nuist.spring.mod.Person.<init>()
。
- 当使用
3. IoC 容器
3.1 IoC:反转控制
- 在应用程序中的组件需要获取资源时,传统的方式是组件主动地从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本的同时也降低了开发效率。
- 反转控制(IoC,Inversion of Control) 的思想完全颠覆了应用程序组件获取资源的传统方式:反转了获取方向 —— 改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器时如何创建资源对象的,只需要提供接受资源的方式即可,极大地降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
- 举例
- 传统方式(主动式):我想吃饭 → 买菜做饭 // 要什么资源手动创建即可
- 反转控制(被动式):我想吃饭 → 饭来张口 // 由容器主动将资源推动给需要的组件
3.2 DI:依赖注入
- IoC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入("注入"即"赋值",底层利用反射)。相对于 IoC 而言,这种表述更直接。
- 【总结】 IoC 是一种反转控制的思想,而 DI 是对 IoC 的一种具体实现。
3.3 IoC 容器在 Spring 中的实现
- 前提:Spring 有 IoC 思想,IoC 思想必须基于 IoC 容器来完成,而 {IoC 容器} 在最底层实质上就是一个对象工厂
- 在通过 {IoC 容器} 读取 Bean 的实例之前,需要先将 {IoC 容器} 本身初始化
- 容器的结构图
- Spring 提供了 {IOC 容器} 的两种实现方式
- BeanFactory: {IOC 容器} 的基本实现,是 Spring 内部的基础设置,是面向 Spring 本身的,不是提供给开发人员使用的
- ApplicationContext:是 BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都是用 ApplicationContext 而不是底层的 BeanFactory
- ConfigurableApplicationContext 是 ApplicationContext 的子接口,包含一些扩展方法,如:
refresh()
和close()
让 ApplicationContext 具有启动、关闭和刷新上下文的能力- ClassPathXmlApplicationContext 对应类路径下的 XML 格式的配置文件(相对路径)
- FileSystemXmlApplicationContext 对应文件系统的 XML 格式的配置文件(绝对路径)
- WebApplicationContext 专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作
4. Bean 的属性配置(DI)
4.1 单一属性赋值
4.1.1 依赖注入的方式
4.1.2 p 名称空间
为了简化 XML 文件的配置,越来越多的 XML 文件采用属性而非子元素配置信息。Spring 从 2.5 版本开始引入了一个新的 p 命名空间,可以通过 <bean>
元素属性的方式配置 Bean的属性。
使用 p 命名空间后,基于 XML 的配置方式将进一步简化。
<!-- p命名空间 -->
<bean id="sp" class="cn.edu.nuist.spring.di.Student"
p:id="1212" p:age="21" p:name="TREE" p:sex="男" p:score="60" p:teacher-ref="teacher"
/>
4.1.3 可以使用哪些值
- 字面值
- 可以使用字符串表示的值,通过 value 属性或 value 子节点的方式指定
- 基本数据类型及其封装类、String 等类型都可以采取字面值注入的方式
- 若字面值中包含特殊字符,可以使用
<![CDATA[]]>
把字面值包裹起来
- null 值
- 给 bean 的级联属性赋值
<!-- 级联属性赋值:属性.属性 --> <bean id="s4" class="cn.edu.nuist.spring.di.Student"> <property name="id" value="1234"></property> <property name="name" value="1101"></property> <property name="age" value="22"></property> <!-- 赋值为 null --> <property name="sex"><null/></property> <property name="score" value="100"></property> <property name="teacher" ref="teachar"></property> <property name="teacher.tid" value="9876"></property> <property name="teacher.tname" value="成秀敏"></property> </bean>
- 引用外部已声明的 bean(ref 属性值为配置文件中某一个 bean 的 id)
<bean id="s5" class="cn.edu.nuist.spring.di.Student"> <constructor-arg name="id" value="1101"></constructor-arg> <constructor-arg name="name" value="LY"></constructor-arg> <constructor-arg name="age" value="15"></constructor-arg> <constructor-arg name="sex" value="男"></constructor-arg> <constructor-arg name="score" value="60"></constructor-arg> <constructor-arg name="teacher" ref="teachar"></constructor-arg> </bean> <bean id="teachar" class="cn.edu.nuist.spring.di.Teacher"> <property name="tid" value="1"></property> <property name="tname" value="周星智"></property> </bean>
- 内部 bean
- 当 bean 实例仅仅给一个特定的属性使用时,可以将其声明为"内部bean"。内部 bean 声明直接包含在
<property>
或<constructor-arg>
元素里,不需要设置任何 id 属性或 name 属性。 - 内部 bean 不能使用在任何其他地方
- 举例
<bean id="s6" class="cn.edu.nuist.spring.di.Student"> <property name="id" value="23333"></property> <property name="name" value="TREE"></property> <property name="age" value="31"></property> <property name="sex" value="女"></property> <property name="score" value="100"></property> <property name="teacher"> <!-- 内部bean:定义在 bean 内部的 bean,只能在当前 bean 中使用 --> <bean id="innerTeacher" class="cn.edu.nuist.spring.di.Teacher"> <property name="tid" value="22"></property> <property name="tname" value="白世珠"></property> </bean> </property> </bean>
- 当 bean 实例仅仅给一个特定的属性使用时,可以将其声明为"内部bean"。内部 bean 声明直接包含在
4.2 集合属性赋值
在 Spring 中可以通过一组内置的 XML 标签来配置集合属性,例如:<list>
,<set>
或 <map>
。
4.2.1 数组和 List
配置 java.util.List
类型的属性,需要指定 <list>
标签,在标签里包含一些元素。
这些元素可以通过 <value>
指定简单的常量值;通过 <ref>
指定对其他 bean 的引用;通过 <bean>
指定内置 bean 定义;通过 <null/>
指定空元素;甚至可以内嵌其他集合。
数组的定义可以用 <list> | <array>
元素来赋值。
配置 java.util.Set
需要使用 <set>
标签,定义的方法和 List 一样。
<bean id="t1" class="cn.edu.nuist.spring.di.Teacher">
<property name="tid" value="0001"></property>
<property name="tname" value="卡卡西"></property>
<property name="cls">
<list>
<value>a</value>
<value>b</value>
<value>c</value>
</list>
</property>
</bean>
<bean id="t2" class="cn.edu.nuist.spring.di.Teacher">
<property name="tid" value="10001"></property>
<property name="tname" value="卡卡西"></property>
<property name="stuList">
<list>
<ref bean="s1"/>
<ref bean="s2"/>
<ref bean="s3"/>
</list>
</property>
</bean>
4.2.2 Map
配置 java.util.Map
类型的属性,需要指定 <map>
标签。
因为键和值的类型没有限制,所以可以自由地为它们指定 <value>
、<ref>
、<bean>
或 <null/>
元素。
可以将 Map 的键和值作为 <entry>
的属性定义:简单常量使用 key 和 value 定义;bean 通过 key-ref 和 value-ref 属性定义。
<bean id="t3" class="cn.edu.nuist.spring.di.Teacher">
<property name="tid" value="10003"></property>
<property name="tname" value="卡卡西"></property>
<property name="bossMap">
<map>
<entry>
<key>
<value>1000</value>
</key>
<value>张兆金</value>
</entry>
<entry key="1001" value="王利永"></entry>
<entry key-ref="..." value-ref="..."></entry>
</map>
</property>
</bean>
4.2.3 集合类型 bean
如果只能将集合对象配置在某个 bean 内部,则这个集合的配置将不能重用。我们需要将集合 bean 的配置拿到外面,供其他 bean 使用。
配置集合类型的 bean 需要引用 util 名称空间,如:<util:list>
,<util:map>
。
<bean id="t4" class="cn.edu.nuist.spring.di.Teacher">
<property name="tid" value="10004"></property>
<property name="tname" value="Finch"></property>
<property name="stuList" ref="stuList"></property>
</bean>
<!-- 创建一个表示集合的bean -->
<util:list id="stuList">
<ref bean="s4"/>
<ref bean="s5"/>
<ref bean="s6"/>
</util:list>
<util:map id="bossMap">
<entry key="key1" value="value1"></entry>
<entry key="key2" value="value2"></entry>
</util:map>
5. FactoryBean
Spring 中有两种类的的 bean,一种是普通 bean,另一种是工厂 bean,即 FactoryBean。
工厂 bean 跟普通 bean 不同,getBean 返回的对象不是指定类的一个实例,而是该工厂 bean 的 getObject()
所返回的对象。
工厂 bean 必须实现 org.springframework.beans.factory.FactoryBean<T>
。
public class MyFactory implements FactoryBean<Car>{
@Override
public Car getObject() throws Exception {
Car car = new Car();
car.setBrand("梅赛德斯奔驰");
car.setPrice(800000.0);
return car;
}
@Override
public Class<?> getObjectType() {
return Car.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
<!-- ac.getBean(carFactory) 返回的是这个工厂所创建的对象,而不是工厂对象 -->
<bean id="carFactory" class="cn.edu.nuist.spring.factorybean.MyFactory"></bean>
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("factory-bean.xml");
Object obj1 = ac.getBean("carFactory");
Object obj2 = ac.getBean("carFactory");
System.out.println(obj1 == obj2); // false
}
6. 作用域和生命周期
6.1 bean 的作用域
在 Spring 中,可以在 <bean>
元素的 scope 属性里设置 bean 的作用域,以决定这个 bean 是单例还是多实例的。
默认情况下,Spring 只为每一个在 IOC 容器里声明的 bean 创建唯一一个实例,整个 IOC 容器范围内都能共享该实例:后续所有 getBean()
的调用和 bean 引用都将给你返回这个唯一的 bean 实例。该作用域被称为:singleton,它是所有 bean 的默认作用域。
- singleton:在 SpringIOC 容器中仅存在一个 bean 实例,bean 以单实例的方式存在
- prototype:每次调用
getBean()
时都会返回一个新的实例 - request:每次 HTTP 请求都会创建一个新的 bean,该作用域名仅适用于 WebApplicationContext 环境
- session:同一个 HTTP Session 共享一个 bean,不同的 HTTP Session 使用不同的 bean。该作用域也仅适用于 WebApplicationContext 环境
当 bean 的作用域为单例(饿汉式)时,Spring 会在 IOC 容器对象创建时就创建 bean 的对象实例。而当 bean 的作用域为 prototype 时,IOC 容器在获取 bean 的实例时创建 bean 的实例对象。
6.2 bean 的生命周期
Spring IOC 容器可以管理 bean 的生命周期,Spring 允许在 bean 生命周期内特定的时间点执行指定的任务。
Spring IOC 容器对 bean 的生命周期进行管理的过程:
- 通过构造器或工厂方法创建 bean 实例
- 为 bean 的属性设置(注入) 值和对其他 bean 的引用
- 调用 bean 的初始化方法
- 使用 bean
- 当容器关闭时,调用 bean 的销毁方法
在配置 bean 时,通过 init-method 和 destory-method 属性为 bean 指定初始化和销毁方法。
6.3 bean 的后置处理器
- 需要实现接口:BeanPostProcessor
Object postProcessBeforeInitialization(Object bean, String beanName) Object postProcessAfterInitialization(Object bean, String beanName)
- 在初始化方法被调用前后,Spring 将把每个 bean 实例分别传递给上述接口 2 个方法形参。接口中的两个方法都要将传入的 bean 返回,而不能返回 null,如果返回的是 null 那么我们通过
getBean()
将得不到目标对象。 - 在 xml 中配置(对所有 bean 有效):
<bean class="cn.edu.nuist.ioc.life.AfterHandler" />
- 添加 bean 后置处理器后 bean 的生命周期:
- 通过构造器或工厂方法创建 bean 实例
- 为 bean 的属性设置值和对其他 bean 的引用
- 将 bean 实例传递给 bean 后置处理器的 postProcessBeforeInitialization()
- 调用 bean 的初始化方法
- 将 bean 实例传递给 bean 后置处理器的 postProcessAfterInitialization()
- bean 可以使用了
- 当容器关闭时,调用 bean 的销毁方法
7. 引用外部属性文件
当 bean 的配置信息逐渐增多时,查找和修改一些 bean 的配置信息就会变得愈加困难。这时可以将一部分信息提取到 bean 配置文件的外部,以 properties 格式的属性文件保存起来,同时在 bean 的配置文件中引入 properties 属性文件中的内容,从而实现一部分属性值在发生变化时进修改 properties 属性文件即可。这种技术多用于连接数据库的基本信息的配置。
db.properties
# k = v
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///test
jdbc.username=root
jdbc.password=123456
datasource.xml
<!-- [plan1] 加载资源文件
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="db.properties"></property>
</bean>
-->
<!--
[plan2] 引入 context 名称空间,使用 context 下的标签
location 指定 properties 属性文件的位置
classpath:xxx 表示属性文件位于类路径下
-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 从properties属性文件中引入属性值,格式:${ key } -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
8. 自动装配
- 概念
- 手动装配:以 value 或 ref 的方法明确指定属性值都是手动装配
- 自动装配:根据指定的装配规则,不需要明确指定,Spring 自动将匹配的(非字面量)属性值注入 bean 中
- 装配模式:当设置 bean 标签 的 autowire 属性后,Spring 将会根据属性值所对应的策略自动为该 bean 中所有的非字面量属性赋值
autowire="byType"
:根据类型自动装配;将类型匹配的 bean 作为属性注入到另一个 bean 中- 若 IOC 容器中有多个与目标 bean 类型一致的 bean,Spring 将无法判定哪个 bean 最合适该属性继而抛出异常:NoUniqueBeanDefinitionException
- 若属性类型是 接口/父类 类型,可以将 实现类/子类 bean 赋值该属性(兼容性)
autowire="byName"
:根据名称自动装配;必须将目标 bean 的名称和属性名设置的完全相同autowire="constructor"
:通过构造器自动装配;当 bean 中存在多个构造器时,此种自动装配方式将会很复杂,不推荐使用
- 选用建议:相对于使用注解的方式实现的自动装配,在 XML 文档中进行的自动装配略显笨拙,在项目中更多的使用注解方式实现
9. 通过注解配置 bean
相对于 XML 方式而言,通过注解的方式配置 bean 更加简洁和优雅,而且和 MVC 组件化开发的理念十分契合,是开发中常用的使用方式。
组件:就是 Spring 中管理的 bean。会自动在 Spring 容器中生成相应的 bean,这些 bean 的 id 会以类的"首字母小写,其余不变"作为 id 值
9.1 使用注解标识组件
- 普通组件 @Component:标识一个受 Spring IOC 容器管理的组件
- 持久化层组件 @Repository:标识一个受 Spring IOC 容器管理的持久化层组件
- 业务逻辑层组件 @ Service:标识一个受 Spring IOC 容器管理的业务逻辑层组件
- 表述层控制器组件 @Controller:标识一个受 SpringIOC 容器管理的表述层控制器组件
9.2 扫描组件
组件被上述注解标识后还需要通过 Spring 进行扫描才能够侦测到。
<context:component-scan base-package="包全限定名[, ...]">
- 指定被扫描的 package,base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包及其子包中的所有类
- 如果这个类加了注解,则会将该类作为 Spring 的组件进行加载
<context:include-filter>
- 在设定的包结构下,再次通过注解或类型具体包含到某个或某几个类
- 通常需要与 use-default-filters 属性配合使用才能够达到“仅包含某些组件”这样的效果。即:通过将 use-default-filters 属性设置为 false,禁用默认过滤器(扫描 base-package 下所有类),然后扫描的就只是
<context:include-filter>
中的规则指定的组件了。
<context:exclude-filter>
- 在设定的包结构下,扫描所有类的同时,排除那些通过注解或类型相关的某个或某几个类
- 注意:use-default-filters 属性设置为 true,意味着所有的都扫描,除了那些排除在外的
补充:
- 过滤表达式
- 组件注解的 value 属性值将作为自动生成的 bean 的 id。若不手动设置,则容器默认将扫描到的组件 id 设置成类名的"首字母大写,其余不变"
<context:component-scan>
下可以拥有若干个<context:include-filter>
或 若干个<context:exclude-filter>
子节点,但不能同时出现- 基于注解的组件化管理:@Component,@Controller,@Service,@Repository 这 4 个注解功能完全相同,不过在实际开发中,要在实现不同功能的类上加上相应的注解
9.3 组件装配
9.3.1 概述
【需求】Controller 组件中往往需要用到 Service 组件的实例,Service 组件中往往需要用到 Repository 组件的实例。Spring 可以通过注解的方式帮我们实现属性的装配。
@Controller
public class UserController {
@Autowired
private UserService userService;
public void addUser() {
userService.addUser();
}
}
【实现依据】在指定要扫描的包时,<context:component-scan>
元素会自动注册一个 bean 的后置处理器: AutowiredAnnotationBeanPostProcessor 的实例。该后置处理器可以自动装配标记了 @Autowired、@Resource 或 @Inject 注解的属性
9.3.2 @Autowired注解
- 注解底层是通过 byType 的方式实现自动装配的
- 构造器、普通字段 (即使是 !public)、一切具有参数的方法都可以应用 @Autowired 注解
- 默认情况下,所有使用 @Autowired 注解的属性都需要被设置。当 Spring 找不到匹配的 bean 装配属性时,会抛出异常
- 若某一属性允许不被设置,可以设置 @Autowired 注解的 required 属性为 false(默认为 true)
- @Autowired 一般用在变量上,Spring 容器会自动注入值。当用在方法上,Spring 容器会在类加载后自动注入这个方法的参数,并执行一遍方法
- 默认情况下,当 IOC 容器里存在多个类型兼容的 bean 时
- Spring 会尝试匹配 bean 的 id 值是否与变量名相同(byName 方式),如果相同则进行装配。如果 bean 的 id 值不相同,通过类型的自动装配将无法工作。
- 可以在 @Qualifier 注解里提供 bean 的名称(在方法的形参上标注 @Qualifiter 注解以指定注入 bean 的名称)
@Autowired public void putUserDao(@Qualifier("userDaoMybatisImpl") UserDao userDao) { this.userDao = userDao; }
补充:
- @Autowired 注解也可以应用在数组类型的属性上,此时 Spring 将会把所有匹配的 bean 进行自动装配。
- @Autowired 注解也可以应用在集合属性上,此时 Spring 读取该集合的类型信息,然后自动装配所有与之兼容的 bean。
- @Autowired 注解用在 java.util.Map 上时,若该 Map 的键值为 String,那么 Spring 将自动装配与值类型兼容的 bean 作为值,并以 bean 的 id 值作为键。