場景一:使用this調用被增強的方法
下面是一個類里面的一個增強方法
@Service
public class MyService implements CommandLineRunner {private MyService myService;public void performTask(int x) {System.out.println("Executing performTask method");if(x<5) this.performTask(1+x);
// if(x<5) myService.performTask(1+x);}@Overridepublic void run(String... args) throws Exception {myService = AopTestApplication.getBean(MyService.class);}
}
增強邏輯是在方法調用的前后輸出各一行語句
@Aspect
@Component
public class LoggingAspect {@Pointcut("execution(* com.example.aoptest.MyService.performTask(..))")public void performTaskPointcut() {// Pointcut for performTask method}@Before("performTaskPointcut()")public void beforePerformTask() {System.out.println("方法調用前");}@After("performTaskPointcut()")public void afterPerformTask() {System.out.println("方法調用后");}
}
運行項目并在Controller層調用方法得到的輸出如下
可以看出只有第一次調用是執行了增強邏輯的,剩下那些this調用都沒有。
原因
aop代理的原理:使用AOP對某一個Bean的方法進行增強之后,放進IOC容器的這個Bean不會是原本的類實例,而是專門創建的一個代理對象Bean.這個代理對象內部有對原始Bean的引用,在用原始Bean調用方法前會先執行增強邏輯。這里就是相當于加了一層。
用this.失效的原因: 用this.xxx相當于直接調用了原始Bean的方法,而不是外層的代理Bean的方法。示意圖如下,左邊的是用代理對象調用方法,右邊的是在原始Bean的任意方法內部用this.調用增強方法。
解決方案
將this.調用改為Bean調用
代碼如下。
必須從IOC容器里面直接獲取到Bean對象,而不是使用@Autowried注解注入,否則會出現循環依賴的報錯。
@SpringBootApplication
public class AopTestApplication implements ApplicationContextAware {private static ApplicationContext context;public static void main(String[] args) {SpringApplication.run(AopTestApplication.class, args);}public static <T> T getBean(Class<T> beanClass) {return context.getBean(beanClass);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}
}
@Service
public class MyService implements CommandLineRunner {private MyService myService;public void performTask(int x) {System.out.println("Executing performTask method");if(x<5) myService.performTask(1+x);}@Overridepublic void run(String... args) throws Exception {myService = AopTestApplication.getBean(MyService.class);}
}
這次的輸出如下,可以看見每一次方法調用都有執行增強邏輯。
場景二:增強方法使用private修飾
@RestController
@Service
public class MyService implements CommandLineRunner {private MyService myService;@PostMapping("test/{x}")private void performTask(@PathVariable("x")int x) {System.out.println("Executing performTask method");if(Objects.isNull(myService))System.out.println("222222");if(x<5) myService.performTask(1+x);}@Overridepublic void run(String... args) throws Exception {myService = AopTestApplication.getBean(MyService.class);if(Objects.isNull(myService))System.out.println("1111111");}
}
上面代碼的輸出如下,可以看見并沒有輸出增強邏輯。并且在使用myService調用方法時還報錯myService為空。明明run方法里面都已經注入了Bean.
原因
aop代理無法代理私有方法?代理對象無法通過原始Bean調用里面的私有方法?
同一個類里面存在一個私有和公有的兩個增強方法時如下
@RestController
@Service
public class MyService implements CommandLineRunner {private MyService myService;@PostMapping("test/{x}")private void performTask(@PathVariable("x")int x) {System.out.println("Executing performTask method");if(Objects.isNull(myService))System.out.println("222222");if(x<5) myService.performTask(1+x);}@PostMapping("test2/{x}")public void performTask2(@PathVariable("x")int x) {System.out.println("Executing performTask method2");if(Objects.isNull(myService))System.out.println("222222");if(x<5) myService.performTask2(1+x);}@Overridepublic void run(String... args) throws Exception {myService = AopTestApplication.getBean(MyService.class);if(Objects.isNull(myService))System.out.println("1111111");}
}
先后調用方法2和方法1得到輸出如下
結果是方法2一切正常,方法1還是有問題。
?奇怪的點是方法2里面測出局部變量myService是非空的,但是方法1里面卻是空的????
解決方案
將private改成public,現在可以看見增強邏輯都正常輸出了。
,
AOP失效會導致的問題
估計所有依賴于AOP實現的功能都會有問題
首當其沖的就是事務注解@Transactional,事務注解里面也是依賴了AOp,在方法調用前開啟事務,在方法調用后進行回滾或者提交事務。
但是像上面失效的場景下就事務注解就會失效,試想,在一個方法內用this調用了同一個類中的一個事務方法,那這個事務方法的事務就會失效,里面的事務方法報錯回滾會無法回滾。
事務失效場景一:拋出檢查異常checked
默認情況下,Spring事務管理器只在拋出未檢查(unchecked)異常(即RuntimeException及其子類)時才會回滾事務。如果方法拋出的是檢查(checked)異常,事務不會回滾,除非你顯式配置了事務注解的rollbackFor屬性。
如下所示,我在業務代碼中手動拋出了一個自定義的檢查異常,報錯是報錯了,但是事務并沒有回滾。
@Service
public class UserServiceImpl implements UserService , CommandLineRunner {public class MyCheckedException extends Exception {public MyCheckedException(String message) {super(message);}}private UserMapper userMapper;private UserService userService;@Override@Transactionalpublic void createUser(String name) throws MyCheckedException{user user = new user();user.setName(name);userMapper.insert(user);throw new MyCheckedException("This is a custom checked exception");}@Overridepublic void run(String... args) throws Exception {userMapper = AopTestApplication.getBean(UserMapper.class);userService = AopTestApplication.getBean(UserService.class);}
}
原因
spring事務默認是只在拋出未檢查異常時才會進行回滾。
解決方案
顯式配置rollback屬性
在上面代碼的事務注解改成如下,這樣子無論是什么異常都會自動回滾了
@Transactional(rollbackFor = Exception.class)
當然,還有一個屬性選擇是Throwable,?這個是Exception和Error的父類,這樣不管是拋出異常還是錯誤都會進行回滾了,正常來想,應該也是要這樣設置的。
@Transactional(rollbackFor = Throwable.class)
事務失效場景二:捕獲異常之后不拋出
@Override@Transactional(rollbackFor = Exception.class)public void createUser(String name) throws MyCheckedException{user user = new user();user.setName(name);userMapper.insert(user);try{throw new MyCheckedException("This is a custom checked exception");}catch(Exception ex){}}
如上所示,沒有將異常拋出就不會觸發回滾。
解決方案
記得拋出異常
@Override@Transactional(rollbackFor = Exception.class)public void createUser(String name) throws MyCheckedException{user user = new user();user.setName(name);userMapper.insert(user);try{throw new MyCheckedException("This is a custom checked exception");}catch(Exception ex){throw ex;}}
事務失效場景三:使用了非public方法
Spring的事務管理依賴于AOP代理,而AOP只能代理public方法。如果你在非public方法上使用了@Transactional注解,這個注解將不起作用,事務也不會生效。
原理和上面aop失效的原理一樣。
事務失效場景四:內部方法調用
如果在同一個類中,一個方法調用了另一個帶有@Transactional注解的方法,Spring的AOP代理將無法攔截這個內部調用,導致事務注解失效。解決方法之一是將被調用的方法提取到另一個bean中。
原理也和上面aop使用this調用失效的原理一樣。
代碼如下:
一個普通方法調用了一個事務方法,并且事務方法里面手動拋出異常,正常來說應該回滾的,但是因為是this.調用,aop失效就無法回滾了。
@Service
public class UserServiceImpl implements UserService , CommandLineRunner {public class MyCheckedException extends Exception {public MyCheckedException(String message) {super(message);}}private UserMapper userMapper;private UserService userService;@Overridepublic void createUser(String name) throws MyCheckedException {user user = new user();user.setName(name);
// userMapper.insert(user);this.test();}@Transactional(rollbackFor = Exception.class)public void test() throws MyCheckedException {user user = new user();user.setName(new Date().toString());userMapper.insert(user);try{throw new MyCheckedException("This is a custom checked exception");}catch(Exception ex){throw ex;}}@Overridepublic void run(String... args) throws Exception {userMapper = AopTestApplication.getBean(UserMapper.class);userService = AopTestApplication.getBean(UserService.class);}
}
解決方案一
將this.調用換成Bean調用
解決方案二
使用事務方法調用事務方法,這樣子即使是this調用也會回滾了。
代碼如下所示,但是這個的本質是外層事務方法進行的回滾,里面調用的事務方法并沒有執行回滾。
@Override@Transactional(rollbackFor = Exception.class)public void createUser(String name) throws MyCheckedException {user user = new user();user.setName(name);
// userMapper.insert(user);this.test();}@Transactional(rollbackFor = Exception.class)public void test() throws MyCheckedException {user user = new user();user.setName(new Date().toString());userMapper.insert(user);try{throw new MyCheckedException("This is a custom checked exception");}catch(Exception ex){throw ex;}}
事務失效場景五:事務傳播屬性設置不當
事務失效場景六:不同的事務管理器
如果應用中有多個數據源,可能會有多個事務管理器。如果在配置事務管理器時沒有指定正確的事務管理器,可能會導致事務失效。