动态代理
动态概述
利用反射机制在运行时创建代理类,对代理类进行增强。
JDK动态代理
//四则运算接口
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
//四则运算实现类
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
/**
* java.lang.reflect Proxy 使用JDK动态代理的对象必须实现一个或多个接口
* static newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
*/
public class CalculatorProxy {
/**
* 获取Calculator代理对象
* @param calculator 被代理对象接口
* @return 代理对象
*
* newProxyInstance(ClassLoader loader, 类加载器
* Class<?>[] interfaces, 类的接口
* InvocationHandler h) 方法执行器-切面类
*/
public static Calculator getProxy(final Calculator calculator) {
return (Calculator) Proxy.newProxyInstance(
calculator.getClass().getClassLoader(),
calculator.getClass().getInterfaces(),
new InvocationHandler() {
/**
*
* @param proxy 代理对象,JDK专用
* @param method 当前将要执行的目标对象的方法
* @param args 目标方法调用时外界传入的参数
* @return 目标方法执行后的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Object result = null;//结果集接收
try {
System.out.println("["+method.getName()+"]方法开始执行,参数为"+ Arrays.asList(args));
//利用反射执行目标方法,指明执行方法的对象,返回执行结果
result = method.invoke(calculator, args);
System.out.println("["+method.getName()+"]方法执行完毕,结果为"+ result);
} catch (Exception e) {
System.err.println("["+method.getName()+"]方法出现异常,异常信息为"+e);
} finally {
System.out.println(method.getName()+"最终执行完毕");
}
return result;
}
}
);
}
}
@Test
public void test1() {
//获得代理的Calculator对象
Calculator calculator = CalculatorProxy.getProxy(new CalculatorImpl());
calculator.div(2, 2);
}
CGLib动态代理
CGLib是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成重写一个子类,对子类进行增强。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
//四则运算实现类
public class CalculatorImpl {
public int add(int i, int j) {
return i + j;
}
public int sub(int i, int j) {
return i - j;
}
public int mul(int i, int j) {
return i * j;
}
public int div(int i, int j) {
return i / j;
}
}
//日志切面类
public class MyAspect {
private Method method;//目标方法
public MyAspect(Method method) {
this.method = method;
}
public void before(Object[] args) {
System.out.println("["+method.getName()+"]方法开始执行,参数为"+ Arrays.asList(args));
}
public void exception(Exception e) {
System.err.println("["+method.getName()+"]方法出现异常,异常信息为"+e);
}
public void after(Object result) {
System.out.println("["+method.getName()+"]方法执行完毕,结果为"+ result);
}
}
public class CalculatorEnhancer implements MethodInterceptor {
/**
* 此方法主要描述如何增强目标类
* @param proxy 代理对象引用
* @param method 被代理对象的方法的引用
* @param args 被代理方法的参数
* @param methodProxy 代理对象对代理对象方法的描述
* @return 返回执行结果
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
//引入增强切面类
MyAspect aspect = new MyAspect(method);
aspect.before(args);
//1.调用代理对象方法,代理对象是目标对象的子类,执行父类方法
//methodProxy.invokeSuper(proxy, args);
Object invoke = null;
try {
//2.直接调用代理对象执行目标方法
//invoke = method.invoke(target, args);
invoke = methodProxy.invokeSuper(proxy, args);
} catch (Exception e) {
aspect.exception(e);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
aspect.after(invoke);
return invoke;//返回执行结果
}
}
public class CalculatorProxy {
//获取Calculator代理对象
public Object getProxy(Object target) {
//使用CGLib增强被代理对象,创建代理工厂
Enhancer enhancer = new Enhancer();
//生成目标对象类的子类进行增强,指定目标类的类型
enhancer.setSuperclass(target.getClass());
//制定增强方案,实现MethodInterceptor接口,重写intercept方法增强
enhancer.setCallback(new CalculatorEnhancer());//Callback指定实现MethodInterceptor的增强描述类
return enhancer.create();//生成目标类对象的代理增强对象并返回
}
}
public class App1 {
@Test
public void test1() {
CalculatorProxy calculatorProxy = new CalculatorProxy();
CalculatorImpl proxy = (CalculatorImpl) calculatorProxy.getProxy(new CalculatorImpl());
proxy.add(1,2);
proxy.div(3,0);
}
}
AOP
AOP概述
AOP(面向切面编程):
通过预编译方式和程序运行期间动态代理,在不修改源码的前提下能对方法进行增强的技术。
AOP术语
1.连接点(joinpoint)
连接点描述的是程序执行的某个特定位置,比如某个方法调用前,调用后,出现异常等。具有边界特征的特定点称为连接点。
2.切入点(pointcut)
切入点是连接点的过滤条件,AOP通过切入点定位到特定的连接点,一个切入点可以切入多个连接点。
3.通知(Advice)
切面在切入点采取的具体行为称为通知。切面的核心逻辑都写在通知上,通知是切面功能的具体实现。
4.通知器(Advisor)
通知器由一个切入点和一个通知组成,对方法进行增强。
5.切面(Aspect)
与通知器类似都是通知+切入点。区别在于,切面中的类无需实现通知接口,但需要在配置文件中指定类中的方法名,
而通知器仅需指定类名即可,因为通知器中的类都实现了通知接口,很明确的知道通知方法是哪个。
6.目标(target)
被通知的对象(方法)。
7.代理(Proxy)
向目标对象应用通知之后创建的对象。
8.织入(weaving)
把切面类应用到目标类创建新的代理对象的过程。
9.引入(introduction)
允许我们向现有的类中新增属性和方法。
Maven依赖
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.3</version>
<scope>test</scope>
</dependency>
</dependencies>
计算器日志(注解)
<!-- 扫描目标类和切面类 -->
<context:component-scan base-package="com.example"/>
<!-- 开启注解的AOP功能 -->
<aop:aspectj-autoproxy/>
//累加计算器
@Component
public class AccumulationCalculator {
public int count(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
}
/**
* @Berfore 前置通知
* @AfterReturning 后置通知
* @AfterThrowing 异常通知
* @After 返回通知
* @Around 环绕通知
* 切入点表达式 value = "execution(public int com.example.AccumulationCalculator.count(int))"
* 权限修饰符(不写默认匹配所有权限) 返回值类型 全类名 方法名(参数类型1,参数类型2,...)
* 切入点表达式通配符 *:匹配一个或多个(一层路径) ..:匹配任意多个参数和任意类型(多层路径)
* JoinPoint:获取目标方法参数信息,getSignature获取方法签名,getArgs获取方法参数
* returning = "result"(接收返回值) throwing = "e":返回异常信息
*/
@Aspect
@Component
public class AccumulationCalculatorEnhancer {
//运算前计时器
private static Long beforeTime = 0L;
/**
* 抽取可重用切入点表达式,声明一个空方法
* 标注PointCut注解,指定切入点
* 在通知方法的Value指定切入点方法
*/
@Pointcut(value = "execution(public int com.example.service.AccumulationCalculator.count(int))")
public void aopEdge() {}
//计算开始前
@Before(value = "aopEdge()")
public void countBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();//获取目标方法签名
Object[] args = joinPoint.getArgs();//获取参数
System.out.println("["+signature.getName()+"]准备执行,参数为"+ Arrays.asList(args));
beforeTime = System.currentTimeMillis();
}
//计算正常结束
@AfterReturning(value = "aopEdge()", returning = "result")
public void countAfter(JoinPoint joinPoint, Object result) {
System.out.println("本次计算消耗" + (System.currentTimeMillis() - beforeTime)+"毫秒");
Signature signature = joinPoint.getSignature();//获取目标方法签名
System.out.println("["+signature.getName()+"]正常执行完毕,结果为"+ result);
}
//计算异常处理
@AfterThrowing(value = "aopEdge()", throwing = "e")
public void countException(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();//获取目标方法签名
System.err.println("["+signature.getName()+"]发生了异常,异常信息为"+e);
}
//程序最终结束
@After(value = "aopEdge()")
public void countFinally() {
System.out.println("程序运行完毕");
}
/**
* 环绕通知(等同于动态代理)
* @param proceedingJoinPoint 切入点进程切入点(非常强大)
* 环绕前置 -> 普通前置 -> 目标方法 -> 环绕返回/异常 -> 环绕后置 -> 普通后置 -> 普通返回/异常
* 如果环绕通知捕获了异常必须放行异常,否则普通通知无法捕获异常
*/
@Around(value = "aopEdge()")
public Object countAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object invoke = null;//接收返回值
System.err.println("[Around]前置");
try {
Object[] args = proceedingJoinPoint.getArgs();//获取参数
invoke = proceedingJoinPoint.proceed(args);//利用反射执行目标方法
System.err.println("[Around]正常返回");
} catch (Exception e) {
System.err.println("[Around]异常");
} finally {
System.err.println("[Around]后置");
}
return invoke;//返回反射调用后的返回值
}
}
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"classpath:application1.xml"})
public class App1 {
@Autowired
private AccumulationCalculator accumulationCalculator;
@Test
public void test1() {
System.out.println("对象信息:"+accumulationCalculator.getClass());
System.out.println(accumulationCalculator.count(100));
}
}
计算器日志(XML)
public class AccumulationCalculatorEnhancer {
//运算前计时器
private static Long beforeTime = 0L;
//计算开始前
public void countBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();//获取目标方法签名
Object[] args = joinPoint.getArgs();//获取参数
System.out.println("["+signature.getName()+"]准备执行,参数为"+ Arrays.asList(args));
beforeTime = System.currentTimeMillis();
}
//计算正常结束
public void countAfter(JoinPoint joinPoint, Object result) {
System.out.println("本次计算消耗" + (System.currentTimeMillis() - beforeTime)+"毫秒");
Signature signature = joinPoint.getSignature();//获取目标方法签名
System.out.println("["+signature.getName()+"]正常执行完毕,结果为"+ result);
}
//计算异常处理
public void countException(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();//获取目标方法签名
System.err.println("["+signature.getName()+"]发生了异常,异常信息为"+e);
}
//程序最终结束
public void countFinally() {
System.out.println("程序运行完毕");
}
public Object countAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object invoke = null;//接收返回值
System.err.println("[Around]前置");
try {
Object[] args = proceedingJoinPoint.getArgs();//获取参数
invoke = proceedingJoinPoint.proceed(args);//利用反射执行目标方法
System.err.println("[Around]正常返回");
} catch (Exception e) {
System.err.println("[Around]异常");
} finally {
System.err.println("[Around]后置");
}
return invoke;//返回反射调用后的返回值
}
}
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 向容器添加目标类(累加计算器) -->
<bean id="accumulationCalculator" class="com.example.service.AccumulationCalculator"/>
<!-- 向容器添加切面类(累加计算器增强类) -->
<bean id="accumulationCalculatorEnhancer" class="com.example.aspect.AccumulationCalculatorEnhancer"/>
<!-- Spring AOP配置 -->
<aop:config>
<!-- 抽取公共切入点表达式 -->
<aop:pointcut id="aopEdge"
expression="execution(public int com.example.service.AccumulationCalculator.count(int))"/>
<!-- 告诉Spring容器AccumulationCalculatorEnhancer是切面类 -->
<aop:aspect ref="accumulationCalculatorEnhancer" order="1">
<aop:before method="countBefore" pointcut-ref="aopEdge"/>
<aop:after-returning method="countAfter" pointcut-ref="aopEdge" returning="result"/>
<aop:after-throwing method="countException" pointcut-ref="aopEdge" throwing="e"/>
<aop:after method="countFinally" pointcut-ref="aopEdge"/>
<aop:around method="countAround" pointcut-ref="aopEdge"/>
</aop:aspect>
</aop:config>
</beans>
多切面切入
//切入面类B:两个切面同时切入一个方法时默认会比较切面类类名ASCII值,先执行的切面会在最后结束通知,中间其他切面先结束。
//可以通过在类上添加@Order(int)值改变其切面执行优先级,数值越小优先级越高,默认值Integer.MAX_VALUE
@Aspect
@Component
public class AspectB {
//运算前计时器
private static Long beforeTime = 0L;
/**
* 抽取可重用切入点表达式,声明一个空方法
* 标注PointCut注解,指定切入点
* 在通知方法的Value指定切入点方法
*/
@Pointcut(value = "execution(public int com.example.service.AccumulationCalculator.count(int))")
public void aopEdge() {}
//计算开始前
@Before(value = "aopEdge()")
public void countBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();//获取目标方法签名
Object[] args = joinPoint.getArgs();//获取参数
System.err.println("["+signature.getName()+"]准备执行,参数为"+ Arrays.asList(args));
beforeTime = System.currentTimeMillis();
}
//计算正常结束
@AfterReturning(value = "aopEdge()", returning = "result")
public void countAfter(JoinPoint joinPoint, Object result) {
System.err.println("本次计算消耗" + (System.currentTimeMillis() - beforeTime)+"毫秒");
Signature signature = joinPoint.getSignature();//获取目标方法签名
System.err.println("["+signature.getName()+"]正常执行完毕,结果为"+ result);
}
//计算异常处理
@AfterThrowing(value = "aopEdge()", throwing = "e")
public void countException(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();//获取目标方法签名
System.err.println("["+signature.getName()+"]发生了异常,异常信息为"+e);
}
//程序最终结束
@After(value = "aopEdge()")
public void countFinally() {
System.err.println("程序运行完毕");
}
}
Spring 事务
Maven依赖
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 增加maven编译插件,设置编译版本,防止刷新后变为jdk5-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
dp.properties
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://47.172.193.151:3306/st01
dataSource.username=root
dataSource.password=123456
applicationContext.xml
<!-- 扫描Dao组件和Service组件 -->
<context:component-scan base-package="com.example"/>
<!-- 加载mysql配置文件 -->
<context:property-placeholder location="classpath:dp.properties"/>
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${dataSource.driverClassName}"/>
<property name="url" value="${dataSource.jdbcUrl}"/>
<property name="username" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
</bean>
<!-- 配置JdbcTemplate操作数据库,引用Druid数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!-- 配置事务管理器(事务切面) -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制数据源 -->
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!-- 开启基于注解驱动的事务控制,依赖tx名称空间 -->
<tx:annotation-driven transaction-manager="tm"/>
Dao
public interface EmployeeDao {
/**
* 根据员工姓名更新员工余额
* @param name 员工姓名
* @param money 更新金额数目
*/
void updateBalanceByName(String name, Double money);
}
@Repository
public class EmployeeDaoImpl implements EmployeeDao {
private JdbcTemplate jdbcTemplate;//引入JdbcTemplate
public EmployeeDaoImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void updateBalanceByName(String name, Double money) {
String sql = "update employee set balance = balance - ? where emp_name = ?;";
jdbcTemplate.update(sql, money, name);
}
}
Service
public interface EmployeeService {
/**
* 模拟交易场景
* @param consumer 消费者
* @param business 商家
* @param money 交易金额
*/
void transferAccounts(String consumer, String business, Double money);
}
@Service
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeDao employeeDao;//引入Dao组件
public EmployeeServiceImpl(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
/**
* 声明式事务参数细节
* timeout int:单位是秒
* readonly boolean:设置事务只读属性
* noRollbackFor Class[]:设置哪些异常可以不回滚,指定类反射实例
* noRollbackForClassName String[]:设置哪些异常可以不回滚,全类名
* RollbackFor Class[]:设置哪些异常可以回滚,指定类反射实例
* RollbackForClassName String[]:设置哪些异常可以回滚,全类名
* isolation:隔离级别,默认值是默认数据库隔离级别
* -1 Isolation.DEFAULT:MySQL默认隔离级别可重复读
* 1 Isolation.READ_UNCOMMITTED:读未提交
* 2 Isolation.READ_COMMITTED:读已提交
* 4 Isolation.REPEATABLE_READ:可重复读
* 8 Isolation.SERIALIZABLE:串行化
* propagation:事务传播行为
* 当一个事务方法被另外一个事务方法调用时,要指定事务如何传播。
* 1.Propagation.REQUIRED 如果有事务运行,当前方法就在该事务内,否则自己开一个事务
* 2.Propagation.REQUIRED_NEW 当前方法必须自己开一个事务,之前运行的事务挂起
* 3.Propagation.SUPPORTS 如果有事务运行就运行在该事务内,否则就不使用事务
* 4.Propagation.NOT_SUPPORTS 该方法不运行在事务内,如果有事务运行就挂起
* 5.Propagation.MANDATORY 该方法必须运行在事务内,如果没有事务抛异常
* 6.Propagation.NEVER 当前方法不应该运行在事务内,否则抛异常
* 注意:
* 1.如果子事务REQUIRED继承一个主事务,那么会继承所有配置,REQUIRED_NEW可以自定义配置
* 2.如果主事务和子事务在同一个类,那么子事务会被看成普通方法,和主事务是一个事务,不走
* 代理对象,如果子事务设置REQUIRED_NEW不会生效
*/
@Override
@Transactional(propagation = Propagation.NESTED)
public void transferAccounts(String consumer, String business, Double money) {
employeeDao.updateBalanceByName(consumer, money);//消费者付款
//System.out.println(3 / 0);//异常模拟
employeeDao.updateBalanceByName(business, -money);//商家收到钱
}
}
Spring Test
@RunWith(value = SpringRunner.class)
@ContextConfiguration(locations = {"classpath:application1.xml"})
public class App1 {
@Autowired
private EmployeeService employeeService;
@Test
public void test() {
employeeService.transferAccounts("Jack","Maria",300.00);
}
}
事务控制(XML)
<!-- 扫描Dao组件和Service组件 -->
<context:component-scan base-package="com.example"/>
<!-- 加载mysql配置文件 -->
<context:property-placeholder location="classpath:dp.properties"/>
<!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${dataSource.driverClassName}"/>
<property name="url" value="${dataSource.jdbcUrl}"/>
<property name="username" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
</bean>
<!-- 配置JdbcTemplate操作数据库,引用Druid数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!-- 注入EmployeeDao,依赖注入jdbcTemplate -->
<bean id="employeeDao" class="com.example.dao.impl.EmployeeDaoImpl">
<constructor-arg name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!-- 注入EmployeeService,依赖注入EmployeeDao -->
<bean id="employeeService" class="com.example.service.impl.EmployeeServiceImpl">
<constructor-arg name="employeeDao" ref="employeeDao"/>
</bean>
<!-- 配置事务管理器(事务切面) -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制数据源 -->
<property name="dataSource" ref="druidDataSource"/>
</bean>
<!-- AOP切面配置 -->
<aop:config>
<!-- 配置事务切入点 -->
<aop:pointcut id="txPoint" expression="execution(public void com.example.service.impl.EmployeeServiceImpl.transferAccounts(String, String, Double))"/>
<!-- 事务增强:引用事务管理器和切入点 -->
<aop:advisor advice-ref="txManager" pointcut-ref="txPoint"/>
</aop:config>
<!-- 指定配置那个事务管理器,指定需要使用事务的切入点方法,配置事务属性 -->
<tx:advice id="txManager" transaction-manager="tm">
<tx:attributes>
<tx:method name="transferAccounts"
isolation="READ_COMMITTED"
timeout="3"
propagation="REQUIRES_NEW"
no-rollback-for="FileSystemException.class"
rollback-for="NullPointerException.class"
read-only="true"
/>
</tx:attributes>
</tx:advice>
事务传播图解