寫在前面
本文是部落客在看完面向切面的Spring(《Spring實戰》第4章)後的一些實踐筆記。
為什麼要用AOP呢?作者在書中也明确提到了,使用AOP,可以讓代碼邏輯更多的去關注自己本身的業務,而不用混雜和關注一些其它的東西。包括:安全,緩存,事務,日志等等。
名詞概念
- 通知(Advice)
定義了切面做什麼和什麼時候去做。簡單點來說,就是AOP執行時會調用的方法,通知除了定義切面要完成的工作(What),還會定位什麼時候(When)去履行這項工作,是在方法調用前,還是調用之後,還是前後都是,還是抛出異常時
在切面定義中,一共有以下五種通知類型
類型 | 作用 |
---|---|
Before | 某方法調用之前發出通知 |
After | 某方法完成之後發出通知,不考慮方法運作的結果 |
AfterReturning | 将通知放置在被通知的方法成功執行之後 |
AfterThrowing | 将通知放置在被通知的方法抛出異常之後 |
Around | 通知包裹在被通知的方法的周圍,在方法調用之前和之後發出(環繞通知 = 前置 + 目标方法執行 + 後置通知) |
- 切點,也叫切入點(Pointcut)
上面說的連接配接點的基礎上,來定義切入點,你的一個類裡,有15個方法,那就有十幾個連接配接點了對吧,但是你并不想在所有方法附件都使用通知(使用叫織入,下面再說),你隻是想讓其中幾個,在調用這幾個方法之前、之後或者抛出異常時幹點什麼,那麼就用切入點來定義這幾個方法,讓切點來篩選連接配接點,選中那幾個你想要的方法
- 連接配接點,也叫參加點(JoinPoint)
連接配接點是切面在應用程式執行過程中插入的地方,可能是方法調用(前、後)的時候,也可能是異常抛出的時候。連接配接點如果可以說是切點的全集,那麼切點就是連接配接點的子集
- 切面(Aspect)
切面其實就是通知和切點的結合。通知說明了幹什麼和什麼時候幹(通過方法上使用@Before、@After等就能知道),則切點說明了在哪幹(指定到底是哪個方法),這就組成了一個完整的切面定義
Spring對AOP的支援
- Spring建議在Java中編寫AOP,雖然用XML也可以實作
- Spring通過使用代理類,在運作階段将切面編織進bean中
- Spring隻支援方法級别的連接配接點,不像AspectJ還可以通過構造器或屬性注入
切點表達式
切點表達式算是一些比較概念性的知識,下面截了兩個圖供大家參考參考
切點表達式1
切點表達式2
看得頭暈了吧,不過好在隻有execution()是用來執行比對的,剩下的都是為了限制或定制連接配接點要比對的位置
以下是execution()定義的格式(其中,帶?号的為可選,否則必須給出) :
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
還是舉個真實栗子模仿一下吧
execution(* com.example.aspectj.UserDao.updateName(..))
- execution:用于定義什麼方法執行時會被觸發,這裡是指com.example.aspectj包下的UserDao接口中的updateName方法執行時觸發
- * :忽略方法傳回值類型
- (..) :比對任意參數
實戰測試(SpringBoot + JPA)
- Create Entity
@Table(name = "tb_user")
@Entity
@Data
public class User {
@Id
@GeneratedValue
private Integer id;
private String name;
}
- Create Dao
public interface UserDao extends JpaRepository<User, Integer> {
@Modifying
@Transactional
@Query("update User u set u.name = ?1 where u.id = ?2")
int updateName(String name, int id);
}
- Create Service
@Service
public class UserService {
@Resource
private UserDao userDao;
@Transactional
public void save(User user) {
userDao.save(user);
}
public void update(String name, int id) {
userDao.updateName(name, id);
}
}
第一種風格的切面
- Create Aspect(使用了@Before、@After、@AfterReturning和@AfterThrowing這四個注解)
@Aspect
public class UserAspectjOne {
@Resource
private UserService userService;
@Before("execution(* com.example.aspectj.UserDao.updateName(..))")
public void before() {
System.out.println("1.------------before()");
}
@After("execution(* com.example.aspectj.UserDao.updateName(..))")
public void after() {
System.out.println("1.------------after()");
}
@AfterReturning("execution(* com.example.aspectj.UserDao.updateName(..))")
public void afterReturning() {
System.out.println("1.------------afterReturning()");
User user = new User();
user.setName("afterReturning1");
userService.save(user);
}
@AfterThrowing("execution(* com.example.aspectj.UserDao.updateName(..))")
public void afterThrowing() {
System.out.println("1.------------afterThrowing()");
User user = new User();
user.setName("afterThrowing1");
userService.save(user);
}
}
- Create Configuration
@Configuration
// @EnableAspectJAutoProxy //實測可以不添加該注解,因為SpringBoot中已經預設開啟了AOP功能
public class AspectjConfiguration {
@Bean
public UserAspectjOne userAspectjOne() {
return new UserAspectjOne();
}
}
SpringBoot已經預設開啟了aop
- Test updateName() with UserAspectjOne
- 6.1 先往資料庫裡添加一條資料
添加User@Test public void testAdd() { User user = new User(); user.setName("jared"); userDao.save(user); }
- 6.2 測試正常執行updateName()
``` java
@Test
public void testUpdateName() {
userService.update("jared qiu", 1);
}
```
- 6.3.1 列印結果
![輸出結果][5]
- 6.3.2 資料庫結果
![資料庫結果][6]
- 6.4 測試非正常執行updateName(),隻需要把UserDao類中updateName()上的@Modifying或者@Transactional注解去掉即可
``` java
@Test
public void testUpdateName() {
userService.update("error jared qiu", 1);
}
```
- 6.5.1 列印結果
![輸出結果][7]
- 6.5.2 資料庫結果
![資料庫結果][8]
第二種風格的切面
- Create Aspect(依舊使用了@Before、@After、@AfterReturning和@AfterThrowing這四個注解,但新增了@Pointcut注解,把切面的定義抽離了出來進行統一)
@Aspect
public class UserAspectjTwo {
@Resource
private UserService userService;
@Pointcut("execution(* com.example.aspectj.UserDao.updateName(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before() {
System.out.println("2.------------before()");
}
@After("pointcut()")
public void after() {
System.out.println("2.------------after()");
}
@AfterReturning("pointcut()")
public void afterReturning() {
System.out.println("2.------------afterReturning()");
User user = new User();
user.setName("afterReturning2");
userService.save(user);
}
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("2.------------afterThrowing()");
User user = new User();
user.setName("afterThrowing2");
userService.save(user);
}
}
@Configuration
public class AspectjConfiguration {
@Bean
public UserAspectjTwo userAspectjTwo() {
return new UserAspectjTwo();
}
}
- Test updateName() with UserAspectjTwo
@Test public void testAdd() { User user = new User(); user.setName("jared"); userDao.save(user); }
- 6.2 測試正常執行updateName()
``` java
@Test
public void testUpdateName() {
userService.update("jared qiu", 1);
}
```
- 6.3.1 列印結果
![輸出結果][10]
- 6.3.2 資料庫結果
![資料庫結果][11]
- 6.4 測試非正常執行updateName(),隻需要把UserDao類中updateName()上的@Modifying或者@Transactional注解去掉即可
``` java
@Test
public void testUpdateName() {
userService.update("error jared qiu", 1);
}
```
- 6.5.1 列印結果
![輸出結果][12]
- 6.5.2 資料庫結果
![資料庫結果][13]
第三種風格的切面
- Create Aspect(使用了@Around這個環繞注解)
@Aspect
public class UserAspectjThree {
@Resource
private UserService userService;
/**
* 方法的傳回值類型須與切面所在方法的傳回值類型保持一緻
*/
@Around("execution(* com.example.aspectj.UserDao.updateName(..))")
public int around(ProceedingJoinPoint joinPoint) {
try {
System.out.println("3.------------before()");
System.out.println("3.------------after()");
joinPoint.proceed();//用于啟動目标方法執行(必須)
System.out.println("3.------------afterReturning()");
User user = new User();
user.setName("afterReturning3");
userService.save(user);
} catch (Throwable e) {
System.out.println("3.------------afterThrowing()");
User user = new User();
user.setName("afterThrowing3");
userService.save(user);
}
return 1;
}
}
@Configuration
public class AspectjConfiguration {
@Bean
public UserAspectjThree userAspectjThree() {
return new UserAspectjThree();
}
}
- Test updateName() with UserAspectjThree
@Test public void testAdd() { User user = new User(); user.setName("jared"); userDao.save(user); }
- 6.2 測試正常執行updateName()
@Test public void testUpdateName() { userService.update("jared qiu", 1); }
- 6.3.1 列印結果
- 6.3.2 資料庫結果
- 6.4 測試非正常執行updateName(),隻需要把UserDao類中updateName()上的@Modifying或者@Transactional注解去掉即可
@Test public void testUpdateName() { userService.update("error jared qiu", 1); }
- 6.5.1 列印結果
- 6.5.2 資料庫結果
第四種風格的切面
- Create Aspect(依舊使用了@Around這個環繞注解,但加入了@Pointcut注解和傳遞了參數)
@Aspect
public class UserAspectjFour {
@Resource
private UserService userService;
@Pointcut("execution(* com.example.aspectj.UserDao.updateName(String,*)) && args(name,*)")
public void pointcut(String name) {
}
@Around(value = "pointcut(name)", argNames = "joinPoint,name")
public int around(ProceedingJoinPoint joinPoint, String name) {
try {
System.out.println("4.------------before()");
System.out.println("4.------------after()");
Object proceed = joinPoint.proceed();
System.out.println(proceed);
System.out.println("4.------------afterReturning()");
User user = new User();
user.setName("afterReturning4" + name);
userService.save(user);
} catch (Throwable e) {
System.out.println("4.------------afterThrowing()");
User user = new User();
user.setName("afterThrowing4" + name);
userService.save(user);
}
return 1;
}
}
@Configuration
public class AspectjConfiguration {
@Bean
public UserAspectjFour userAspectjFour() {
return new UserAspectjFour();
}
}
- Test updateName() with UserAspectjFour
@Test public void testAdd() { User user = new User(); user.setName("jared"); userDao.save(user); }
@Test public void testUpdateName() { userService.update("jared qiu", 1); }
@Test public void testUpdateName() { userService.update("error jared qiu", 1); }
擴充@EnableAspectJAutoProxy
- 表示開啟AOP代理自動配置,如果配@EnableAspectJAutoProxy表示使用cglib進行代理對象的生成;設定@EnableAspectJAutoProxy(exposeProxy=true)表示通過aop架構暴露該代理對象,使得aopContext能夠直接通路
- 從@EnableAspectJAutoProxy的定義可以看出,它引入AspectJAutoProxyRegister.class對象,該對象是基于注解@EnableAspectJAutoProxy注冊了一個AnnotationAwareAspectJAutoProxyCreator,通過調用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry),注冊了一個aop代理對象生成器
@EnableAspectJAutoProxy
AspectJAutoProxyRegistrar
參考連結
AspectJ Spring AOP系列 Spring AOP中JoinPoint的表達式定義描述