一、AOP切面編程原理
AOP全稱為Aspect-Oriented Programming,中文翻譯為「面向切面編程」。AOP和OOP(Object-Oriented Programming)作為兩種編程範式,都追求實現代碼的模塊化、可重用性和可維護性。AOP是通過將橫切關注點抽象出來來實現這一點,將其稱為「切面」(Aspect) 。AOP採用的是將這些關注點與原方法進行分離的方式來降低代碼的耦合度的。
在Java中,AOP動態代理的實現,使得我們可以在執行目標方法之前和之後,插入一些特定的處理,而不是在主業務中添加大量冗餘代碼。AOP在實現中使用到了反射技術,對java層面的代碼進行攔截和切入操作。
二、AOP切面編程是什麼
AOP切面編程指的是一種面向切面的編程思想和設計模式,它將各個業務邏輯之間相同或類似的處理抽象成一個切面,達到了代碼重用,簡化程序設計的目的。
例如,當我們需要在應用程序中統計某個方法的運行時間,如果在每次調用該方法時都手動添加代碼,代碼將變得冗餘且不易維護。但是,使用AOP切面編程,我們可以將計時的代碼組織成一個切面,並將其織入到目標方法中,達到代碼重用的目的。
三、AOP切面編程三種實現方式
1. 靜態代理
靜態代理是通過在編譯期間,手動編寫代理類的方式進行的,需要我們手動為每一個類編寫代理類,存在大量的冗餘代碼。其原理是在代理類中生成一個目標類的對象並對其進行包裝,通過調用代理類中的方法來實現對目標類方法的調用。
2. JDK動態代理
JDK動態代理是面向介面代理,實現InvocationHandler介面,重寫invoke方法。在運行期間,使用Java反射機制,在內存中生成代理類,並且實現了目標類的介面,直接調用代理類中的方法會觸發InvocationHandler的invoke方法,我們可以在這個方法中添加需要的操作。JDK動態代理可以通過proxy.newProxyInstance()方法來動態的生成代理類,支持多個目標類的代理。
3. CGLIB動態代理
CGLIB動態代理是直接生成目標類的子類,因而可以對類進行代理(而不僅僅是介面),不過,需要引入cglib-nodep依賴。CGLIB動態代理同樣實現了InvocationHandler介面,通過創建Enhancer來動態生成目標類的子類,然後在子類中織入相關增強邏輯,最後創建子類對象。
四、切面編程是什麼意思
切面編程是AOP編程的實現方式之一,它將通用或者常見的功能,像日誌記錄、性能分析、許可權控制等,稱為切面。這些切面可以縱向地貫穿應用的各層,比如Controller,Service,Dao等,也可以橫向地貫穿應用的多個模塊和組件。
切面可以看做是一組針對特定類某一類方法的函數,利用指定的切面可以更方便、更直觀、更快捷地處理代碼,它對代碼的可讀性和維護性都有很好的幫助。
五、AOP面向切面編程應用場景
通過AOP編程實現的切面,可以在任何地方被調用,可以方便地通過合適的實現方式,實現各種不同的應用場景,包括:
1. 日誌記錄
記錄關鍵方法的執行情況,比如介面調用、運行時間、日誌級別等,例如下面是一個簡單的列印日誌的AOP切面:
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Before("execution(* com.example.demo.service.*.*(..))")
public void doBefore() {
logger.info("method start...");
}
@After("execution(* com.example.demo.service.*.*(..))")
public void doAfter() {
logger.info("method end...");
}
}
2. 許可權管理
處理用戶許可權相關問題,比如用戶是否具有操作某項資源的許可權、用戶是否處於登錄狀態等。在使用Spring Security等安全框架時,會經常使用這種方式對請求進行認證和授權。
3. 數據緩存
對於一些數據的計算和處理非常耗費時間,可以將結果緩存起來,再次用到時直接從緩存中獲取數據,提高效率。例如通過redis工具實現數據緩存:
@Around("execution(* com.example.demo.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
//生成redis的key
String key = RedisKeyUtils.getKey(methodName, Arrays.toString(args));
//先從redis中查找數據
Object obj = stringRedisTemplate.opsForValue().get(key);
if (obj != null) {
logger.info("Redis exists key - " + key);
return obj;
}
logger.info("Redis not exists key - " + key);
//如果redis中不存在數據則進行計算,然後添加到redis中
Object result = pjp.proceed();
if (result != null) {
stringRedisTemplate.opsForValue().set(key, result.toString(), 300, TimeUnit.SECONDS);
logger.info("Redis add key - " + key);
}
return result;
}
4. 聲明式事務
通過AOP的方式來幫助我們完成事務的操作,比如管理資料庫連接、開啟或關閉事務、定義事務的邊界等。在Spring整合Mybatis時,經常會用到這種方式來實現聲明式事務,只需要在需要使用事務的方法上添加@Transactional註解即可:
@Transactional
public void insertUser(User user) {
userDao.insert(user);
}
六、AOP切面編程思想
AOP切面編程思想,是其在設計模式上的具體落地。通過將不同的功能劃分為不同的切面或者模塊,使其更具有可讀性、可維護性和可擴展性。大大減少了重複代碼和代碼冗餘,使得代碼更加簡介,減輕了程序員的工作量,提高開發效率。
七、AOP切面編程架構
AOP切面編程架構通常有三個核心模塊,即切面模塊、切入點模塊和通知模塊。
1. 切面模塊
切面模塊是AOP切面編程的核心模塊,它由一個或多個切面組成。每一個切面都包含了一個或多個通知和一個或多個切點。
2. 切入點模塊
切入點模塊用於定位在目標類中的切點位置。在整個AOP過程中,先針對具體的方法進行攔截,攔截到方法後再根據用戶定義的切面進行處理。
3. 通知模塊
通知模塊則包括了具體的增強代碼,而這些增強代碼都是用戶自己定義的。通知可以包括Before Advice、After Advice、Around Advice、After Throwing Advice和After Returning Advice等。
八、AOP切面編程作用
AOP切面編程能夠讓我們的代碼更加優雅、簡潔、易於維護、擴展和復用,並且可以大大降低多個組件之間的耦合度,並且大大減少重複開發、代碼冗餘以及出錯幾率。性能方面也有所提高,例如Redis緩存、聲明式事務等。
九、AOP切面編程代碼示例
下面是一個簡單的Spring Boot應用,使用AOP切面編程來記錄用戶新增和刪除操作的日誌。
1. 新增記錄日誌切面
@Aspect
@Component
public class UserAddAspect {
@Autowired
private HttpServletRequest request;
private static final Logger logger = LoggerFactory.getLogger(UserAddAspect.class);
@Before("@annotation(com.example.demo.annotation.UserAdd)")
public void addLog() {
String methodName = "新增用戶";
String params = request.getParameter("username");
logger.info("執行 " + methodName + " 方法,參數為:" + params);
}
}
2. 刪除記錄日誌切面
@Aspect
@Component
public class UserDeleteAspect {
@Autowired
private HttpServletRequest request;
private static final Logger logger = LoggerFactory.getLogger(UserDeleteAspect.class);
@Before("@annotation(com.example.demo.annotation.UserDelete)")
public void deleteLog() {
String methodName = "刪除用戶";
String params = request.getParameter("userId");
logger.info("執行 " + methodName + " 方法,參數為:" + params);
}
}
3. 控制器中使用
@RestController
public class UserController {
@Autowired
private UserService userService;
@UserAdd //記錄新增用戶日誌
@PostMapping("/add")
public String addUser(@RequestParam String username, @RequestParam String password) {
userService.addUser(username, password);
return "success";
}
@UserDelete //記錄刪除用戶日誌
@DeleteMapping("/delete/{userId}")
public String deleteUser(@PathVariable String userId) {
userService.deleteUser(userId);
return "success";
}
}
將註解UserAdd和UserDelete,分別標記在新增用戶和刪除用戶的方法上。當我們調用這兩個方法時,將會在控制台上列印出相應的日誌,從而實現了記錄用戶操作的目的。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/227400.html