Spring4
一、 依赖注入的方式
Spring 支持 3 种依赖注入的方式:属性注入(即setter注入)、构造器注入、工程方法注入
a) 属性注入
属性注入即通过 setter 方法注入Bean 的属性值或依赖的对象
属性注入使用 <property> 元素, 使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值
属性注入是实际应用中最常用的注入方式
b) 构造器注入
通过构造方法注入Bean 的属性值或依赖的对象,它保证了 Bean 实例在实例化后就可以使用。
构造器注入在 <constructor-arg> 元素里声明属性, <constructor-arg> 中没有 name 属性
c) 工厂方法注入(很少使用,不推荐)
二、 基于 XML 文件的方式
1、 字面值
字面值:可用字符串表示的值,可以通过 <value> 元素标签或 value 属性进行注入。
基本数据类型及其封装类、String 等类型都可以采取字面值注入的方式。若字面值中包含特殊字符,可以使用 <![CDATA[]]> 把字面值包裹起来。
2、 引用其它 Bean
组成应用程序的 Bean 经常需要相互协作以完成应用程序的功能. 要使 Bean 能够相互访问, 就必须在 Bean 配置文件中指定对 Bean 的引用。在 Bean 的配置文件中, 可以通过 <ref> 元素或 ref 属性为 Bean 的属性或构造器参数指定对 Bean 的引用. 也可以在属性或构造器里包含 Bean 的声明, 这样的 Bean 称为内部 Bean
3、 内部 Bean
当 Bean 实例仅仅给一个特定的属性使用时, 可以将其声明为内部 Bean. 内部 Bean 声明直接包含在 <property> 或 <constructor-arg> 元素里, 不需要设置任何 id 或 name 属性。内部 Bean 不能使用在任何其他地方
4、 注入参数详解:null 值和级联属性
可以使用专用的 <null/> 元素标签为 Bean 的字符串或其它对象类型的属性注入 null 值。和 Struts、Hiberante 等框架一样,Spring 支持级联属性的配置。
5、 集合属性
• 在 Spring中可以通过一组内置的 xml 标签(例如: <list>, <set> 或 <map>) 来配置集合属性。
• 配置 java.util.List 类型的属性, 需要指定 <list> 标签, 在标签里包含一些元素. 这些标签可以通过 <value> 指定简单的常量值, 通过 <ref> 指定对其他 Bean 的引用.
• 通过<bean> 指定内置 Bean 定义. 通过 <null/> 指定空元素. 甚至可以内嵌其他集合。数组的定义和 List 一样, 都使用 <list>。
• 配置 java.util.Set 需要使用 <set> 标签, 定义元素的方法与 List 一样.
• Java.util.Map 通过 <map> 标签定义, <map> 标签里可以使用多个 <entry> 作为子标签. 每个条目包含一个键和一个值.
• 必须在 <key> 标签里定义键
• 因为键和值的类型没有限制, 所以可以自由地为它们指定 <value>, <ref>, <bean> 或 <null> 元素.
• 可以将 Map 的键和值作为 <entry> 的属性定义: 简单常量使用 key 和 value 来定义; Bean 引用通过 key-ref 和 value-ref 属性定义
• 使用 <props> 定义 java.util.Properties, 该标签使用多个 <prop> 作为子标签. 每个 <prop> 标签必须定义 key 属性.
6、 使用 utility scheme 定义集合
• 使用基本的集合标签定义集合时, 不能将集合作为独立的 Bean 定义, 导致其他 Bean 无法引用该集合, 所以无法在不同 Bean 之间共享集合.
• 可以使用 util schema 里的集合标签定义独立的集合 Bean. 需要注意的是, 必须在 <beans> 根元素里添加 util schema 定义
7、 使用 p 命名空间
• 为了简化 XML 文件的配置,越来越多的 XML 文件采用属性而非子元素配置信息。
• Spring 从 2.5 版本开始引入了一个新的 p 命名空间,可以通过 <bean> 元素属性的方式配置 Bean 的属性。
• 使用 p 命名空间后,基于 XML 的配置方式将进一步简化
8、XML 配置里的 Bean 自动装配
• Spring IOC 容器可以自动装配 Bean. 需要做的仅仅是在 <bean> 的 autowire 属性里指定自动装配的模式
• byType(根据类型自动装配): 若 IOC 容器中有多个与目标 Bean 类型一致的 Bean. 在这种情况下, Spring 将无法判定哪个 Bean 最合适该属性, 所以不能执行自动装配.
• byName(根据名称自动装配): 必须将目标 Bean 的名称和属性名设置的完全相同.
• constructor(通过构造器自动装配): 当 Bean 中存在多个构造器时, 此种自动装配方式将会很复杂. 不推荐使用
9、XML 配置里的 Bean 自动装配的缺点
• 在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性. 然而, 若只希望装配个别属性时, autowire 属性就不够灵活了.
• autowire 属性要么根据类型自动装配, 要么根据名称自动装配, 不能两者兼而有之.
• 一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些
10、继承 Bean 配置
• Spring 允许继承 bean 的配置, 被继承的 bean 称为父 bean. 继承这个父 Bean 的 Bean 称为子 Bean
• 子 Bean 从父 Bean 中继承配置, 包括 Bean 的属性配置
• 子 Bean 也可以覆盖从父 Bean 继承过来的配置
• 父 Bean 可以作为配置模板, 也可以作为 Bean 实例. 若只想把父 Bean 作为模板, 可以设置 <bean> 的abstract 属性为 true, 这样 Spring 将不会实例化这个 Bean
• 并不是 <bean> 元素里的所有属性都会被继承. 比如: autowire, abstract 等.
• 也可以忽略父 Bean 的 class 属性, 让子 Bean 指定自己的类, 而共享相同的属性配置. 但此时 abstract 必须设为 true
11、依赖 Bean 配置
• Spring 允许用户通过 depends-on 属性设定 Bean 前置依赖的Bean,前置依赖的 Bean 会在本 Bean 实例化之前创建好
• 如果前置依赖于多个 Bean,则可以通过逗号,空格或的方式配置 Bean 的名称
12、Bean 的作用域
• 在 Spring 中, 可以在 <bean> 元素的 scope 属性里设置 Bean 的作用域.
• 默认情况下, Spring 只为每个在 IOC 容器里声明的 Bean 创建唯一一个实例, 整个 IOC 容器范围内都能共享该实例:所有后续的 getBean() 调用和 Bean 引用都将返回这个唯一的 Bean 实例.该作用域被称为 singleton, 它是所有 Bean 的默认作用域.
13、使用外部属性文件
• 在配置文件里配置 Bean 时, 有时需要在 Bean 的配置里混入系统部署的细节信息(例如: 文件路径, 数据源配置信息等). 而这些部署细节实际上需要和 Bean 配置相分离
• Spring 提供了一个 PropertyPlaceholderConfigurer 的 BeanFactory 后置处理器, 这个处理器允许用户将 Bean 配置的部分内容外移到属性文件中. 可以在 Bean 配置文件里使用形式为 ${var} 的变量, PropertyPlaceholderConfigurer 从属性文件里加载属性, 并使用这些属性来替换变量.
• Spring 还允许在属性文件中使用 ${propName},以实现属性之间的相互引用。
14、Spring表达式语言:SpEL
• Spring 表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言。
• 语法类似于 EL:SpEL 使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是 SpEL
• SpEL 为 bean 的属性进行动态赋值提供了便利
• 通过 SpEL 可以实现:
– 通过 bean 的 id 对 bean 进行引用
– 调用方法以及引用对象中的属性
– 计算表达式的值
– 正则表达式的匹配
15、SpEL:字面量
• 字面量的表示:
– 整数:<property name="count" value="#{5}"/>
– 小数:<property name="frequency" value="#{89.7}"/>
– 科学计数法:<property name="capacity" value="#{1e4}"/>
– String可以使用单引号或者双引号作为字符串的定界符号:<property name=“name” value="#{'Chuck'}"/> 或 <property name='name' value='#{"Chuck"}'/>
– Boolean:<property name="enabled" value="#{false}"/>
16、SpEL:引用 Bean、属性和方法(1)
• 引用其他对象:
•
• 引用其他对象的属性
•
• 调用其他方法,还可以链式操作
17、SpEL支持的运算符号(1)
• 算数运算符:+, -, *, /, %, ^:
• 加号还可以用作字符串连接:
• 比较运算符: <, >, ==, <=, >=, lt, gt, eq, le, ge
18、SpEL支持的运算符号(2)
• 逻辑运算符号: and, or, not, |
• if-else 运算符:?: (ternary), ?: (Elvis)
• if-else 的变体
• 正则表达式:matches
19、SpEL:引用 Bean、属性和方法(2)
• 调用静态方法或静态属性:通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性:
20、IOC 容器中 Bean 的生命周期方法
• Spring IOC 容器可以管理 Bean 的生命周期, Spring 允许在 Bean 生命周期的特定点执行定制的任务.
• Spring IOC 容器对 Bean 的生命周期进行管理的过程:
– 通过构造器或工厂方法创建 Bean 实例
– 为 Bean 的属性设置值和对其他 Bean 的引用
– 调用 Bean 的初始化方法
– Bean 可以使用了
– 当容器关闭时, 调用 Bean 的销毁方法
• 在 Bean 的声明里设置 init-method 和 destroy-method 属性, 为 Bean 指定初始化和销毁方法.
21、创建 Bean 后置处理器
• Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理.
• Bean 后置处理器对 IOC 容器里的所有 Bean 实例逐一处理, 而非单一实例. 其典型应用是: 检查 Bean 属性的正确性或根据特定的标准更改 Bean 的属性.
• 对Bean 后置处理器而言, 需要实现 接口. 在初始化方法被调用前后, Spring 将把每个 Bean 实例分别传递给上述接口的以下两个方法:
22、添加 Bean 后置处理器后 Bean 的生命周期
• Spring IOC 容器对 Bean 的生命周期进行管理的过程:
– 通过构造器或工厂方法创建 Bean 实例
– 为 Bean 的属性设置值和对其他 Bean 的引用
– 将 Bean 实例传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法
– 调用 Bean 的初始化方法
– 将 Bean 实例传递给 Bean 后置处理器的 postProcessAfterInitialization方法
– Bean 可以使用了
– 当容器关闭时, 调用 Bean 的销毁方法
23、通过调用静态工厂方法创建 Bean
• 调用静态工厂方法创建 Bean是将对象创建的过程封装到静态方法中. 当客户端需要对象时, 只需要简单地调用静态方法, 而不同关心创建对象的细节.
• 要声明通过静态方法创建的 Bean, 需要在 Bean 的 class 属性里指定拥有该工厂的方法的类, 同时在 factory-method 属性里指定工厂方法的名称. 最后, 使用 <constrctor-arg> 元素为该方法传递方法参数.
24、通过调用实例工厂方法创建 Bean
• 实例工厂方法: 将对象的创建过程封装到另外一个对象实例的方法里. 当客户端需要请求对象时, 只需要简单的调用该实例方法而不需要关心对象的创建细节.
• 要声明通过实例工厂方法创建的 Bean
– 在 bean 的 factory-bean 属性里指定拥有该工厂方法的 Bean
– 在 factory-method 属性里指定该工厂方法的名称
– 使用 construtor-arg 元素为工厂方法传递方法参数
25、实现 FactoryBean 接口在 Spring IOC 容器中配置 Bean
• Spring 中有两种类型的 Bean, 一种是普通Bean, 另一种是工厂Bean, 即FactoryBean.
• 工厂 Bean 跟普通Bean不同, 其返回的对象不是指定类的一个实例, 其返回的是该工厂 Bean 的 getObject 方法所返回的对象
以上知识点代码实例:
实例1:
Bean.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 配置一个 bean -->
<bean id="helloWorld" class="com.atguigu.spring.helloworld.HelloWorld">
<!-- 为属性赋值 -->
<property name="user" value="Jerry"></property>
</bean>
<!-- 配置一个 bean -->
<bean id="helloWorld2" class="com.atguigu.spring.helloworld.HelloWorld">
<!-- 为属性赋值 -->
<!-- 通过属性注入: 通过 setter 方法注入属性值 -->
<property name="user" value="Tom"></property>
</bean>
<!-- 通过构造器注入属性值 -->
<bean id="helloWorld3" class="com.atguigu.spring.helloworld.HelloWorld">
<!-- 要求: 在 Bean 中必须有对应的构造器. -->
<constructor-arg value="Mike"></constructor-arg>
</bean>
<!-- 若一个 bean 有多个构造器, 如何通过构造器来为 bean 的属性赋值 -->
<!-- 可以根据 index 和 value 进行更加精确的定位. (了解) -->
<bean id="car" class="com.atguigu.spring.helloworld.Car">
<constructor-arg value="KUGA" index="1"></constructor-arg>
<constructor-arg value="ChangAnFord" index="0"></constructor-arg>
<constructor-arg value="250000" type="float"></constructor-arg>
</bean>
<bean id="car2" class="com.atguigu.spring.helloworld.Car">
<constructor-arg value="ChangAnMazda"></constructor-arg>
<!-- 若字面值中包含特殊字符, 则可以使用 DCDATA 来进行赋值. (了解) -->
<constructor-arg>
<value><![CDATA[<ATARZA>]]></value>
</constructor-arg>
<constructor-arg value="180" type="int"></constructor-arg>
</bean>
<!-- 配置 bean -->
<bean id="dao5" class="com.atguigu.spring.ref.Dao"></bean>
<bean id="service" class="com.atguigu.spring.ref.Service">
<!-- 通过 ref 属性值指定当前属性指向哪一个 bean! -->
<property name="dao" ref="dao5"></property>
</bean>
<!-- 声明使用内部 bean -->
<bean id="service2" class="com.atguigu.spring.ref.Service">
<property name="dao">
<!-- 内部 bean, 类似于匿名内部类对象. 不能被外部的 bean 来引用, 也没有必要设置 id 属性 -->
<bean class="com.atguigu.spring.ref.Dao">
<property name="dataSource" value="c3p0"></property>
</bean>
</property>
</bean>
<bean id="action" class="com.atguigu.spring.ref.Action">
<property name="service" ref="service2"></property>
<!-- 设置级联属性(了解) -->
<property name="service.dao.dataSource" value="DBCP2"></property>
</bean>
<bean id="dao2" class="com.atguigu.spring.ref.Dao">
<!-- 为 Dao 的 dataSource 属性赋值为 null, 若某一个 bean 的属性值不是 null, 使用时需要为其设置为 null(了解) -->
<property name="dataSource"><null/></property>
</bean>
<!-- 装配集合属性 -->
<bean id="user" class="com.atguigu.spring.helloworld.User">
<property name="userName" value="Jack"></property>
<property name="cars">
<!-- 使用 list 元素来装配集合属性 -->
<list>
<ref bean="car"/>
<ref bean="car2"/>
</list>
</property>
</bean>
<!-- 声明集合类型的 bean -->
<util:list id="cars">
<ref bean="car"/>
<ref bean="car2"/>
</util:list>
<bean id="user2" class="com.atguigu.spring.helloworld.User">
<property name="userName" value="Rose"></property>
<!-- 引用外部声明的 list -->
<property name="cars" ref="cars"></property>
</bean>
<bean id="user3" class="com.atguigu.spring.helloworld.User"
p:cars-ref="cars" p:userName="Titannic"></bean>
<!-- bean 的配置能够继承吗 ? 使用 parent 来完成继承 -->
<bean id="user4" parent="user" p:userName="Bob"></bean>
<bean id="user6" parent="user" p:userName="维多利亚"></bean>
<!-- 测试 depents-on -->
<bean id="user5" parent="user" p:userName="Backham" depends-on="user6"></bean>
</beans>
==============================================================================
Java文件:
package com.atguigu.spring.helloworld;
import java.io.PrintStream;
public class HelloWorld
{
private String user;
public HelloWorld()
{
System.out.println("HelloWorld's constructor...");
}
public void setUser(String user) {
System.out.println("setUser:" + user);
this.user = user;
}
public HelloWorld(String user) {
this.user = user;
}
public void hello() {
System.out.println("Hello: " + this.user);
}
}
=====================================================================
package com.atguigu.spring.helloworld;
public class Car
{
private String company;
private String brand;
private int maxSpeed;
private float price;
public Car(String company, String brand, float price)
{
this.company = company;
this.brand = brand;
this.price = price;
}
public Car(String company, String brand, int maxSpeed)
{
this.company = company;
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public Car(String company, String brand, int maxSpeed, float price)
{
this.company = company;
this.brand = brand;
this.maxSpeed = maxSpeed;
this.price = price;
}
public String toString()
{
return "Car [company=" + this.company + ", brand=" + this.brand + ", maxSpeed=" +
this.maxSpeed + ", price=" + this.price + "]";
}
}
=====================================================================
package com.atguigu.spring.helloworld;
import java.util.List;
public class User
{
private String userName;
private List<Car> cars;
private String wifeName;
public String getWifeName()
{
return this.wifeName;
}
public void setWifeName(String wifeName) {
System.out.println("setWifhName: " + wifeName);
this.wifeName = wifeName;
}
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public List<Car> getCars() {
return this.cars;
}
public void setCars(List<Car> cars) {
this.cars = cars;
}
public User() {
System.out.println("User's Construtor...");
}
public String toString()
{
return "User [userName=" + this.userName + ", cars=" + this.cars + "]";
}
public void init() {
System.out.println("init method...");
}
public void destroy() {
System.out.println("destroy method...");
}
}
=====================================================================
package com.atguigu.spring.helloworld;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
@SuppressWarnings("resource")
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
HelloWorld helloWorld = (HelloWorld)ctx.getBean("helloWorld3");
helloWorld.hello();
Car car = (Car)ctx.getBean("car");
System.out.println(car);
Car car2 = (Car)ctx.getBean("car2");
System.out.println(car2);
User user = (User)ctx.getBean("user5");
System.out.println(user);
}
}
实例2:
Bean-auto.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 自动装配: 只声明 bean, 而把 bean 之间的关系交给 IOC 容器来完成 -->
<!--
byType: 根据类型进行自动装配. 但要求 IOC 容器中只有一个类型对应的 bean, 若有多个则无法完成自动装配.
byName: 若属性名和某一个 bean 的 id 名一致, 即可完成自动装配. 若没有 id 一致的, 则无法完成自动装配
-->
<!-- 在使用 XML 配置时, 自动转配用的不多. 但在基于 注解 的配置时, 自动装配使用的较多. -->
<bean id="dao" class="com.atguigu.spring.ref.Dao">
<property name="dataSource" value="C3P0"></property>
</bean>
<!-- 默认情况下 bean 是单例的! -->
<!-- 但有的时候, bean 就不能使单例的. 例如: Struts2 的 Action 就不是单例的! 可以通过 scope 属性来指定 bean 的作用域 -->
<!--
prototype: 原型的. 每次调用 getBean 方法都会返回一个新的 bean. 且在第一次调用 getBean 方法时才创建实例
singleton: 单例的. 每次调用 getBean 方法都会返回同一个 bean. 且在 IOC 容器初始化时即创建 bean 的实例. 默认值
-->
<bean id="dao2" class="com.atguigu.spring.ref.Dao" scope="prototype"></bean>
<bean id="service" class="com.atguigu.spring.ref.Service" autowire="byName"></bean>
<bean id="action" class="com.atguigu.spring.ref.Action" autowire="byType"></bean>
<!-- 导入外部的资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 测试 SpEL: 可以为属性进行动态的赋值(了解) -->
<bean id="girl" class="com.atguigu.spring.helloworld.User">
<property name="userName" value="周迅"></property>
</bean>
<bean id="boy" class="com.atguigu.spring.helloworld.User" init-method="init" destroy-method="destroy">
<property name="userName" value="高胜远"></property>
<property name="wifeName" value="#{girl.userName}"></property>
</bean>
<!-- 配置 bean 后置处理器: 不需要配置 id 属性, IOC 容器会识别到他是一个 bean 后置处理器, 并调用其方法 -->
<bean class="com.atguigu.spring.ref.MyBeanPostProcessor"></bean>
<!-- 通过工厂方法的方式来配置 bean -->
<!-- 1. 通过静态工厂方法: 一个类中有一个静态方法, 可以返回一个类的实例(了解) -->
<!-- 在 class 中指定静态工厂方法的全类名, 在 factory-method 中指定静态工厂方法的方法名 -->
<bean id="dateFormat" class="java.text.DateFormat" factory-method="getDateInstance">
<!-- 可以通过 constructor-arg 子节点为静态工厂方法指定参数 -->
<constructor-arg value="2"></constructor-arg>
</bean>
<!-- 2. 实例工厂方法: 先需要创建工厂对象, 再调用工厂的非静态方法返回实例(了解) -->
<!-- ①. 创建工厂对应的 bean -->
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd hh:mm:ss"></constructor-arg>
</bean>
<!-- ②. 用实例工厂方法来创建 bean 实例 -->
<!-- factory-bean 指向工厂 bean, factory-method 指定工厂方法(了解) -->
<bean id="datetime" factory-bean="simpleDateFormat" factory-method="parse">
<!-- 通过 constructor-arg 执行调用工厂方法需要传入的参数 -->
<constructor-arg value="1990-12-12 12:12:12"></constructor-arg>
</bean>
<!-- 配置通过 FactroyBean 的方式来创建 bean 的实例(了解) -->
<bean id="user" class="com.atguigu.spring.ref.UserBean"></bean>
</beans>
============================================================================================
db.properties配置文件
jdbc.user=dev
jdbc.password=dev
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://192.168.0.200/SHM
jdbc.initPoolSize=5
jdbc.maxPoolSize=10
===============================================================================
Java文件
package com.atguigu.spring.ref;
import java.io.PrintStream;
public class Action
{
private Service service;
public void setService(Service service)
{
this.service = service;
}
public Service getService() {
return this.service;
}
public void execute() {
System.out.println("Action's execute...");
this.service.save();
}
}
===============================================================================
package com.atguigu.spring.ref;
import java.io.PrintStream;
public class Dao
{
private String dataSource = "dbcp";
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public void save() {
System.out.println("save by " + this.dataSource);
}
public Dao() {
System.out.println("Dao's Constructor...");
}
}
===============================================================================
package com.atguigu.spring.ref;
import java.io.PrintStream;
public class Service
{
private Dao dao;
public void setDao(Dao dao)
{
this.dao = dao;
}
public Dao getDao() {
return this.dao;
}
public void save() {
System.out.println("Service's save");
this.dao.save();
}
}
============================================================================================
package com.atguigu.spring.ref;
import com.atguigu.spring.helloworld.Car;
import com.atguigu.spring.helloworld.User;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.FactoryBean;
public class UserBean
implements FactoryBean<User>
{
public User getObject()
throws Exception
{
User user = new User();
user.setUserName("abc");
user.setWifeName("ABC");
List cars = new ArrayList();
cars.add(new Car("ShangHai", "BuiKe", 180, 300000.0F));
cars.add(new Car("ShangHai", "CRUZE", 130, 150000.0F));
user.setCars(cars);
return user;
}
public Class<?> getObjectType()
{
return User.class;
}
public boolean isSingleton()
{
return true;
}
}
============================================================================================
package com.atguigu.spring.ref;
import com.atguigu.spring.helloworld.User;
import java.io.PrintStream;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (beanName.equals("boy")) {
User user = (User) bean;
System.out.println("postProcessAfterInitialization..." + bean + ","+ beanName);
user.setUserName("?????");
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (beanName.equals("boy"))
System.out.println("postProcessBeforeInitialization..." + bean + "," + beanName);
return bean;
}
}
==================================================================================
package com.atguigu.spring.ref;
import com.atguigu.spring.helloworld.User;
import java.io.PrintStream;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.Date;
import javax.sql.DataSource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
throws SQLException
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-auto.xml");
Action action = (Action)ctx.getBean(Action.class);
action.execute();
Dao dao1 = (Dao)ctx.getBean("dao2");
Dao dao2 = (Dao)ctx.getBean("dao2");
System.out.println(dao1 == dao2);
DataSource dataSource = (DataSource)ctx.getBean("dataSource");
System.out.println(dataSource.getConnection());
User boy = (User)ctx.getBean("boy");
System.out.println(boy.getUserName() + ":" + boy.getWifeName());
DateFormat dateFormat = (DateFormat)ctx.getBean("dateFormat");
System.out.println(dateFormat.format(new Date()));
Date date = (Date)ctx.getBean("datetime");
System.out.println(date);
User user = (User)ctx.getBean("user");
System.out.println(user);
ctx.close();
}
}
三、 基于注解的方式(基于注解配置 Bean;基于注解来装配 Bean 的属性)
1、在 classpath 中扫描组件
• 组件扫描(component scanning): Spring 能够从 classpath 下自动扫描, 侦测和实例化具有特定注解的组件.
• 特定组件包括:
– @Component: 基本注解, 标识了一个受 Spring 管理的组件
– @Respository: 标识持久层组件
– @Service: 标识服务层(业务层)组件
– @Controller: 标识表现层组件
• 对于扫描到的组件, Spring 有默认的命名策略: 使用非限定类名, 第一个字母小写. 也可以在注解中通过 value 属性值标识组件的名称
• 当在组件类上使用了特定的注解之后, 还需要在 Spring 的配置文件中声明 <context:component-scan> :
– base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包里及其子包中的所有类.
– 当需要扫描多个包时, 可以使用逗号分隔.
– 如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern 属性过滤特定的类,示例:
– <context:include-filter> 子节点表示要包含的目标类
– <context:exclude-filter> 子节点表示要排除在外的目标类
– <context:component-scan> 下可以拥有若干个 <context:include-filter> 和 <context:exclude-filter> 子节点
• <context:include-filter> 和 <context:exclude-filter> 子节点支持多种类型的过滤表达式:
2、组件装配
• <context:component-scan> 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例, 该实例可以自动装配具有 @Autowired 和 @Resource 、@Inject注解的属性.
3、使用 @Autowired 自动装配 Bean
• @Autowired 注解自动装配具有兼容类型的单个 Bean属性
– 构造器, 普通字段(即使是非 public), 一切具有参数的方法都可以应用@Authwired 注解
– 默认情况下, 所有使用 @Authwired 注解的属性都需要被设置. 当 Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置 @Authwired 注解的 required 属性为 false
– 默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作. 此时可以在 @Qualifier 注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter 已指定注入 Bean 的名称
– @Authwired 注解也可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配.
– @Authwired 注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean.
– @Authwired 注解用在 java.util.Map 上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值
4、使用 @Resource 或 @Inject 自动装配 Bean
• Spring 还支持 @Resource 和 @Inject 注解,这两个注解和 @Autowired 注解的功用类似
• @Resource 注解要求提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为 Bean 的名称
• @Inject 和 @Autowired 注解一样也是按类型匹配注入的 Bean, 但没有 reqired 属性
• 建议使用 @Autowired 注解
5、泛型依赖注入
• Spring 4.x 中可以为子类注入子类对应的泛型类型的成员变量的引用
6、整合多个配置文件
• Spring 允许通过 <import> 将多个配置文件引入到一个文件中,进行配置文件的集成。这样在启动 Spring 容器时,仅需要指定这个合并好的配置文件就可以。
• import 元素的 resource 属性支持 Spring 的标准的路径资源
以上知识点代码实例:
Bean.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置自动扫描的包-->
<context:component-scan base-package="com.atguigu.spring.annotation"></context:component-scan>
</beans>
=============================================================================
Java文件
package com.atguigu.spring.annotation;
import org.springframework.stereotype.Component;
@Component
public class Article {
private String title;
private String content;
public Article(){ }
public Article(String title,String content){
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Article [title=" + title + ", content=" + content + "]";
}
}
===============================================================================
package com.atguigu.spring.annotation;
public interface ArticleDao {
public void save(String title,String content);
}
===============================================================================
package com.atguigu.spring.annotation;
import org.springframework.stereotype.Repository;
@Repository
public class ArticleDaoImpl implements ArticleDao {
@Override
public void save(String title, String content) {
// TODO Auto-generated method stub
System.out.println(new Article(title,content));
}
}
===============================================================================
package com.atguigu.spring.annotation;
import org.springframework.stereotype.Repository;
@Repository
public class ArticleDaoImpl implements ArticleDao {
@Override
public void save(String title, String content) {
// TODO Auto-generated method stub
System.out.println(new Article(title,content));
}
}
=============================================================================
package com.atguigu.spring.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ArticleService {
@Autowired
private ArticleDaoImpl articleDaoImpl;
public void save(String title,String content){
articleDaoImpl.save(title, content);
}
}
==============================================================
package com.atguigu.spring.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-annotation.xml");
UserAction userAction = (UserAction)ctx.getBean(UserAction.class);
userAction.execute();
ArticleController articleController = (ArticleController)ctx.getBean(ArticleController.class);
articleController.excute();
}
}
=======================================================================
Bean的生命周期
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--
1. 默认情况下, IOC 容器中的 bean 是单例的! 若对象是单例的, 则在创建 IOC 容器时即创建 bean 的实例, 并对 bean 的属性进行初始化.
2. 可以通过 bean 的 scope 属性来修改 bean 的作用域. 若取值为 prototype, 则 bean 为原型的: 每次向容器获取实例, 得到的都是一个新的对象.
而且, 不在创建 IOC 容器时创建 bean 的实例了.
3. IOC 容器中 bean 的生命周期:
3.1 一般地, 讨论 bean 的生命周期, 是建立在 bean 是单例的基础上的.
3.2 可以为 bean 指定 init 和 destroy 方法
3.3 还可以通过 bean 的后置处理器来更加丰富 bean 的生命周期方法(面试时.).
-->
<bean id="helloWorld"
class="com.atguigu.spring.helloworld.HelloWorld"
scope="singleton"
init-method="init"
destroy-method="destroy">
<property name="userName" value="atguigu"></property>
</bean>
<!--
1. 在 IOC 容器中配置 bean 之间的关联关系
-->
<bean id="userDao"
class="com.atguigu.spring.ref.UserDao"></bean>
<bean id="userService"
class="com.atguigu.spring.ref.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userAction"
class="com.atguigu.spring.ref.UserAction">
<property name="userService" ref="userService"></property>
</bean>
</beans>
=======================================================================
package com.atguigu.spring.helloworld;
import java.io.PrintStream;
public class HelloWorld
{
private String user;
public HelloWorld()
{
System.out.println("HelloWorld's constructor...");
}
public void setUserName(String user)
{
System.out.println("setUserName:" + user);
this.user = user;
}
public void hello() {
System.out.println("Hello:" + this.user);
}
public void init() {
System.out.println("init method...");
}
public void destroy() {
System.out.println("destroy method...");
}
}
=======================================================================
package com.atguigu.spring.helloworld;
import java.io.PrintStream;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld helloWorld = (HelloWorld)ctx.getBean("helloWorld");
helloWorld.hello();
HelloWorld helloWorld2 = (HelloWorld)ctx.getBean("helloWorld");
System.out.println(helloWorld == helloWorld2);
ctx.close();
}
}
=======================================================================
package com.atguigu.spring.ref;
import java.io.PrintStream;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao
{
public void save()
{
System.out.println("UserDao's save...");
}
}
=======================================================================
package com.atguigu.spring.ref;
import java.io.PrintStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService
{
@Autowired
private UserDao userDao;
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
public void addNew() {
System.out.println("addNew...");
this.userDao.save();
}
}
=======================================================================
package com.atguigu.spring.ref;
import java.io.PrintStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserAction
{
private UserService userService;
@Autowired
public void setUserService(UserService userService)
{
this.userService = userService;
}
public void execute() {
System.out.println("execute...");
this.userService.addNew();
}
}
======================================================================
package com.atguigu.spring.ref;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-annotation.xml");
UserAction userAction = (UserAction)ctx.getBean("userAction");
userAction.execute();
}
}
四、Spring AOP
1、AOP 前奏
WHY AOP ?
代码实现片段
问题
• 代码混乱:越来越多的非业务需求(日志和验证等)加入后, 原有的业务方法急剧膨胀. 每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点.
• 代码分散: 以日志需求为例, 只是为了满足这个单一需求, 就不得不在多个模块(方法)里多次重复相同的日志代码. 如果日志需求发生变化, 必须修改所有模块.
2、使用动态代理解决上述问题
• 代理设计模式的原理: 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理. 代理对象决定是否以及何时将方法调用转到原始对象上.
代码实例:
package com.atguigu.spring.aop;
public abstract interface ArithmeticCalculator
{
public abstract int add(int paramInt1, int paramInt2);
public abstract int sub(int paramInt1, int paramInt2);
public abstract int mul(int paramInt1, int paramInt2);
public abstract int div(int paramInt1, int paramInt2);
}
===============================================================================
package com.atguigu.spring.aop;
import org.springframework.stereotype.Component;
public class ArithmeticCalculatorImpl
implements ArithmeticCalculator
{
public int add(int i, int j)
{
int result = i + j;
return result;
}
public int sub(int i, int j)
{
int result = i - j;
return result;
}
public int mul(int i, int j)
{
int result = i * j;
return result;
}
public int div(int i, int j)
{
int result = i / j;
return result;
}
}
=======================================================================
package com.atguigu.spring.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ArithmeticCalculatorLoggingProxy {
//要代理的对象
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
super();
this.target = target;
}
//返回代理对象
public ArithmeticCalculator getLoggingProxy(){
ArithmeticCalculator proxy = null;
ClassLoader loader = target.getClass().getClassLoader();
Class [] interfaces = new Class[]{ArithmeticCalculator.class};
InvocationHandler h = new InvocationHandler() {
/**
* proxy: 代理对象。 一般不使用该对象
* method: 正在被调用的方法
* args: 调用方法传入的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
String methodName = method.getName();
//打印日志
System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));
//调用目标方法
Object result = null;
try {
//前置通知
result = method.invoke(target, args);
//返回通知, 可以访问到方法的返回值
} catch (NullPointerException e) {
e.printStackTrace();
//异常通知, 可以访问到方法出现的异常
}
//后置通知. 因为方法可以能会出异常, 所以访问不到方法的返回值
//打印日志
System.out.println("[after] The method ends with " + result);
return result;
}
};
/**
* loader: 代理对象使用的类加载器。
* interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法.
* h: 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法
*/
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
return proxy;
}
}
======================================================================
package com.atguigu.spring.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
ArithmeticCalculator aCalculator = new ArithmeticCalculatorImpl();
ArithmeticCalculatorAgent proxy = new ArithmeticCalculatorAgent(aCalculator);
proxy.getLoggingProxy().add(3, 3);
System.out.println("代理对象:"+proxy.getLoggingProxy().getClass().getName());
}
}
======================================================================
2、AOP 简介
• AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充.
• AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.
• 在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
• AOP 的好处:
– 每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
– 业务模块更简洁, 只包含核心业务代码.
AOP
3、AOP 术语
• 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
• 通知(Advice): 切面必须要完成的工作
• 目标(Target): 被通知的对象
• 代理(Proxy): 向目标对象应用通知之后创建的对象
• 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
• 切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
4、Spring AOP
• AspectJ:Java 社区里最完整最流行的 AOP 框架.
• 在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
5、在 Spring 中启用 AspectJ 注解支持
• 要在 Spring 应用中使用 AspectJ 注解, 必须在 classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
• 将 aop Schema 添加到 <beans> 根元素中.
• 要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>
• 当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.
6、用 AspectJ 注解声明切面
• 要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
• 在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
• 通知是标注有某种注解的简单的 Java 方法.
• AspectJ 支持 5 种类型的通知注解:
– @Before: 前置通知, 在方法执行之前执行
– @After: 后置通知, 在方法执行之后执行
– @AfterRunning: 返回通知, 在方法返回结果之后执行
– @AfterThrowing: 异常通知, 在方法抛出异常之后
– @Around: 环绕通知, 围绕着方法执行
7、前置通知
• 前置通知:在方法执行之前执行的通知
• 前置通知使用 @Before 注解, 并将切入点表达式的值作为注解值.
8、利用方法签名编写 AspectJ 切入点表达式
• 最典型的切入点表达式时根据方法的签名来匹配各种方法:
– execution * com.atguigu.spring.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.
– execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的所有公有方法.
– execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 类型数值的方法
– execution public double ArithmeticCalculator.*(double, ..): 匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数
– execution public double ArithmeticCalculator.*(double, double): 匹配参数类型为 double, double 类型的方法.
9、合并切入点表达式
• 在 AspectJ 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来.
10、让通知访问当前连接点的细节
• 可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值.
11、后置通知
• 后置通知是在连接点完成之后执行的, 即连接点返回结果或者抛出异常的时候, 下面的后置通知记录了方法的终止.
• 一个切面可以包括一个或者多个通知.
12、返回通知
• 无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知.
13、在返回通知中访问连接点的返回值
• 在返回通知中, 只要将 returning 属性添加到 @AfterReturning 注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称.
• 必须在通知方法的签名中添加一个同名参数. 在运行时, Spring AOP 会通过这个参数传递返回值.
• 原始的切点表达式需要出现在 pointcut 属性中
14、异常通知
• 只在连接点抛出异常时才执行异常通知
• 将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
• 如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.
15、环绕通知
• 环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.
• 对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.
• 在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
• 注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
环绕通知示例代码
16、指定切面的优先级
• 在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
• 切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.
• 实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.
• 若使用 @Order 注解, 序号出现在注解中
17、重用切入点定义
• 在编写 AspectJ 切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现.
• 在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的.
• 切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.
• 其他通知可以通过方法名称引入该切入点.
重用切入点定义示例代码:
18、引入通知
• 引入通知是一种特殊的通知类型. 它通过为接口提供实现类, 允许对象动态地实现接口, 就像对象已经在运行时扩展了实现类一样.
• 引入通知可以使用两个实现类 MaxCalculatorImpl 和 MinCalculatorImpl, 让 ArithmeticCalculatorImpl 动态地实现 MaxCalculator 和 MinCalculator 接口. 而这与从 MaxCalculatorImpl 和 MinCalculatorImpl 中实现多继承的效果相同. 但却不需要修改 ArithmeticCalculatorImpl 的源代码
• 引入通知也必须在切面中声明
• 在切面中, 通过为任意字段添加@DeclareParents 注解来引入声明.
• 注解类型的 value 属性表示哪些类是当前引入通知的目标. value 属性值也可以是一个 AspectJ 类型的表达式, 以将一个即可引入到多个类中. defaultImpl 属性中指定这个接口使用的实现类
引入通知示例代码:
以上知识点代码实例:
Java代码:
package com.atguigu.spring.aop;
public abstract interface ArithmeticCalculator
{
public abstract int add(int paramInt1, int paramInt2);
public abstract int sub(int paramInt1, int paramInt2);
public abstract int mul(int paramInt1, int paramInt2);
public abstract int div(int paramInt1, int paramInt2);
}
=======================================================================
package com.atguigu.spring.aop;
import org.springframework.stereotype.Component;
@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl
implements ArithmeticCalculator
{
public int add(int i, int j)
{
int result = i + j;
return result;
}
public int sub(int i, int j)
{
int result = i - j;
return result;
}
public int mul(int i, int j)
{
int result = i * j;
return result;
}
public int div(int i, int j)
{
int result = i / j;
return result;
}
}
===============================================================================
package com.atguigu.spring.aop;
import java.io.PrintStream;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* AOP 的 helloWorld
* 1. 加入 jar 包
* com.springsource.net.sf.cglib-2.2.0.jar
* com.springsource.org.aopalliance-1.0.0.jar
* com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
* spring-aspects-4.0.0.RELEASE.jar
*
* 2. 在 Spring 的配置文件中加入 aop 的命名空间。
*
* 3. 基于注解的方式来使用 AOP
* 3.1 在配置文件中配置自动扫描的包: <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>
* 3.2 加入使 AspjectJ 注解起作用的配置: <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
* 为匹配的类自动生成动态代理对象.
*
* 4. 编写切面类:
* 4.1 一个一般的 Java 类
* 4.2 在其中添加要额外实现的功能.
*
* 5. 配置切面
* 5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解
* 5.2 声明是一个切面: 添加 @Aspect
* 5.3 声明通知: 即额外加入功能对应的方法.
* 5.3.1 前置通知: @Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))")
* @Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体.
* @Before 里面的是切入点表达式:
*
* 6. 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数.
*
* 7. @After 表示后置通知: 在方法执行之后执行的代码.
*/
@Aspect
@Component
public class LoggingAspect
{
@Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))")
public void beforeMethod(JoinPoint joinPoint)
{
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
@After("execution(* com.atguigu.spring.aop.*.*(..))")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends");
}
}
===============================================================================
package com.atguigu.spring.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-aop.xml");
ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator)ctx.getBean("arithmeticCalculator");
System.out.println(arithmeticCalculator.getClass().getName());
int result = arithmeticCalculator.add(11, 12);
System.out.println("result:" + result);
result = arithmeticCalculator.div(21, 3);
System.out.println("result:" + result);
}
}
===============================================================================
Aop配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
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-4.0.xsd">
<!-- 自动扫描的包 -->
<context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>
<context:component-scan base-package="com.atguigu.spring.testAOP"></context:component-scan>
<!-- 使 AspectJ 的注解起作用 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
=============================================================================
Bean的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.atguigu.spring.ref"></context:component-scan>
</beans>
19、用基于 XML 的配置声明切面
• 除了使用 AspectJ 注解声明切面, Spring 也支持在 Bean 配置文件中声明切面. 这种声明是通过 aop schema 中的 XML 元素完成的.
• 正常情况下, 基于注解的声明要优先于基于 XML 的声明. 通过 AspectJ 注解, 切面可以与 AspectJ 兼容, 而基于 XML 的配置则是 Spring 专有的. 由于 AspectJ 得到越来越多的 AOP 框架支持, 所以以注解风格编写的切面将会有更多重用的机会.
基于 XML ---- 声明切面
• 当使用 XML 声明切面时, 需要在 <beans> 根元素中导入 aop Schema
• 在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 <aop:config> 元素内部. 对于每个切面而言, 都要创建一个 <aop:aspect> 元素来为具体的切面实现引用后端 Bean 实例.
• 切面 Bean 必须有一个标示符, 供 <aop:aspect> 元素引用
声明切面的实例代码:
基于 XML ---- 声明切入点
• 切入点使用 <aop:pointcut> 元素声明
• 切入点必须定义在 <aop:aspect> 元素下, 或者直接定义在 <aop:config> 元素下.
– 定义在 <aop:aspect> 元素下: 只对当前切面有效
– 定义在 <aop:config> 元素下: 对所有切面都有效
• 基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点.
声明切入点的示例代码:
基于 XML ---- 声明通知
• 在 aop Schema 中, 每种通知类型都对应一个特定的 XML 元素.
• 通知元素需要使用 <pointcut-ref> 来引用切入点, 或用 <pointcut> 直接嵌入切入点表达式. method 属性指定切面类中通知方法的名称.
声明通知示例代码:
声明引入
• 可以利用 <aop:declare-parents> 元素在切面内部声明引入
AOP—xml配置方式代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置 bean -->
<bean id="arithmeticCalculator"
class="com.atguigu.spring.xml.ArithmeticCalculatorImpl"></bean>
<bean id="calculator" class="com.atguigu.spring.testXML.CalculatorImpl"></bean>
<!-- 配置切面的 bean. -->
<bean id="loggingAspect"
class="com.atguigu.spring.xml.LoggingAspect"></bean>
<bean id="vlidationAspect"
class="com.atguigu.spring.xml.VlidationAspect"></bean>
<bean id="calculatorAspect"
class="com.atguigu.spring.testXML.CalculatorAspect"></bean>
<!-- 配置 AOP -->
<aop:config>
<!-- 配置切点表达式 放在<aop:config>下对所有aop有效,放在<aop:aspect>下只对当前切面有效 -->
<aop:pointcut expression="execution(* com.atguigu.spring.xml.ArithmeticCalculator.*(int, int))"
id="pointcut"/>
<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
<!-- -->
<aop:aspect ref="calculatorAspect">
<aop:pointcut expression="execution(* com.atguigu.spring.testXML.Calculator.*(int,int))" id="pointcut1"/>
<aop:before method="beforeMethod" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
</beans>
五、Spring 对 JDBC 的支持
1、JdbcTemplate 简介
• 为了使 JDBC 更加易于使用, Spring 在 JDBC API 上定义了一个抽象层, 以此建立一个 JDBC 存取框架.
• 作为 Spring JDBC 框架的核心, JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法. 每个模板方法都能控制整个过程, 并允许覆盖过程中的特定任务. 通过这种方式, 可以在尽可能保留灵活性的情况下, 将数据库存取的工作量降到最低.
2、使用 JdbcTemplate 更新数据库
• 用 sql 语句和参数更新数据库:
• 批量更新数据库:
3、使用 JdbcTemplate 查询数据库
• 查询单行:
• 便利的 BeanPropertyRowMapper 实现
• 查询多行:
• 单值查询:
4、简化 JDBC 模板查询
• 每次使用都创建一个 JdbcTemplate 的新实例, 这种做法效率很低下.
• JdbcTemplate 类被设计成为线程安全的, 所以可以再 IOC 容器中声明它的单个实例, 并将这个实例注入到所有的 DAO 实例中.
• JdbcTemplate 也利用了 Java 1.5 的特定(自动装箱, 泛型, 可变长度等)来简化开发
• Spring JDBC 框架还提供了一个 JdbcDaoSupport 类来简化 DAO 实现. 该类声明了 jdbcTemplate 属性, 它可以从 IOC 容器中注入, 或者自动从数据源中创建.
5、注入 JDBC 模板示例代码
6、扩展 JdbcDaoSupport 示例代码
7、在 JDBC 模板中使用具名参数
• 在经典的 JDBC 用法中, SQL 参数是用占位符 ? 表示,并且受到位置的限制. 定位参数的问题在于, 一旦参数的顺序发生变化, 就必须改变参数绑定.
• 在 Spring JDBC 框架中, 绑定 SQL 参数的另一种选择是使用具名参数(named parameter).
• 具名参数: SQL 按名称(以冒号开头)而不是按位置进行指定. 具名参数更易于维护, 也提升了可读性. 具名参数由框架类在运行时用占位符取代
• 具名参数只在 NamedParameterJdbcTemplate 中得到支持
8、在 JDBC 模板中使用具名参数
• 在 SQL 语句中使用具名参数时, 可以在一个 Map 中提供参数值, 参数名为键
• 也可以使用 SqlParameterSource 参数
• 批量更新时可以提供 Map 或 SqlParameterSource 的数组
以上知识点代码实例:
Xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-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.atguigu.spring"></context:component-scan>
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置 C3P0 数据源 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置 Spirng 的 JdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 -->
<bean id="namedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
============================================================================
db.properties文件
jdbc.username=dev
jdbc.password=dev
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://192.168.0.200/spring
jdbc.initPoolSize=5
jdbc.maxPoolSize=10
==============================================================================
Java文件
package com.atguigu.spring.jdbc;
import org.springframework.stereotype.Component;
@Component
public class Department
{
private Integer id;
private String name;
public Integer getId()
{
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String toString()
{
return "Department [id=" + this.id + ", name=" + this.name + "]";
}
}
==============================================================================
package com.atguigu.spring.jdbc;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
@Repository
public class DepartmentDao extends JdbcDaoSupport
{
@Autowired
public void setDataSource2(DataSource dataSource)
{
setDataSource(dataSource);
}
public Department get(Integer id) {
String sql = "SELECT id, dept_name name FROM departments WHERE id = ?";
RowMapper rowMapper = new BeanPropertyRowMapper(Department.class);
return (Department)getJdbcTemplate().queryForObject(sql, rowMapper, new Object[] { id });
}
}
===============================================================================
package com.atguigu.spring.jdbc;
import org.springframework.stereotype.Component;
@Component
public class Employee
{
private Integer id;
private String lastName;
private String email;
private Integer dpetId;
public Integer getId()
{
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getDpetId() {
return this.dpetId;
}
public void setDpetId(Integer dpetId) {
this.dpetId = dpetId;
}
public String toString()
{
return "Employee [id=" + this.id + ", lastName=" + this.lastName + ", email=" +
this.email + ", dpetId=" + this.dpetId + "]";
}
}
===============================================================================
package com.atguigu.spring.jdbc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDao
{
@Autowired
private JdbcTemplate jdbcTemplate;
public Employee get(Integer id)
{
String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
RowMapper rowMapper = new BeanPropertyRowMapper(Employee.class);
Employee employee = (Employee)this.jdbcTemplate.queryForObject(sql, rowMapper, new Object[] { id });
return employee;
}
}
===============================================================================
package com.atguigu.spring.jdbc;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
public class JDBCTest
{
private ApplicationContext ctx = null;
private JdbcTemplate jdbcTemplate = (JdbcTemplate)this.ctx.getBean("jdbcTemplate");
private EmployeeDao employeeDao = (EmployeeDao)this.ctx.getBean(EmployeeDao.class);
private DepartmentDao departmentDao = (DepartmentDao)this.ctx.getBean(DepartmentDao.class);
private NamedParameterJdbcTemplate namedParameterJdbcTemplate = (NamedParameterJdbcTemplate)this.ctx.getBean(NamedParameterJdbcTemplate.class);
@Test
public void testNamedParameterJdbcTemplate2()
{
String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:lastName,:email,:dpetId)";
Employee employee = new Employee();
employee.setLastName("XYZ");
employee.setEmail("xyz@sina.com");
employee.setDpetId(Integer.valueOf(3));
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employee);
this.namedParameterJdbcTemplate.update(sql, paramSource);
}
@Test
public void testNamedParameterJdbcTemplate()
{
String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:ln,:email,:deptid)";
Map paramMap = new HashMap();
paramMap.put("ln", "FF");
paramMap.put("email", "ff@atguigu.com");
paramMap.put("deptid", Integer.valueOf(2));
this.namedParameterJdbcTemplate.update(sql, paramMap);
}
@Test
public void testDepartmentDao() {
System.out.println(this.departmentDao.get(Integer.valueOf(1)));
}
@Test
public void testEmployeeDao() {
System.out.println(this.employeeDao.get(Integer.valueOf(1)));
}
@Test
public void testQueryForObject2()
{
String sql = "SELECT count(id) FROM employees";
long count = ((Long)this.jdbcTemplate.queryForObject(sql, Long.class)).longValue();
System.out.println(count);
}
@Test
public void testQueryForList()
{
String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?";
RowMapper rowMapper = new BeanPropertyRowMapper(Employee.class);
List employees = this.jdbcTemplate.query(sql, rowMapper, new Object[] { Integer.valueOf(5) });
System.out.println(employees);
}
@Test
public void testQueryForObject()
{
String sql = "SELECT id, last_name lastName, email, dept_id as \"department.id\" FROM employees WHERE id = ?";
RowMapper rowMapper = new BeanPropertyRowMapper(Employee.class);
Employee employee = (Employee)this.jdbcTemplate.queryForObject(sql, rowMapper, new Object[] { Integer.valueOf(1) });
System.out.println(employee);
}
@Test
public void testBatchUpdate()
{
String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(?,?,?)";
List batchArgs = new ArrayList();
batchArgs.add(new Object[] { "AA", "aa@atguigu.com", Integer.valueOf(1) });
batchArgs.add(new Object[] { "BB", "bb@atguigu.com", Integer.valueOf(2) });
batchArgs.add(new Object[] { "CC", "cc@atguigu.com", Integer.valueOf(3) });
batchArgs.add(new Object[] { "DD", "dd@atguigu.com", Integer.valueOf(3) });
batchArgs.add(new Object[] { "EE", "ee@atguigu.com", Integer.valueOf(2) });
this.jdbcTemplate.batchUpdate(sql, batchArgs);
}
@Test
public void testUpdate()
{
String sql = "UPDATE employees SET last_name = ? WHERE id = ?";
this.jdbcTemplate.update(sql, new Object[] { "Jack", Integer.valueOf(5) });
}
@Test
public void testDataSource() throws SQLException {
DataSource dataSource = (DataSource)this.ctx.getBean(DataSource.class);
System.out.println(dataSource.getConnection());
}
}
六、Spring 中的事务管理
1、事务简介
• 事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
• 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用
• 事务的四个关键属性(ACID)
– 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
– 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
– 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
– 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
4、事务管理的问题
• 问题:
– 必须为不同的方法重写类似的样板代码
– 这段代码是特定于 JDBC 的, 一旦选择类其它数据库存取技术, 代码需要作出相应的修改
5、Spring 中的事务管理
• 作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.
• Spring 既支持编程式事务管理, 也支持声明式的事务管理.
• 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.
• 声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.
6、Spring 中的事务管理器
• Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了.
• Spring 的核心事务管理抽象是 它为事务管理封装了一组独立于技术的方法. 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的.
7、Spring 中的事务管理器的不同实现
• :在应用程序中只需要处理一个数据源, 而且通过 JDBC 存取
• : 在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理
• :用 Hibernate 框架存取数据库
• ……
• 事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中
需求
数据表中的数据
Account 表
Book 表
Book_STOCK 表
8、用事务通知声明式地管理事务
• 事务管理是一种横切关注点
• 为了在 Spring 2.x 中启用声明式事务管理, 可以通过 tx Schema 中定义的 <tx:advice> 元素声明事务通知, 为此必须事先将这个 Schema 定义添加到 <beans> 根元素中去.
• 声明了事务通知后, 就需要将它与切入点关联起来. 由于事务通知是在 <aop:config> 元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须在 <aop:config> 元素中声明一个增强器通知与切入点关联起来.
• 由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理.
用事务通知声明式地管理事务示例代码
9、用 @Transactional 注解声明式地管理事务
• 除了在带有切入点, 通知和增强器的 Bean 配置文件中声明事务外, Spring 还允许简单地用 @Transactional 注解来标注事务方法.
• 为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.
• 可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的.
• 在 Bean 配置文件中只需要启用 <tx:annotation-driven> 元素, 并为之指定事务管理器就可以了.
• 如果事务处理器的名称是 transactionManager, 就可以在<tx:annotation-driven> 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.
10、事务传播属性
• 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
• 事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为.
11、Spring 支持的事务传播行为
需求
• 新定义 Cashier 接口: 表示客户的结账操作
• 修改数据表信息如下, 目的是用户 Tom 在结账时, 余额只能支付第一本书, 不够支付第二本书:
12、REQUIRED 传播行为
• 当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时, 它默认会在现有的事务内运行. 这个默认的传播行为就是 REQUIRED. 因此在 checkout() 方法的开始和终止边界内只有一个事务. 这个事务只在 checkout() 方法结束的时候被提交, 结果用户一本书都买不了
• 事务传播属性可以在 @Transactional 注解的 propagation 属性中定义
13、REQUIRES_NEW 传播行为
• 另一种常见的传播行为是 REQUIRES_NEW. 它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它.
14、在 Spring 2.x 事务通知中配置传播属性
• 在 Spring 2.x 事务通知中, 可以像下面这样在 <tx:method> 元素中设定传播事务属性
15、并发事务所导致的问题
• 当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题
• 并发事务所导致的问题可以分为下面三种类型:
– 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但 还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
– 不可重复读:对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
– 幻读:对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
16、事务的隔离级别
• 从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行.
• 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.
• 事务的隔离级别可以通过隔离事务属性指定
17、Spring 支持的事务隔离级别
• 事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
• Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE
• Mysql 支持 4 中事务隔离级别.
18、设置隔离事务属性
• 用 @Transactional 注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别.
• 在 Spring 2.x 事务通知中, 可以在 <tx:method> 元素中指定隔离级别
19、设置回滚事务属性
• 默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会.
• 事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类.
– rollbackFor: 遇到时必须进行回滚
– noRollbackFor: 一组异常类,遇到时必须不回滚
• 在 Spring 2.x 事务通知中, 可以在 <tx:method> 元素中指定回滚规则. 如果有不止一种异常, 用逗号分隔.
20、超时和只读属性
• 由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响.
• 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化.
• 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
• 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
设置超时和只读事务属性
• 超时和只读属性可以在 @Transactional 注解中定义.超时属性以秒为单位来计算.
• 在 Spring 2.x 事务通知中, 超时和只读属性可以在 <tx:method> 元素中进行指定.
以上知识点代码实例:
Xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-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.atguigu.spring"></context:component-scan>
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置 C3P0 数据源 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置 Spirng 的 JdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 bean -->
<bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService" class="com.atguigu.spring.tx.xml.service.impl.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="com.atguigu.spring.tx.xml.service.impl.CashierImpl">
<property name="bookShopService" ref="bookShopService"></property>
</bean>
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
===========================================================================
db.properties文件
jdbc.username=dev
jdbc.password=dev
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://192.168.0.200/spring
jdbc.initPoolSize=5
jdbc.maxPoolSize=10
==============================================================================
Java文件
package com.atguigu.spring.tx;
public abstract interface BookShopDao
{
public abstract int findBookPriceByIsbn(String paramString);
public abstract void updateBookStock(String paramString);
public abstract void updateUserAccount(String paramString, int paramInt);
}
===============================================================================
package com.atguigu.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int findBookPriceByIsbn(String isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
@Override
public void updateBookStock(String isbn) {
//检查书的库存是否足够, 若不够, 则抛出异常
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if(stock == 0){
throw new BookStockException("库存不足!");
}
String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
jdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserAccount(String username, int price) {
//验证余额是否足够, 若不足, 则抛出异常
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
if(balance < price){
throw new UserAccountException("余额不足!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql, price, username);
}
}
=========================================================================
基于xml方式配置事务
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置 C3P0 数据源 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置 Spirng 的 JdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 bean -->
<bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService" class="com.atguigu.spring.tx.xml.service.impl.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="com.atguigu.spring.tx.xml.service.impl.CashierImpl">
<property name="bookShopService" ref="bookShopService"></property>
</bean>
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
============================================================================
Java文件
package com.atguigu.spring.tx.xml;
public abstract interface BookShopDao
{
public abstract int findBookPriceByIsbn(String paramString);
public abstract void updateBookStock(String paramString);
public abstract void updateUserAccount(String paramString, int paramInt);
}
==============================================================================
package com.atguigu.spring.tx.xml;
import org.springframework.jdbc.core.JdbcTemplate;
public class BookShopDaoImpl
implements BookShopDao
{
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate)
{
this.jdbcTemplate = jdbcTemplate;
}
public int findBookPriceByIsbn(String isbn)
{
String sql = "SELECT price FROM book WHERE isbn = ?";
return ((Integer)this.jdbcTemplate.queryForObject(sql, Integer.class, new Object[] { isbn })).intValue();
}
public void updateBookStock(String isbn)
{
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = ((Integer)this.jdbcTemplate.queryForObject(sql2, Integer.class, new Object[] { isbn })).intValue();
if (stock == 0) {
throw new BookStockException("??��??!");
}
String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
this.jdbcTemplate.update(sql, new Object[] { isbn });
}
public void updateUserAccount(String username, int price)
{
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = ((Integer)this.jdbcTemplate.queryForObject(sql2, Integer.class, new Object[] { username })).intValue();
if (balance < price) {
throw new UserAccountException("?????!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
this.jdbcTemplate.update(sql, new Object[] { Integer.valueOf(price), username });
}
}
==============================================================================
package com.atguigu.spring.tx.xml;
public class BookStockException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public BookStockException()
{
}
public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
{
super(message, cause);
}
public BookStockException(String message, Throwable cause)
{
super(message, cause);
}
public BookStockException(String message)
{
super(message);
}
public BookStockException(Throwable cause)
{
super(cause);
}
}
=============================================================================
package com.atguigu.spring.tx.xml;
public class UserAccountException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public UserAccountException()
{
}
public UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
{
super(message, cause);
}
public UserAccountException(String message, Throwable cause)
{
super(message, cause);
}
public UserAccountException(String message)
{
super(message);
}
public UserAccountException(Throwable cause)
{
super(cause);
}
}
==============================================================================
package com.atguigu.spring.tx.xml.service;
public abstract interface BookShopService
{
public abstract void purchase(String paramString1, String paramString2);
}
==============================================================================
package com.atguigu.spring.tx.xml.service.impl;
import com.atguigu.spring.tx.xml.BookShopDao;
import com.atguigu.spring.tx.xml.service.BookShopService;
public class BookShopServiceImpl
implements BookShopService
{
private BookShopDao bookShopDao;
public void setBookShopDao(BookShopDao bookShopDao)
{
this.bookShopDao = bookShopDao;
}
public void purchase(String username, String isbn)
{
try
{
Thread.sleep(1000L);
}
catch (InterruptedException localInterruptedException) {
}
int price = this.bookShopDao.findBookPriceByIsbn(isbn);
this.bookShopDao.updateBookStock(isbn);
this.bookShopDao.updateUserAccount(username, price);
}
}
==============================================================================
package com.atguigu.spring.tx.xml.service;
import java.util.List;
public abstract interface Cashier
{
public abstract void checkout(String paramString, List<String> paramList);
}
==============================================================================
package com.atguigu.spring.tx.xml.service.impl;
import com.atguigu.spring.tx.xml.service.BookShopService;
import com.atguigu.spring.tx.xml.service.Cashier;
import java.util.List;
public class CashierImpl
implements Cashier
{
private BookShopService bookShopService;
public void setBookShopService(BookShopService bookShopService)
{
this.bookShopService = bookShopService;
}
public void checkout(String username, List<String> isbns)
{
for (String isbn : isbns)
this.bookShopService.purchase(username, isbn);
}
}
==============================================================================
package com.atguigu.spring.tx.xml;
import com.atguigu.spring.tx.xml.service.BookShopService;
import com.atguigu.spring.tx.xml.service.Cashier;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
public class SpringTransactionTest
{
private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
private Cashier cashier = null;
@Test
public void testTransactionlPropagation()
{
this.cashier.checkout("AA", Arrays.asList(new String[] { "1001", "1002" }));
}
@Test
public void testBookShopService() {
this.bookShopService.purchase("AA", "1001");
}
}
===============================================================================
七、Spring 整合 Hibernate
1、Spring 整合 Hibernate
• Spring 支持大多数流行的 ORM 框架, 包括 Hibernate JDO, TopLink, Ibatis 和 JPA。
• Spring 对这些 ORM 框架的支持是一致的, 因此可以把和 Hibernate 整合技术应用到其他 ORM 框架上.
• Spring 2.0 同时支持 Hibernate 2.x 和 3.x. 但 Spring 2.5 只支持 Hibernate 3.1 或更高版本
2、在 Spring 中配置 SessionFactory
• 对于 Hibernate 而言, 必须从原生的 Hibernate API 中构建 SessionFactory. 此外, 应用程序也无法利用 Spring 提供的数据存储机制(例如: Spring 的事务管理机制)
• Spring 提供了对应的工厂 Bean, 可以用单实例的形式在 IOC 容器中创建 SessionFactory 实例.
需求
3、在 Spring 中配置 SessionFactory(1)
• 可以利用 LocalSessionFactoryBean 工厂 Bean, 声明一个使用 XML 映射文件的 SessionFactory 实例.
• 需要为该工厂 Bean 指定 configLocation 属性来加载 Hibernate 配置文件.
在 Spring 中配置 SessionFactory(2)
• 如果在 Spring IOC 容器中配置数据源. 可以将该数据源注入到 LocalSessionFactoryBean 的 dataSource 属性中. 该属性可以指定的数据源会覆盖掉 Hibernate 配置文件里的数据库配置.
在 Spring 中配置 SessionFactory(3)
• 可以将所有配置合并到 LocalSessionFactoryBean 中,从而忽略 Hibernate 配置文件.
• 可以在 LocalSessionFactoryBean 的 mappingResources 属性中指定 XML 映射文件的位置.该属性为 String[] 类型. 因此可以指定一组映射文件.
• 在 hibernateProperties 属性中指定数据库方言等.
4、用 Spring 的 ORM 模板持久化对象
• 在单独使用 ORM 框架时, 必须为每个 DAO 操作重复某些常规任务. 例如: 打开关闭 Session 对象; 启动, 提交, 回滚事务等.
• 同 JDBC 一样, Spring 采取了相同的方法 ------ 定义模板类和 DAO 支持类来简化 ORM 框架的使用. 而且 Spring 在不同的事务管理 API 之上定义了一个事务抽象层. 对于不同的 ORM 框架, 只需要选择相应的事务管理器实现.
5、Spring 对不同数据存储策略的支持类
• HibernateTemplate 确保了 Hibernate 会话能够正确地打开和关闭.
• HibernateTemplate 也会让原生的 Hibernate 事务参与到 Spring 的事务管理体系中来. 从而利用 Spring 的声明式事务管理事务.
6、使用 Hibernate 模板
• HibernateTemplate 中的模板方法管理会话和事务. 如果在一个支持事务的 DAO 方法中有多个 Hibernate 操作, 模板方法可以确保它们会在同一个会话和事务中运行. 因此没有必要为了会话和事务管理去和 Hibernate API 打交道.
• 通过为 DAO 方法添加 @Transactional 注解将其声明为受事务管理的.
• HibernateTemplate 类是线程安全的, 因此可以在 Bean 配置文件中只声明一个实例, 并将该实例注入到所有的 Hibernate DAO 中.
使用 Hibernate 模板示例代码
7、在 HibernateTemplate 中访问 Hibernate 底层 Session
8、继承 Hibernate 的 DAO 支持类
• Hibernate DAO 可以通过继承 HibernateDaoSupport 来继承 setSessionFactory() 和 setHibernateTemplate() 方法. 然后, 只要在 DAO 方法中调用 getHibernateTemplate() 方法就可以获取到模板实例.
• 如果为 HibernateDaoSupport 实现类注入了 SessionFactory 实例, 就不需要在为之注入 HibernateTemplate 实例了, 因为HibernateDaoSupport 会根据传入的 SessionFactory 在其构造器内创建 HibernateTemplate 的实例, 并赋给 hibernateTemplate 属性
9、用 Hibernate 的上下文 Session 持久化对象
• Spring 的 HibernateTemplate 可以管理会话和事务, 简化 DAO 实现. 但使用 HibernateTemplate 意味着DAO 必须依赖于 Spring 的 API
• 代替 HibernateTemplate 的另一种办法是使用 Hibernate 的上下文 Session 对象.
• Hibernate 上下文 Session 对象和 Spring 的事务管理合作的很好, 但此时需保证所有的DAO 方法都支持事务
• 注意此时不需在 beans.xml 文件中配置, 因为 Spring 此时已经开始事务, 所以已经在 ThreadLocal 对象中绑定了 Session 对象
• 在 Hibernate 会话中调用原生的方法时, 抛出的异常依旧是原生的 HibernateException.
• 为了保持一致的异常处理方法, 即把 Hibernate 异常转换为 Spring 的 DataAccessException 异常, 那么必须为需要异常转换的 DAO 类添加 @Respository 注解.
• 然后在注册一个 实例, 将原生的 Hibernate 异常转换为 Spring 的 DataAccessException 层次结构中的数据存取异常. 这个 Bean 后置处理器只为添加了@Respository 注解的 Bean 转换异常.
10、Hibernate 上下文相关的 Session(1)
• 从 Hibernate 3 开始, SessionFactory 新增加了 getCurrentSession() 方法, 该方法可直接获取“上下文“相关的 Session.
• Hibernate 通过 CurrentSessionContext 接口的实现类和 配置参数hibernate.current_session_context_class定义 “上下文”
– JTASessionContext: 根据 JTA 来跟踪和界定 Session 对象.
– ThreadLocalSessionContext: 通过当前正在执行的线程来跟踪和界定 Session 对象
– ManagedSessionContext: 通过正在当前执行来跟踪和界定 Session 对象. 但程序需要调用该类的静态方法来绑定 Sessio 对象, 取消绑定, flush 或者关闭 Session 对象.
Hibernate 上下文相关的 Session(2)
• 如果使用 ThreadLocalSessionContext 策略, Hibernate 的 Session 会随着 getCurrentSession() 方法自动打开, 随着事务提交自动关闭.
• 若当前应用是基于 JTA 的分布式事务, 通常采用第一种方式; 而对于独立的 Hibernate 应用则使用第二种应用.
• 配置:
– 根据 JTA 来跟踪和界定 Session 对象:
– 通过当前正在执行的线程来跟踪和界定 Session 对象: