天天看點

面向切面的Spring

寫在前面

  本文是部落客在看完面向切面的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還可以通過構造器或屬性注入

切點表達式

  切點表達式算是一些比較概念性的知識,下面截了兩個圖供大家參考參考

面向切面的Spring

切點表達式1

面向切面的Spring

切點表達式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)

  1. Create Entity
@Table(name = "tb_user")
@Entity
@Data
public class User {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

}
           
  1. 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);

}

           
  1. 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);
    }

}
           

第一種風格的切面

  1. 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);
    }

}
           
  1. Create Configuration
@Configuration
// @EnableAspectJAutoProxy //實測可以不添加該注解,因為SpringBoot中已經預設開啟了AOP功能
public class AspectjConfiguration {

    @Bean
    public UserAspectjOne userAspectjOne() {
        return new UserAspectjOne();
    }

}
           
面向切面的Spring

SpringBoot已經預設開啟了aop

  1. Test updateName() with UserAspectjOne
    • 6.1 先往資料庫裡添加一條資料
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
               
    面向切面的Spring
    添加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]
           

第二種風格的切面

  1. 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();
    }

}
           
  1. Test updateName() with UserAspectjTwo
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
               
    面向切面的Spring
- 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]
           

第三種風格的切面

  1. 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();
    }

}
           
  1. Test updateName() with UserAspectjThree
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
               
    面向切面的Spring
    • 6.2 測試正常執行updateName()
    @Test
    public void testUpdateName() {
        userService.update("jared qiu", 1);
    }
               
    • 6.3.1 列印結果
    面向切面的Spring
    輸出結果
    • 6.3.2 資料庫結果
    面向切面的Spring
    資料庫結果
    • 6.4 測試非正常執行updateName(),隻需要把UserDao類中updateName()上的@Modifying或者@Transactional注解去掉即可
    @Test
    public void testUpdateName() {
        userService.update("error jared qiu", 1);
    }
               
    • 6.5.1 列印結果
    面向切面的Spring
    • 6.5.2 資料庫結果
    面向切面的Spring

第四種風格的切面

  1. 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();
    }

}
           
  1. Test updateName() with UserAspectjFour
    @Test
    public void testAdd() {
        User user = new User();
        user.setName("jared");
        userDao.save(user);
    }
               
    面向切面的Spring
    @Test
    public void testUpdateName() {
        userService.update("jared qiu", 1);
    }
               
    面向切面的Spring
    面向切面的Spring
    @Test
    public void testUpdateName() {
        userService.update("error jared qiu", 1);
    }
               
    面向切面的Spring
    面向切面的Spring

擴充@EnableAspectJAutoProxy

  • 表示開啟AOP代理自動配置,如果配@EnableAspectJAutoProxy表示使用cglib進行代理對象的生成;設定@EnableAspectJAutoProxy(exposeProxy=true)表示通過aop架構暴露該代理對象,使得aopContext能夠直接通路
  • 從@EnableAspectJAutoProxy的定義可以看出,它引入AspectJAutoProxyRegister.class對象,該對象是基于注解@EnableAspectJAutoProxy注冊了一個AnnotationAwareAspectJAutoProxyCreator,通過調用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry),注冊了一個aop代理對象生成器
面向切面的Spring

@EnableAspectJAutoProxy

面向切面的Spring

AspectJAutoProxyRegistrar

參考連結

AspectJ Spring AOP系列 Spring AOP中JoinPoint的表達式定義描述