目录
2.在src下创建并配置applicationcontext.xml文件
1.context:property-placeholder标签:
2.使用PropertyPlaceholderConfigurer对象:
1.基于TransactionTemplate的编程式事务管理
一、SpringIOC
(一)SpringIOC的概念
IOC(Inversion of Control),即控制反转,不是什么技术而是一种设计思想,这是Spring的核心。所谓IOC,对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系。
传统JavaSE程序设计,是程序主动去创建依赖对象,也就是正转。而IOC是由专门一个容器来创建这些对象,即由IOC容器来控制对象的创建,由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,也就是反转。所以控制的什么被反转了?就是:获得依赖对象的方式反转了。
IOC是一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。在传统应用程序中都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合 ,难于测试;有了IOC容器后,把创建和查询依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IOC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取声明资源都是主动出击,但是在IOC/DI思想中,应用程序就变成被动的了,被动的等待IOC容器来创建并注入它所需要的资源了。IOC很好的体现了面向对象设计法则之——好莱坞法则“别找我们,我们找你”,即由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
SpringIOC对项目对象的解耦的颗粒度是粗颗粒的。一般在层与层之间,模块儿与模块儿之间的对象使用IOC进行解耦,层内部的对象一般还是直接创建。例如:servlet和service层要使用IOC解耦,service和mapper层要使用IOC解耦。
(二)SpringIOC的使用
1.所需jar包
commons-logging-1.1.3.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
2.在src下创建并配置applicationcontext.xml文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<!-- 在配置文件中配置需要被管理的对象 -->
<bean id="user1" class="com.bjsxt.pojo.User2"></bean>
</beans>
3.在java代码中的应用
//1.获取Spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
//2.获取Spring容器对象中的被管理的对象
User u1 = (User) ac.getBean("uaser1");
(三)SpringIOC创建对象的三种方式
SpringIOC三种创建对象的方式:带有固定初始化数据的对象或空对象。
1.构造器方式
<!-- 无参数构造器:SpringIOC的默认方式。直接使用bean标签配置即可 -->
<bean id="user" class="com.bjsxt.pojo.User"></bean>
<!-- 有参数构造器: 告诉Spring容器创建一个带有我们希望的初始化数据的对象-->
<bean id="user2" class="com.bjsxt.pojo.User">
<constructor-arg index="0" name="uid" type="int" value="1"></constructor-arg>
<constructor-arg index="1" name="uname" type="String" value="zhangsna"></constructor-arg>
<constructor-arg index="2" name="uage" type="int" value="18"></constructor-arg>
</bean>
2.工厂方式
<!--
静态工厂:生产对象的方法是静态的,工厂被spring容器所管理即可,工厂内部的对象不需要管理
使用:声明bean标签,class属性为静态工厂类全限定路径,fatory-method为生产对象的方法
作用:实现了工厂生产对象的过程和代码的解耦
-->
<bean id="sfactory" class="com.bjsxt.factory.StudentFactory" factory-method="newIntance"></bean>
<!--
动态工厂:生产对象的方法是非静态的,需要先获取工厂的实例化对象,然后再调用方法生产对象
使用:声明bean标签,class属性为动态工厂的全限定路径
声明bean标签,class属性为工厂生产的对象的全限定路径
factory-bean为动态工厂bean标签的id
fatory-method为动态工厂生产对象的方法名
作用:实现了工厂生产对的过程和代码的解耦
-->
<bean id="sfactory2" class="com.bjsxt.factory.StudentFactory2"></bean>
<bean id="student" class="com.bjsxt.pojo.User" factory-bean="sfactory2" factory-method="newIntance"></bean>
3.属性注入方式
分为4种。基本类型、数组、Map集合、List集合。
<!-- 属性注入方式 -->
<bean id="user3" class="com.bjsxt.pojo.User">
<!-- 基本类型属性 -->
<property name="uid" value="2"></property>
<property name="uname" value="lisi"></property>
<property name="uage" value="30"></property>
<!-- 数组类型 -->
<property name="str">
<!--
使用array标签声明该属性的值为一个数组对象,value标签表示一个基本类型的数组的元素
可以继续嵌套array标签使用,表示一个数组元素
-->
<array>
<value>aaa</value>
<value>bbb</value>
</array>
</property>
<!-- 集合类型 -->
<!-- list集合 -->
<property name="list">
<!--list标签表明该属性赋值为list集合,使用value标签赋值基本类型数据-->
<list>
<value>jjj</value>
<value>AAA</value>
</list>
</property>
<!-- map集合 -->
<property name="map">
<!-- 使用map子标签标示该属性的值为map集合对象,使用entry标签声明初始化的键值对数据 -->
<map>
<entry key="a1" value="kkkQ"></entry>
<entry key="b2" value="2222"></entry>
</map>
</property>
</bean>
(四)Spring的DI依赖注入
DI(Dependency Injection),依赖注入:对象之间依赖关系由容器在运行期决定(IOC容器注入应用程序某个它需要的外部资源)。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁来实现。
IOC和DI其实是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象的这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对于IOC而言,“依赖注入”明确描述了“被注入对象依赖IOC容器配置依赖对象”。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”。
①谁依赖谁:应用程序依赖于IOC容器
②为什么需要依赖:应用程序需要IOC容器来提供对象需要的外部资源
③谁注入了谁:IOC容器注入应用程序某个对象。
④注入了什么:注入某个对象所需要的外部资源(对象、资源、常量数据等)
1.构造器注入之依赖注入
在Spring创建一个对象的同时给其引用类型的属性赋值。在Spring容器的配置文件中使用bean标签配置被管理的对象,在bean标签中使用constructor-arg标签指明要调用的构造器。前提是存在这样的构造器。
<bean id="s" class="com.bjsxt.pojo.Student">
<property name="sname" value="李四"></property>
</bean>
<bean id="user" class="com.bjsxt.pojo.User">
<constructor-arg index="0" name="uid" type="int" value="1"></constructor-arg>
<constructor-arg index="1" name="name" type="String" value="王五"></constructor-arg>
<constructor-arg index="2" name="uage" type="int" value="18"></constructor-arg>
<constructor-arg index="3" name="s" type="com.bjsxt.pojo.Student" ref="s"></constructor-arg>
</bean>
2.属性注入之依赖注入
在bean标签下使用property标签,但是用其ref属性,属性值为要注入的bean对象的ID
<bean id="s2" class="com.bjsxt.pojo.Student">
<property name="sname" value="德玛西亚"></property>
</bean>
<bean id="user2" class="com.bjsxt.pojo.User">
<property name="uid" value="2"></property>
<property name="name" value="赏金猎人"></property>
<property name="uage" value="25"></property>
<property name="s" ref="s2"></property>
</bean>
3.Spring的自动注入
在Spring的配置文件中使用依赖注入时,如果要被注入的bean的ID和注入的bean的属性名相同,我们仍然需要在配置文件中声明注入方式,比较麻烦。解决:自动注入(自动注入是针对依赖注入的)。
设置bean标签的属性autowire的值为以下其中一个:byName、byType、constructor、default、no
<!-- byName:通过属性名和要注入的Bean的id相同的规则 -->
<!-- 配置学生bean -->
<bean id="s1" class="com.bjsxt.pojo.Student" autowire="byName"></bean>
<!--配置教师bean -->
<bean id="teacher" class="com.bjsxt.pojo.Teacher"></bean>
<!-- byType:通过属性名和要注入的bean的类型相同的规则 -->
<!-- 配置学生bean -->
<bean id="s2" class="com.bjsxt.pojo.Student" autowire="byType"></bean>
<!--配置教师bean -->
<bean id="t2" class="com.bjsxt.pojo.Teacher"></bean>
<!-- constructor:根据构造器的形参的类型和要注入的bean的类型相同的规则注入 -->
<!-- 配置学生bean -->
<bean id="s3" class="com.bjsxt.pojo.Student" autowire="constructor"></bean>
<!--配置教师bean -->
<bean id="t3" class="com.bjsxt.pojo.Teacher"></bean>
<!-- default:使用全局的注入配置,在顶层标签beans中使用属性default-autowire声明全局自动注入方式-->
<!-- 配置学生bean -->
<bean id="s4" class="com.bjsxt.pojo.Student" autowire="default"></bean>
<!--配置教师bean -->
<bean id="teacher" class="com.bjsxt.pojo.Teacher"></bean>
<!-- no:不使用自动注入,使用手动方式的依赖注入,也就是使用property标签方式 -->
(五)SpringIOC的Scope属性
Spring容器在被创建的时候就完成了对其配置的所有的对象的初始化创建,所以连续获取同一个bean对象,返回的是同一个对象。但是如果我们想获取不同的怎么办?让Spring容器在我们每次获取对象的时候都重新创建。设置bean标签的scope属性。
singleton:(单例)默认值,表示在Spring容器被创建的时候完成对象的初始化创建,连续多次获取都返回同一个对象。
prototype:(多例)表示该bean在Spring容器被创建的时候不需要初始化创建,每次获取的时候都重新创建并返回。
request:每次请求重新创建。
session:每个会话创建。
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-autowire="byName"
>
<!--配置Student的bean -->
<!--单例的 -->
<bean id="s" class="com.bjsxt.pojo.Student" scope="singleton"></bean>
<!--多例的 -->
<bean id="s2" class="com.bjsxt.pojo.Student" scope="prototype"></bean>
</beans>
(六)Spring对于Mybatis的整合案列
1.service层和mapper层的解耦
问题一:传统方式是在service层直接通过mybatis对象完成数据的操作,业务层和mapper层耦合性非常高
解决一:使用SpringIOC将service层和Mapper层进行解耦
public UserServiceImpl implements UserService{
private UserMapper userMapper;
public void login(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
userMapper= (userMapper) ac.getBean("userMapper");
}
}
MapperScannerConfigurer会扫描com.bjsxt.mapper下的全部mapper.xml文件,并根据相应的mapper接口创建实例化对象,存储在spring容器中,mapper对象的健名为mapper接口名首字母小写
<!-- 配置mapper扫描bean -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="factory"></property>
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
2.servlet层和service层的解耦
问题二:如果Servlet层中直接new创建业务层对象,虽然可以正常使用,但是会造成servlet层和业务层的耦合性较高,不易于后期的迭代升级
解决二:使用SpringIOC将servlet层和service层进行解耦
public class UserServlet extends HttpServlet{
UserService userService;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
userService = (UserService) ac.getBean("userService");
}
}
<bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl"></bean>
3.Spring容器的合理创建方式
问题三:在以上两个问题的代码中,我们在Servlet中创建了一次Spring容器对象,在Service中又创建了一次Spring容器对象,Spring容器对象在一个线程中会被创建两次,这样造成在高并发环境中占据的内存过多(因为容器中实例化了很多对象)。
解决三:
(1)在service层中我们使用Spring容器对象获取Mapper接口对象,Mapper接口对象本身就在Spring容器中,但是我们把Service也配置成bean了,那么是不是可以使用依赖注入呢。直接在Spring的配置文件中通过依赖注入将Mapper对象注入给service对象。
(2)使用init方法,当服务器启动时就创建Spring容器对象,当请求到来时,所有配置成bean的对象都已经生成好了,可以大大提高效率,而不用等请求到来时再创建。实现这个方式,除了将创建Spring容器的代码写到init方法外,还需要web.xml文件中,配置<load-on-startup>1</load-on-startup>。(load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。其中的参数1为优先级,而非启动延迟时间)。
public class UserServiceImpl implements UserService{
//使用依赖注入必须提供get\set方法
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper umuserMapper{
this.userMapper= userMapper;
}
@Override
public void login(){}
}
public class UserServlet extends HttpServlet{
private UserService userService;
//服务器启动时加载init方法,只会执行一次
@Override
public void init() throws ServletException{
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext.xml");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {}
}
<!-- 配置Servlet -->
<servlet>
<servlet-name>user</servlet-name>
<servlet-class>com.bjsxt.servlet.UserServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>user</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>
4.Spring配置文件路径与代码的解耦
问题四:Spring容器的获取路径在init方法中与代码耦合性较高
解决四:将Spring容器相关路径信息配置到web.xml中
public class UserServlet extends HttpServlet{
UserService userService;
@Override
public void init() throws ServletException{
//优化方式
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
userService = (UserService) ac.getBean("userService");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {}
}
<!--配置Spring文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext.xml</param-value>
</context-param>
<!--配置监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
5.整合代码
(1)所需jar包
asm-3.3.1.jar
cglib-2.2.2.jar
commons-logging-1.1.1.jar
commons-logging-1.1.3.jar
gson-2.2.4.jar
javassist-3.17.1-GA.jar
jstl-1.2.jar
log4j-1.2.17.jar
log4j-api-2.0-rc1.jar
log4j-core-2.0-rc1.jar
mybatis-3.2.7.jar
mybatis-spring-1.2.3.jar
mysql-connector-java-5.1.30.jar
slf4j-api-1.7.5.jar
slf4j-log4j12-1.7.5.jar
spring-aop-4.1.6.RELEASE.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
spring-jdbc-4.1.6.RELEASE.jar
spring-tx-4.1.6.RELEASE.jar
spring-web-4.1.6.RELEASE.jar
standard-1.1.2.jar
(2)配置applicationcontext.xml
DriverManagerDataSource:配置数据源bean,存储数据连接相关参数的对象。
SqlSessionFactoryBean:配置工程bean,用来生产SqlSession对象的。
MapperScannerConfigurer:配置mapper扫描,使用工厂对象生产SqlSession并完成对mapper包的扫描。
UserServiceImpl:配置业务层bean,实现业务层和servlet层的解耦。
<?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
">
<!-- 配置数据源bean -->
<bean id="data" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--
在spring的配置文件中,data的属性数据库驱动与我们以前在db配置文件中的健名不同
在Spring中,是driverClassName,而不是driver。
-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="bjsxt"></property>
</bean>
<!-- 配置工厂bean -->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="data"></property>
</bean>
<!-- 配置mapper扫描bean -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="factory"></property>
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
<!-- 配置业务层bean -->
<bean id="userService" class="com.bjsxt.service.impl.UserServiceImpl">
<property name="userMapper" ref="userMapper"></property>
</bean>
</beans>
(3)配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<!--配置Spring文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext.xml</param-value>
</context-param>
<!--配置监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置Servlet -->
<servlet>
<servlet-name>user</servlet-name>
<servlet-class>com.bjsxt.servlet.UserServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>user</servlet-name>
<url-pattern>/user</url-pattern>
</servlet-mapping>
</web-app>
(4)在servlet中创建Spring容器对象
public class UserServlet extends HttpServlet{
UserService userService;
@Override
public void init() throws ServletException{
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
userService = (UserService) ac.getBean("userService");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {}
}
(5)创建service接口和service实现类
public interface UserService {
void login();
}
public class UserServiceImpl implements UserService{
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper umuserMapper{
this.userMapper= userMapper;
}
@Override
public void login(){
userMapper.login(name,pwd);
}
}
(6)创建mapper接口和mapper.xml
public interface UserMapper {
User login(String uname,String pwd);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjsxt.mapper.UserMapper">
<!-- 用户登录SQL配置 -->
<select id="selUser" resultType="com.bjsxt.pojo.User">
select * from t_user where uname=#{0} and pwd=#{1}
</select>
</mapper>
二、SpringAOP
(一)AOP
AOP(Aspect Oriented Programing,面向切面编程),可以说是OOP(Object Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力,也就是说,OOP允许你定义从上到下的管理,但并不适合定义从左到右的管理。例如日志功能,日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(crosscutting)代码,在OOP设计中,它导致了大量代码冗余,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为"Aspect",即切面。所谓切面,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说"对象"是一个空心的圆柱体,其中封装的是对象的属性和行为,那么面向方便编程的方法,就仿佛一把利刃,将这些空心圆柱体抛开,以获得其内部的消息。而剖开的切面,也就是所谓的切面了。然后它又以巧夺天工的妙手将这些剖开的切面复原,不留痕迹。
使用横切技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。AOP的作用在于分离系统中的各个关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案架构师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑与对其提供支持的通用服务进行分离”。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行,也被称为运行时增强;二是采用静态织入的方式,引入特定的语法创建切面,从而使得编译器可以在编译期间织入有关切面的代码,也称为编译时增强。
切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。切面用Spring的Advisor拦截器实现。
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
通知(Adivce):在特定的连接点,AOP框架执行的动作。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice:BeforAdvice,AfterAdvice,ThrowAdvice和DynamicIntroductionAdiv-ce。
切点(Pointcut):指定一个通知将被引发的一系列连接点的集合(也就是被增强的方法)。AOP框架必须允许开发者指定切点。例如,使用正则表达式。Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解,MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上。
引入(Introduction):添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。Spring中要使用Introduction,可以通过DelegatingIntroductionInterce-ptor来实现通知,通过DefaultIntrodutionAdvisor来配置Advice和代理类要实现的接口。
目标对象(Target Object):包含连接点的对象(真实对象)。也被称作被通知或被代理对象。POJO
AOP代理(AOP Proxy):AOP框架创建的对象(代理对象),包含通知。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving):组装切面来创建一个被通知对象的过程。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯JavaAOP框架一样,在运行时完成织入。
AOP使用场景:AOP用来封装横切关注点,具体可以在下面的场景中使用:Authentication权限、Caching缓存、Context passing内容传递、Error handling错误处理、Lazy loading懒加载、Debugging调试、logging tracing profiling and monitoring记录跟踪优化校准、Performance optimization性能优化、Persistence持久化、Resource pooling资源池、Synchronization同步、Transactions事务。
(二)代理模式
代理模式(Proxy Pattern):所谓代理模式,就是为A提供一个B对象以控制对A的访问。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,并执行现有对象的相关方法,以便向外界提供功能接口。一般代理模式主要解决直接访问对象不合适的情形。比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层,即使用代理模式。
代理模式的应用实例:windows里面的快捷方式,spring aop,远程代理,虚拟代理,防火墙代理等。
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
在代理设计模式中,虽然可以实现在不修改真实方法的源码基础上完成功能的扩展,但是我需要改变调用方式由直接调用真实对象的真实方法,变成调用代理对象的代理方法。为了方便我们进行替换,一般我们会将代理方法和真实方法设计得方法名和形参和返回值一样,为了规范代码的编写,由如下两种实现:
(1)JDK动态代理:真实对象和代理对象实现相同的接口。JDK动态代理只能代理接口,真实对象必须有父接口。
(2)Cglib动态代理:代理对象继承真实对象。Cglib动态代理采用ASM字节码生成框架,使用字节码技术生成代理类,效率更高,但是CGLib不能对final修饰的方法进行代理,因为Cglib是动态生成被代理类的子类,而final修饰的方法不能被继承。
1.基于接口的JDK动态代理
在java的动态代理机制中,有两个重要的类和接口。一个是InvocationHandler、另一个则是Proxy,这一个类和接口是实现动态代理所必须用到的。
(1)InvocationHandler:每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联了一个handler(处理器),当我们动态代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke()方法来进行调用。
//每次生成动态代理类对象时都需要指定一个实现了InvocationHandler接口的调用处理器对象
public class InvocationHandlerImpl implements InvocationHandler {
private Object subject; // 这个就是我们要代理的真实对象,也就是真正执行业务逻辑的类
public InvocationHandlerImpl(Object subject) {// 通过构造方法传入这个被代理对象
this.subject = subject;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
* @param proxy 最终生成的代理类实例
* @param method 被调用的方法对象
* @param args 调用上面method时传入的参数
* @return method对应的方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("当前调用的方法是" + method.getName());
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
result = method.invoke(subject, args);// 需要指定被代理对象和传入参数
System.out.println(method.getName() + "方法的返回值是" + result);
return result;// 返回method方法执行后的返回值
}
}
在源码中invok()方法并没有被显示的调用,而是在生成的代理方法中调用了。在生成的代理方法中是这样的。
public class 代理类 extends Proxy implements 父接口{
public final void bye(){
super.h.invoke(this,m3,null)
}
}
(2)Proxy:这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是newProxyInstance()这个方法:
/**
* loader:一个ClassLoader对象,定义了由哪个ClassLoader来对生成的代理对象进行加载
* interfaces:一个Interfaces对象的数组,表示的是我将要给我需要代理的对象提供一组声明接口,
* 如果我提供了一组接口给他,那么这个代理对象就宣称实现了该接口(多态),这样
* 我就能调用这组接口中的方法了
* h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到
* 哪一个InvocationHand-ler对象上
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException{}
其实我们所说的DynamicProxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interfce给它,然后该class就宣称它实现了这些interface。如此一来,我们可以把该class的实例当做这些interface中的任何一个来用。当然,这个DynaicProxy其实就是一个Proxy,它不会做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管时机的工作。
(3)示例
//需要动态代理的接口
public interface Subject {
public void hello(String name);
public String bye();
}
//被代理类
public class RealSubject implements Subject{
@Override
public void hello(String name) {
System.out.println("hello "+name);
}
@Override
public String bye() {
System.out.println("bye");
return "bye";
}
}
//test
public class Test {
public static void main(String[] args) {
// 被代理的对象
Subject realSubject = new RealSubject();
/**
* 通过InvocationHandlerImpl的构造器生成一个InvocationHandler对象,
* 需要传入被代理对象作为参数
*/
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader loader = realSubject.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
// 需要指定类装载器、一组接口及调用处理器生成动态代理类实例
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
System.out.println("动态代理对象的类型:" + subject.getClass().getName());
subject.hello("Tom");
subject.bye();
}
}
2.基于继承的CGLIB动态代理
前面我们详细的介绍了JDK自身的AOP所系统的一种动态代理的实现,它的实现相对而言是简单的,但是却有一个非常致命性的缺陷,就是只能为接口中的方法完成代理,而委托类自己的方法或者父类中的方法都不可能被代理。CGLB应运而生,它是一个高性能的,底层基于ASM框架的一个代码生成框架,它完美的解决了JDK版本的动态代理只能为借口方法代理的单一性不足问题。CGLIB的底层是基于Enhancer和MethodInterceptor这两个类。
(1)Enhancer:这是CGLIB动态代理中的字节码生成器。首先将被代理的类Student设置成父类,然后设置代理对象的回调对象MyMethodInterceptor,最后执行enhancer.create()动态生成一个代理类,并从Object强制转型为父类型Student。
public class Test{
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Student.class);
enhancer.setCallback(new MyMethodInterceptor());
Student s = (Student)enhancer.create();
s.speak();
s.run();
s.study();
}
}
(2)MethodInterceptor:一个拦截器(父接口Interceptor)。在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口。
public class MyMethodInterceptor implements MethodInterceptor{
//obj真实对象 method真实方法 arg真实方法形参列表 proexy代理方法
public Object intercept(Object obj,Method method,Object[] arg,MethodInterceptor proxy){
System.out.println("Before:"+method);
Object object = proxy.invokeSuper(obj,arg);
System.out.println("After:"+method);
return object;
}
}
(3)示例
//父类
public class Father{
public void sayHello(){
System.out.println("this is father");
}
}
//父接口
public interface Rerson{
void speack();
void run();
}
//委托类(真实对象)
public class Student extends Father implements Person{
public void study(){
System.out.println("i can study,,");
}
@Override
public void speak(){
System.out.println("i am a student,i can speak..");
}
@Override
public void run(){
System.out.println("i am a student,i can run..");
}
}
3.Spring底层的代理模式
SpringAOP默认的代理模式为JDK动态代理,可以在Spring的配置文件中配置代理模式为cglib代理。
proxy-target-class:用来切换JDK动态代理和CGLIB动态代理。默认为false,如果代理对象实现了至少一个接口,Spring会优先选择JDK动态代理,如果没有实现任何接口,则Spring会强制使用CGLIB。
expose-proxy:用来开启基于AspectJ注解的AOP方式。暴露当前的代理对象。SpringAOP只会拦截public方法,不会拦截protected和private方法,并且不会拦截public方法内部调用的其他方法,只会拦截代理对象的方法,通过这个属性就可以暴露出代理对象,拦截器会获取代理对象,并将代理对象转为真实对象,从而对内部调用的方法进行增强。(意思就是,A是真实对象,B是代理对象,a是代理方法,b是代理方法,SpringAOP只会拦截b方法,但是b里调用了a方法,a方法并不会被拦截,而我们真正想要拦截的是a方法,所以开启expose-proxy将a方法暴露出来,这样就可以拦截a方法,对a方法进行增强了。)
<!--设置代理模式为Cglib -->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="false"></aop:aspectj-autoproxy>
(三)SpringAOP
SpringAOP是一个纯JAVA编写的实现了AOP思想的框架。
SpringAOP的实现有三种方式:基于Schem-based、基于AspectJ、基于AspectJ注解。
三者间的关系:Spring是支持AspectJ,而不是依赖于Aspect,不导入AspectJ的jar包,Schem-based方式依然可以使用。基于AspectJ的AOP实现会使用AspectAutoProxyBeanDefinitionParser解析器进行解析。Schem的方式需要创建多个类,AspectJ的方法只用创建一个类,但是AspectJ的方式不方便操作真实对象、代理对象、方法以及返回值,所以建议,如果是增强核心关注点,最好使用schem方式,如果是增加横切关注点,最好使用AspectJ方式。
1.基于Schem-based的AOP
(1)需要导入的jar包
aopalliance.jar
aspectjweaver.jar
commons-logging-1.1.3.jar
spring-aop-4.1.6.RELEASE.jar
spring-aspects-4.1.6.RELEASE.jar
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
(2)创建Person实体类
public class Person {
public String pDemo(){
//int i=5/0;
System.out.println("Person.pDemo()");
return "abc";
}
}
(3)创建通知对象
①前置通知,实现MethodBeforAdvice,重写befor(真实方法对象,真实方法的形参列表,真实对象)
public class MyBefore implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("我是前置通知");
}
}
②后置通知,实现AfterReterningAdvice,重写afterReturning(真实方法的返回值,真实方法对象,真实方法的形参列表,真实对象)
public class MyAfter implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("我是后置通知:"+arg0);
}
}
③环绕通知,实现MethodInterceptor,重写invoke(封装了:真实方法对象,真实方法的形参列表,真实对象,放行)
/**
* 注意:
* 需要放行,如果不放行则会造成真实方法没有执行。
* 环绕通知的声明在applicationcontext.xml中和配置顺序相关。
*/
public class MyRound implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("环绕前");
Object obj = arg0.proceed(); //放行
System.out.println("环绕后:"+obj);
return null;
}
}
④异常通知:实现了ThrowAdvice,重写afterThrowing(抛出异常的类型)
public class MyThrow implements ThrowsAdvice{
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("我是异常通知");
}
}
(4)配置applicationcontext.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置数据源bean -->
<!-- 配置工厂bean -->
<!-- 配置mapperbean -->
<!-- 配置业务层bean -->
<!-- Schema-based方式 -->
<!-- 配置切点bean -->
<bean id="persion" class="com.bjsxt.pojo.Person"></bean>
<!-- 配置前置通知bean -->
<bean id="before" class="com.bjsxt.advice.MyBefore"></bean>
<!-- 配置后置通知bean -->
<bean id="after" class="com.bjsxt.advice.MyAfter"></bean>
<!-- 配置环绕通知bean -->
<bean id="round" class="com.bjsxt.advice.MyRound"></bean>
<!-- 配置异常通知 -->
<bean id="throw" class="com.bjsxt.advice.MyThrow"></bean>
<!-- 声明切面 -->
<aop:config>
<aop:pointcut expression="execution(* com.bjsxt.*.Person.pDemo(..))" id="mp" /><!-- 声明切点 -->
<!-- 环绕通知 -->
<aop:advisor advice-ref="round" pointcut-ref="mp" />
<!-- 前置通知 -->
<aop:advisor advice-ref="before" pointcut-ref="mp" />
<!-- 后置通知 -->
<aop:advisor advice-ref="after" pointcut-ref="mp" />
<!-- 异常通知 -->
<aop:advisor advice-ref="throw" pointcut-ref="mp" />
</aop:config>
</beans>
(5)执行
public class Test {
public static void main(String[] args) {
//SpringAOP-SchemaBased方式:
//获取Spring容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationcontext.xml");
//获取对象
Person p=(Person) ac.getBean("person");
p.pDemo();
}
}
2.基于AspectJ的AOP
(3)创建通知对象
public class MyAdvice {
//前置方法
public void before(String name,int age){
System.out.println("我是前置通知"+name+age);
}
//后置方法
public void after(){
System.out.println("我是后置通知");
}
//环绕通知
public Object myRound(ProceedingJoinPoint p) throws Throwable{
System.out.println("环绕前");
Object obj = p.proceed();
System.out.println("环绕后");
return obj;
}
//异常通知
public void myThrow(){
System.out.println("我是异常通知");
}
}
(4)配置applicationcontext.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- AspectJ方式 -->
<!-- 配置真实对象bean -->
<bean id="persion" class="com.bjsxt.pojo.Person"></bean>
<!-- 配置通知bean -->
<bean id="advice" class="com.bjsxt.advice.MyAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置AspectJ的切面 -->
<aop:aspect ref="advice">
<!-- 配置切点 -->
<aop:pointcut expression="execution(* com.bjsxt.pojo.Person.pDemo(String,int)) and args(name,age)" id="mp">
<!-- 前置通知 -->
<aop:before method="before" arg-names="name,age" pointcut-ref="mp">
<!-- 后置通知:是否出现异常都会执行 -->
<aop:after method="after" pointcut-ref="mp"/>
<!-- 后置通知:出现异常不执行 -->
<aop:after-returning method="after" pointcut-ref="mp"/>
<!-- 环绕通知 -->
<aop:around method="myRound" pointcut-ref="mp"/>
<!-- 异常通知 -->
<aop:after-throwing method="myThrow" pointcut-ref="mp"/>
</aop:aspect>
</aop:config>
</beans>
3.基于AspectJ注解的AOP
注解的配置是基于AspectJ的,也就是我们可以使用注解来替换AspectJ在applicationcontext.xml中的配置信息。
(1)需要在applicaitoncontext.xml文件中声明注解扫描,设置代理模式为cglib。
(2)在真实对象上使用注解:@Compent在类上声明,相当于bean标签。@Pointcut在切面方法上声明,表明该方法为切点
(3)在通知类上使用直接:@Compent在类上声明,相当于bean标签。@Aspect在类上声明,表明该类的对象为通知对象,用来进行功能扩展的。@Befor、@After、@Around、@AfterThrowing。
(3)创建通知对象
@Component
@Aspect
public class MyAdvice {
//前置方法
@Before("com.bjsxt.pojo.Person.pDemo()")
public void before(){
System.out.println("我是前置通知");
}
//后置方法
@After("com.bjsxt.pojo.Person.pDemo()")
public void after(){
System.out.println("我是后置通知");
}
//环绕通知
@Around("com.bjsxt.pojo.Person.pDemo()")
public Object myRound(ProceedingJoinPoint p) throws Throwable{
System.out.println("环绕前");
Object obj = p.proceed();
return obj;
}
//异常通知
@AfterThrowing("com.bjsxt.pojo.Person.pDemo()")
public void myThrow(){
System.out.println("我是异常通知");
}
}
(4)配置applicationcontext.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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 注解方式 -->
<!--声明注解扫描 -->
<context:component-scan base-package="com.bjsxt.advice,com.bjsxt.pojo"></context:component-scan>
<!--设置代理模式 -->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"></aop:aspectj-autoproxy>
</beans>
三、Spring加载properties文件的两种方式
问题:在Spring的配置文件中数据源的相关参数是直接配置在里面的,这样造成数据源的参数和Spring配置文件之间耦合过高,造成Spring的配置文件的通用性降低。
解决:使用Spring加载外部properties文件读取value值
实现:①创建属性配置文件db.properties。②在Spring配置文件中声明属性文件扫描。③在bean标签的属性注入中使用${属性文件健名}的方式获取数据注入给bean。
(一)通过xml方式加载properties文件
1.context:property-placeholder标签:
<!-- 扫描db.properties配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- spring启动时则会去db.properties中找 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
2.使用PropertyPlaceholderConfigurer对象:
用<bean>标签来代替<context:property-placeholder location=" " />,可读性更强:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!--
PropertyPlaceholderConfigurer类中有个locations属性
接收的是一个数组,即我们可以在下面配好多个properties文件
以上方式只能加载单个properties文件
-->
<property name="locations">
<array>
<value>classpath:conn.properties</value>
</array>
</property>
</bean>
注意:属性配置文件扫描注入级别低于依赖注入,属性注入低于属性配置文件扫描注入,我们需要将Factory注入Mapper的级别设置成属性注入级别。(就是说,会先进行依赖注入,再属性配置文件扫描注入,注入factory时,data数据源对象还未创建完成)
<!-- 以前的注入方式 -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="factory"></property>
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
<!-- 换成如下方式 降低factory的注入级别 -->
<bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"></property>
<property name="basePackage" value="com.bjsxt.mapper"></property>
</bean>
(二)通过注解方式加载properties文件
在java代码中使用Value注解来加载配置文件中的值,需要现在applicationcontext.xml中做如下配置:
<!-- 第二种方式是使用注解的方式注入,主要用在java代码中使用注解注入properties文件中相应的value值 -->
<bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<!-- 这里是PropertiesFactoryBean类,它也有个locations属性,也是接收一个数组,跟上面一样 -->
<property name="locations">
<array>
<value>classpath:myProperty.properties</value>
</array>
</property>
</bean>
在java代码中使用直接来获取值:@Value表示去applicationcontext.xml文件中找id="prop"的bean,它是通过注解的方式读取properties配置文件的,然后去相应的配置文件中读取key=filePath的对应的value值。注意要有set方法才能被注入进来,注解写在set方法上即可。
public class FileUploadUtil implements FileUpload {
private String filePath;
@Value("#{prop.filePath}")
public void setFilePath(String filePath) {
System.out.println(filePath);
}
}
总结:以上就是Spring加载properties配置文件的两种方式。实际上,上面基于xml方式中的PropertyPlaceholderConf-igurer类和这里基于注解方式的PropertiesFactoryBean类都是继承PropertiesLoaderSupport,都是用来加载properties配置文件的。
四、Spring的注解
注解的作用:替换配置文件的配置信息,提升开发效率。使用注解可以达到和配置文件相同的效果。Spring的注解的使用:①必须在Spring配置文件中声明注解扫描。使用注解替换XML配置。
(一)bean注解
@compent:声明在普通类上,相当于配置为bean对象,一般用在实体类上。
@service:声明在业务层对象上,相当于配置为bean对象。
@controller:声明在控制器类上,相当于配置为bean对象。
注意:默认类名首字母小写为注解方式声明的bean对象的ID
(二)依赖注入注解
@AutoWire:声明在要被依赖注入的引用类型的属性上,默认使用byType方式注入。
@Resuorce:声明在要被依赖注入的引用类型的属性上,先使用byName,然后使用byType
(三)属性文件注入注解(properties)
@value():声明在实体类的属性上。
@Value("${user.uname}")
private String uname;
五、Spring的事务管理
(一)事务
1.事务的概念
什么是事务?事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务的特性(ACID):
- 原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要不完全不起作用
- 一致性:执行事务前后,数据保持一致
- 隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的
- 持久性:一个事务被提交之后,它对数据库中的数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响
2.Spring事务管理的相关接口
- PlatformTransactionManager:(平台)事务管理器
- TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
- TransactionStatus:事务运行状态
所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。
(1)PlatformTransactionManager接口介绍
Spring并不直接管理事务,而是提供了多种事务管理器。他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。Spring事务管理器的接口是:org.springframework.transaction.PlatformTransactionMana-ger通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了,所各持久化平台框架需要实现这三方法来完成自己数据库的操作。接口中定了三个方法:
Public interface PlatformTransactionManager()...{
//(根据指定的传播行为,返回当前活动的事务或创建一个新事务。)
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// (使用事务目前的状态提交事务)
Void commit(TransactionStatus status) throws TransactionException;
// (对执行的事务进行回滚)
Void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager根据不同持久层框架所对应的接口去实现类,几个比较常见的如下图所示:
事务 | 说明 |
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC或者iBatis进行持久化数据时使用 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 使用Hibernate3.0版本进行持久化数据时使用 |
org.springframework.orm.jpa.JpaTransactionManager | 使用JPA进行数据持久化时使用 |
org.springframework.transaction.jta.JtaTransactionManager | 使用JTA实现来管理事务,在一个事务跨越多个资源时使用 |
比如我们在使用JDBC或者Mybatis进行数据持久化操作时,我们的xml配置通常如下:
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
(2)TransactionDefinition接口介绍
事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到一个事务,这个方法里面的参数是TransactionDefinition,TransactionDefinition接口中定义了5个方法以及一些表示事务属性的常量(隔离级别、传播行为、回滚规则、是否只读、事务超时)。
public interface TransactionDefinition {
//返回事务的传播行为
int getPropagationBehavior();
//返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getIsolationLevel();
//返回事务的名字
String getName();
//返回事务必须在多少秒内完成
int getTimeout();
//返回是否优化为只读事务。
boolean isReadOnly();
}
(3)TransactionStatus接口介绍
TransactionStatus接口用来记录事务的状态:该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PliatformTransactionManager.getTransaction()方法返回一个TransactionStatus对象。返回的TransactionStatus对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。
public interface TransactionStatus{
boolean isNewTransaction(); //是否是新的事务
boolean hasSavepoint(); //是否有恢复点
void setRollbackOnly(); //设置为只回滚
boolean isRollbackOnly(); //是否为只回滚
boolean isCompleted; //是否已完成
}
3.事务隔离级别
事务隔离级别定义了一个事务可能受其他并发事务影响的程度。在典型的应用程序中,多个事务并发进行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作),可能会导致以下的问题:
脏读(Dirty read):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
丢失修改(Lost to modify):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个十五中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这样就发生了一个事务内两次读到的数据时不一样的,因此称为不可重复读。
幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时,在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。(不可重复读的重点是修改,幻读的重点在于新增或者删除。)
TransactionDefinition接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT(默认):使用后端数据库默认的隔离级别,Mysql默认采用的是REPEATABLE_READ隔离界别,Oracle默认采用的是READ_COMMITTED隔离界别
TransactionDefinition.ISOLATION_READ_UNCOMMITTED(读未提交):最低的隔离级别,运毒读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复
TransacitonDefinition.ISOLATION_READ_COMMITTED(读提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
TransactionDefinition.ISOLATION_REPEATALBLE_READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据时被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
TransactionDefinition.ISOLATION_SERIALIZABLE(串行读):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
4.事务传播行为
为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
TransactionDefinition定义中包括了如下几个表示传播行为的常量:
支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常
不支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
其他情况:
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等于TransactionDefinition.PROPAGATION_REQUIRED
这里需要指出的是,前面六种事务传播行为是Spring从EJB中引入的,他们共享相同的概念。而PROPAGATION_NEST-ED是Spring所特有的。以PROPAGATION_NESTED启动的事务内嵌于外部事务中,此时,内嵌事务并不是一个独立地事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。外部事务的回滚也会导致嵌套子事务的回滚。
5.事务超时属性
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在TransactionDefinition中以int的值来表示超时时间,其单位是秒。
6.事务只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、JMS资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在TransactionDefinition 中以boolean类型来表示该事务是否只读。
7.回滚规则
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。但是你可以声明事务在遇到特定的检查型异常时像遇到运行时异常哪样回滚。同样,你还可以声明事务遇到特定的异常不会滚,即使这些异常是运行期异常。
(二)Spring的编程式和声明式事务
Spring支持两种方式的事务管理:
(1)编程式事务管理:通过TransactionTemplate手动管理事务,实际应用中很少使用。
(2)使用XML配置声明式事务:推荐使用(代码侵入性最小),实际是通过AOP实现。
实现声明式事务的四种方式:
(1)基于TransactionInterceptor的声明式事务:Spring声明式事务的基础,通常也不建议使用这种方式,但是与前面一样,了解这种方式对理解Spring声明式事务有很大作用。
(2)基于TransactionProxyFactoryBean的声明式事务:第一种方式的改进版本,简化的配置文件的书写,这是Spring早期推荐的声明式事务管理方式,但是在Spring2.0中已经不推荐了。
(3)基于<tx>和<aop>命名空间的声明式事务管理:目前推荐的方式,其最大特点是与SpringAOP结合紧密,可以充分利用切点表达式的强大支持,使的管理事务更加灵活。
(4)基于@Transactional的全注解方式:将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理Bean的配置,然后在需要实施事务管理的方法或者类上使用@Transactional指定事务规则即可实现事务管理,而且功能也不必其他方式逊色。
1.基于TransactionTemplate的编程式事务管理
(1)业务层方法:
public class OrdersService{
//注入dao层对象
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao){
this.ordersDao = ordersDao;
}
//注入TransactionTemplate对象
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate){
this.transactionTemplate = transactionTemplata;
}
//业务逻辑,写转账业务,调用dao的方法
public void accountMoney(){
tranactionTemplate.execute(new TranactionCallback<Object>(){
@Override
public Object doInTransaction(TransactionStatus status){
Object result = null;
try{
ordersDao.addMoney();
/*
* 转账业务中一个账户需要减少钱,另一个账户需要加钱
* 按照以往的方式,如果不做事务管理,如果从减钱业务到加钱业务之间出现了错误,那么减钱业务执行成功
* 修改了数据,但是加钱业务却没有执行,那么数据就会出错
* 我们希望对这两个业务进行统一管理,如果1个失败则全回滚,所以需要用到事务管理
* 这里我们制造一个0为除数的异常使用TransactionStatus在catch中做回滚处理
*/
int i = 10/0;
oredersDao.reduceMoney();
}catch(Exception e){
status.setRollbackOnly();
result = false;
System.out.println("Transfer Error!");
}
return result;
}
});
}
}
(2)applicationcontext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
>
<!--配置数据源-->
<!--配置工厂-->
<!--配置mapper扫描-->
<!--编程式事务管理-->
<!--配置事务管理器-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器模板-->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<!--注入真正进行事务管理的事务管理器,name必须为transactionManager否则无法注入-->
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
</bean>
<!--对象生成及属性注入-->
<bean id="ordersSerivce" class="com.bjsxt.service.orderService">
<proeprty name="ordersDao" ref="ordersDao"></property>
<!--注入事务管理的模板-->
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
</beans>
2.基于AspectJ<tx>和<aop>命名空间的声明式事务管理
(1)业务层方法:
public class OrdersService{
private OrdersService ordersDao;
public void setOrdersDao(OrdersDao ordersDao){
this.ordersDao = ordersDao;
}
//业务逻辑,写转账业务,调用dao的方法
public void sccountMoney(){
ordersDao.addMoney();
int i = 10/0;
ordersDao.reduceMoney();
}
}
(2)applicationcontext.xml配置文件
<!--配置数据源-->
<!--配置工厂-->
<!--配置mapper扫描-->
<!-- 第一步:配置事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步:配置事务增强 -->
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<!--做事务操作-->
<tx:attributes>
<!--
设置进行事务操作的方法匹配规则
propagetion:事务传播行为
isolation:事务隔离级别
read-only:是否只读
rollback-for:发生哪些异常时回滚
timeout:事务过期时间
-->
<tx:method name="ins*" propagetion="REQUIRED" />
<tx:method name="up*" isolation="DEFAULT" />
<tx:method name="del*" rollback-for="" timeout="-1" />
<tx:method name="sel*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 第三步:配置切面 -->
<aop:config>
<!-- 切点 -->
<aop:pointcut expression="execution(* com.*.service.OrderService.*(..))" id="poincut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="poincut" />
</aop:config>
3.基于@Transactional的全注解声明式事务管理
①业务层方法:
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false,timeout=-1)
public class OrderService{
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao){
this.ordersDao = ordersDao;
}
public void accountMoney(){
ordersDao.addMoney();
int i = 10/0;
ordersDao.reduceMoney();
}
}
②applicationcontext.xml配置文件
<!--配置数据源-->
<!--配置工厂-->
<!--配置mapper扫描-->
<!--第一步:配置事务管理器-->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--第二步:开启事务注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
<!--第三步:在方法所在类上加注解-->
<!-注入对象->