在使用SpringAOP的过程中不知道大家有没有遇到这样一个问题,就是我对一个方法做了AOP的增强,但是在内部方法的调用过程中,却发现它并没有按照预期设定的那样执行AOP的增强。
下面我通过一个例子来说明这种情况。
首先先在数据库中建一个表,这个表只有两列,如图。
然后写一个DataService类,在这个类中,主要实现一个insert方法,在insert方法上加了一个@Transactional的注解;一个insertRollback的方法,在插入数据之后抛出RuntimeException异常,上面加一个注解标明在遇到RuntimeException的异常时做一个rollback回退。
@Transactional
public void insert(int i,String value){
jdbcTemplate.update("insert into springtest values(?,?)",i,value);
}
@Transactional(rollbackFor = RuntimeException.class)
public void insertRollback(int i,String value){
System.out.println("现在是insertRollback方法,插入值为"+value);
jdbcTemplate.update("insert into springtest values(?,?)",i,value);
throw new RuntimeException();
}
然后printAllData方法把表中所有数据都打印出来。
public void printData(){
System.out.println("打印所有数据");
jdbcTemplate.queryForList("select * from springtest")
.forEach(r->System.out.println(r.get("id")+" "+r.get("text")));
}
再通过invokeMethod方法对上面的方法做内部调用。
public void invokeMethod(){
try {
insertRollback(4,"invokeMethod");
}catch (RuntimeException e){
System.out.println("调用invokeMethod方法时异常");
}
}
我们可以看一下用invokeMethod方法来调用insertRollback时跟直接调用insertRollback,这个事务是不是都会生效。
private void demo1(){
s.insert(2,"insert");
s.printData();
try {
s.insertRollback(3,"insertRollback");
}catch (RuntimeException e){
System.out.println("调用insertRollback方法时异常");
}
s.printData();
s.invokeMethod();
s.printData();
}
运行第一个insert方法之后,打印所有数据可以看到表中现在有一个id是2,text是insert的一行数据。
然后执行insertRollback方法插入id是3,text是insertRollback的数据,我们看到数据并没有添加进去,因为我们设定了在插入数据之后抛出RuntimeException异常,所以在插入数据之后,便会回滚。
这是正常使用insertRollback,事务是生效的,与此同时,我们从第三条invokeMethod方法的执行结果来看,id是4,text是invokeMethod的数据插入了表中而没有回滚,说明这种情况下insertRollbcak的事务并没有生效。
学过SpringAOP的话我们都知道,SpringAOP是对我们的Bean做了一个动态的增强,它给我们去做了Proxy的一个对象,其实也就是执行在我的一个代理对象上的一些操作。
如果我是在一个类的内部方法调用,就像上面我用invokeMethod去调用我的insertAndRollBack的时候,程序并没有去走SpringAOP的一个增强逻辑。
知道了原理,那么解决上面这种问题的方法就有了,既然不能直接通过this的这种方式在一个内部方法中去调用AOP增强的方法使其生效,那么就调用SpringAOP增强过的这个Proxy对象。
这里我提供三种方法。让我们回到刚才的例子。
第一种,也是最简单直观的,我去Autowired自己的一个bean进来。
@Autowired
private DataService dataService;
然后写一个invokeWithSelf的方法通过注入的DataService的bean来调用insertRollbcak。
public void invokeWithSelf(){
try {
dataService.insertRollback(5,"invokeWithSelf");
}catch (RuntimeException e){
System.out.println("调用invokeWithSelf方法时异常");
}
}
第二种,通过Spring的ApplicationContextAware的方式,首先把我的ApplicationContext拿到,这里我通过Autowired的方式得到ApplicationContext。
@Autowired
private ApplicationContext applicationContext;
然后写一个invokeWithAC方法通过getBean的方式去拿到我的DataService的bean。
public void invokeWithAC(){
try {
((DataService)applicationContext.getBean("dataService")).insertRollback(6,"invokeWithAC");
}catch (RuntimeException e){
System.out.println("调用invokeWithAC方法时异常");
}
}
第三种可以用一些AOP的辅助方法,可以拿到当前的Proxy的代理对象。
我这里试了一下AOPContext, 这里写了一个invokeWithAopContext的方法,通过调用AopContext.currentProxy(),取到当前bean的代理,最后调用insertRollbcak。
public void invokeWithAopContext(){
try {
((DataService) AopContext.currentProxy()).insertRollback(7,"invokeWithAopContext");
}catch (RuntimeException e){
System.out.println("调用invokeWithAopContext方法时异常");
}
}
但是这边调currentProxy方法时,需要在程序入口类上方设置@EnableAspectJAutoProxy注解的属性exposeProxy 为true才能使其生效。
我们来实验一下。
private void demo2(){
s.invokeWithAopContext();
s.printData();
s.invokeWithSelf();
s.printData();
s.invokeWithAC();
s.printData();
}
从结果可以看到这些方式都可以实现内部调用方法的AOP增强,我个人觉得第一种方法比较方便。