aop學習筆記之Pointcut Express和Advice:http://blog.csdn.net/zyb2017/article/details/79444476
本篇代碼下載地址:https://download.csdn.net/download/zyb2017/10277492
一 環境
開始解決之前先來搭建一個測試的環境,MAVEN+Spring-Boot,版本爲1.5.10,因爲要使用AOP所以在pom裏添加aop的依賴
<!--spring aop 依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
刷新Maven後看下External Libraries中是否有我們要的包
二 普通寫法
測試程序爲管理員對產品進行刪除或添加操作,操作之前要驗證管理員身份,不是admin則拋出異常
2.1 產品類
package com.march.aop.domain;
/**
* Product class
*
* @author TransientBa
* @date 2018/03/01
*/
public class Product {
private Long id;
private String name;
public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public void setId(Long id) { this.id = id; }
}
2.2 持有校驗
package com.march.aop.security;
/**
* CurrentUserHolder class
*
* @author TransientBa
* @date 2018/03/01
*/
public class CurrentUserHolder {
//維護多線程間局部變量
private static final ThreadLocal<String> holder = new ThreadLocal<>();
public static String get(){
//holder.get() 返回局部變量的當前線程值
return holder.get() == null ? "unknown" : holder.get();
}
public static void set(String user){
holder.set(user);
}
}
2.3 身份驗證Service
package com.march.aop.service;
import com.march.aop.security.CurrentUserHolder;
import org.springframework.stereotype.Component;
/**
* AuthService class
*
* @author TransientBa
* @date 2018/03/01
*/
@Component
public class AuthService {
public void checkAccess(){
String user = CurrentUserHolder.get();
String role = "admin";
//若當前用戶不是admin則拋出異常不允許操作
if(!role.equals(user)){
throw new RuntimeException("operation not allow");
}
}
}
2.4 產品Service
package com.march.aop.service;
import com.march.aop.domain.Product;
import com.march.aop.security.AdminOnly;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* ProductService class
*
* @author TransientBa
* @date 2018/03/01
*/
@Service
public class ProductService {
@Autowired
AuthService authService;
public void insert(Product product){
//插入之前進行身份校驗
authService.checkAccess();
System.out.println("insert product");
}
public void delete(Long id){
authService.checkAccess();
System.out.println("delete product");
}
}
2.5 測試啓動類
package com.march.aop;
import com.march.aop.security.CurrentUserHolder;
import com.march.aop.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
ProductService productService;
@Test(expected = Exception.class)
public void annoInsertTrst() {
CurrentUserHolder.set("TransientBa");
productService.delete(1L);
}
@Test
public void adminInsert(){
CurrentUserHolder.set("admin");
productService.delete(1L);
}
}
第一個annoInsertTrst方法因爲插入的管理員叫TransientBa而不是admin,理論上會跑拋異常。我們加上expected = Exception.class,運行測試:
我們看到測試是通過了的。
第二個adminInsert插入了admin,理論上不會拋異常,直接運行測試:
通過
三 aop織入
以aop範式的規則織入,來替代產品Service中的調用
3.1 管理員註解
我們用註解來控制織入的位置,創建管理員註解
package com.march.aop.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* class
*
* @author TransientBa
* @date 2018/3/1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly {
}
自定義註解的方法可查看我的另一片文章:
@interface 自定義註解:http://blog.csdn.net/zyb2017/article/details/78827525
3.2 @Aspect
package com.march.aop.security;
import com.march.aop.service.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* SecurityAspect class
*
* @author TransientBa
* @date 2018/3/1
*/
@Aspect
@Component
public class SecurityAspect {
@Autowired
AuthService authService;
/**切入點爲帶有AdminOnly註解的方法**/
@Pointcut("@annotation(AdminOnly)")
public void adminOnly(){
}
/**在adminOnly方法執行前 做checkAccess的校驗**/
@Before("adminOnly()")
public void check(){
authService.checkAccess();
}
}
同時通過@Component註解將SecurityAspect 類交給Spring進行託管
3.3 修改產品Service
取消原本的用戶校驗,改爲應用@AdminOnly註解來標註接入點
package com.march.aop.service;
import com.march.aop.domain.Product;
import com.march.aop.security.AdminOnly;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* ProductService class
*
* @author TransientBa
* @date 2018/03/01
*/
@Service
public class ProductService {
@Autowired
AuthService authService;
@AdminOnly
public void insert(Product product){
System.out.println("insert product");
}
@AdminOnly
public void delete(Long id){
System.out.println("delete product");
}
}
3.4 測試
回到DemoApplicationTest,進行測試
運行結果如下
annoInsertTrst:
adminInsert:
四 總結
以aop的方式解決一些非功能性的需求,例如日誌、事務、異常等。可以很好的將非業務性的需求拿出來,減少代碼的侵入性,當我們修改時做到不影響原本的業務邏輯。