編程範式主要以下幾大類
- AOP(Aspect Oriented Programming)面向切面編程
- OOP(Object Oriented Programming)面向對象編程
- POP(procedure oriented programming)面向過程編程
- FP(Functional Programming)面向函數編程
關注、轉發、評論頭條號每天分享java知識,私信回覆“555”贈送一些Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式資料
引入aop依賴
以下示例是基於Spring Boot實戰系列(2)數據存儲之Jpa操作MySQL chapter2-1可在Github獲取源碼
項目根目錄 pom.xml 添加依賴 spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-aop
aop註解
- @Aspect: 切面,由通知和切入點共同組成,這個註解標註在類上表示為一個切面。
- @Joinpoint: 連接點,被AOP攔截的類或者方法,在前置通知中有介紹使用@Joinpoint獲取類名、方法、請求參數。
- Advice: 通知的幾種類型
- @Before: 前置通知,在某切入點@Pointcut之前的通知
- @After: 後置通知,在某切入點@Pointcut之後的通知無論成功或者異常。
- @AfterReturning: 返回後通知,方法執行return之後,可以對返回的數據做加工處理。
- @Around: 環繞通知,在方法的調用前、後執行。
- @AfterThrowing: 拋出異常通知,程序出錯跑出異常會執行該通知方法。
- @Pointcut: 切入點,從哪裡開始。例如從某個包開始或者某個包下的某個類等。
實現日誌分割功能
目錄 aspect下 新建 httpAspect.java類,在收到請求之後先記錄請求的相關參數日誌信息,請求成功完成之後打印響應信息,請求處理報錯打印報錯日誌信息。
httpAspect.java
package com.angelo.aspect;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Aspect
@Component
public class HttpAspect {
// 打印日誌模塊
private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
// 下面會一一介紹...
添加切入點
定義切入的入口在哪裡,封裝一個公共的方法實現複用
httpAspect.java
/**
* 定義一個公共的方法,實現服用
* 攔截UserController下面的所有方法
* 攔截UserController下面的userList方法裡的任何參數(..表示攔截任何參數)寫法:@Before("execution(public * com.angelo.controller.UserController.userList(..))")
*/
@Pointcut("execution(public * com.angelo.controller.UserController.*(..))")
public void log() {
}
前置通知
攔截方法之前的一段業務邏輯,獲取請求的一些信息,其中用到了Gson處理對象轉json輸出
httpAspect.java
@Before("log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Map params = new HashMap();
params.put("url", request.getRequestURL()); // 獲取請求的url
params.put("method", request.getMethod()); // 獲取請求的方式
params.put("ip", request.getRemoteAddr()); // 獲取請求的ip地址
params.put("className", joinPoint.getSignature().getDeclaringTypeName()); // 獲取類名
params.put("classMethod", joinPoint.getSignature().getName()); // 獲取類方法
params.put("args", joinPoint.getArgs()); // 請求參數
// 輸出格式化後的json字符串
Gson gson = new GsonBuilder().setPrettyPrinting().create();
logger.info("REQUEST: {}", gson.toJson(params));
}
後置通知
攔截方法之後的一段業務邏輯
httpAspect.java
@After("log()")
public void doAfter() {
logger.info("doAfter");
}
環繞通知
環繞通知是在方法的前後的一段邏輯操作,可以修改目標方法的返回值,第一個參數是org.aspectj.lang.ProceedingJoinPoint類型,注意這裡要調用執行目標方法proceed()獲取值返回,不然會造成空指針異常。在環繞通知裡面也可以捕獲錯誤返回。
httpAspect.java
@Around("log()")
public Object doAround(ProceedingJoinPoint point) {
try {
Object o = point.proceed();
System.out.println("方法環繞proceed,結果是 :" + o);
logger.info("doAround1");
return o;
} catch (Throwable e) {
// e.printStackTrace();
logger.info("doAround2");
return null;
}
}
返回後通知
在切入點完成之後的返回通知,此時就不會拋出異常通知,除非返回後通知的業務邏輯報錯。
httpAspect.java
/**
* 獲取響應返回值
* @param object
*/
@AfterReturning(returning = "object", pointcut = "log()")
public void doAfterReturning(Object object) {
// logger.info("RESPONSE: {}", object); 會打印出一個對象,想打印出具體內容需要在定義模型處加上toString()
logger.info("RESPONSE: {}", object.toString());
}
異常通知
拋出異常後的通知,此時返回後通知@AfterReturning就不會執行。
httpAspect.java
@AfterThrowing(pointcut = "log()")
public void doAfterThrowing() {
logger.error("doAfterThrowing: {}", " 異常情況!");
}
一段段偽代碼讀懂執行順序
try {
// @Before 執行前通知
// 執行目標方法
// @Around 執行環繞通知 成功走finall,失敗走catch
} finally {
// @After 執行後置通知
// @AfterReturning 執行返回後通知
} catch(e) {
// @AfterThrowing 拋出異常通知
}
測試正常異常兩種情況
測試之前先對controller/UserController.java文件的userList方法增加了exception參數
/**
* 查詢用戶列表
* @return
*/
@RequestMapping(value = "/user/list/{exception}")
public ListuserList(@PathVariable("exception") Boolean exception) {
if (exception) {
throw new Error("測試拋出異常!");
}
return userRepository.findAll();
}
- 測試正常情況
curl 127.0.0.1:8080/user/list/false
正常情況返回值如下所示:
- 測試異常情況
curl 127.0.0.1:8080/user/list/true
異常情況返回值如下所示:
通過以上兩種情況測試可以看到環繞通知在正常、異常兩種情況都可以執行到。
關注、轉發、評論頭條號每天分享java知識,私信回覆“
555”贈送一些Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式資料閱讀更多 Java大寶寶 的文章