Spring AOP專案應用——方法入參校驗 & 日誌橫切
應用一:方法入參校驗
由於系統多個方法入參均對外封裝了統一的Dto,其中Dto中幾個必傳引數在每個方法中都會進行相同的校驗邏輯。筆者考慮採用Spring AOP進行優化,攔截方法進行引數校驗。測試case實現如下:
Before
/** * 被代理的目標類 */ @Service public class PayOrderTarget { @Autowired private PaymentOrderService paymentOrderService; public Result testQuery(QueryPaymentDto paymentOrderDto){ PaymentOrderDto paymentOrderDto1 = paymentOrderService.queryPaymentOrder(paymentOrderDto); return ResultWrapper.success(paymentOrderDto1); } }
/** * 通知類,橫切邏輯 */ @Component @Aspect public class Advices { @Before("execution(* com.payment.util.springaop.PayOrderTarget.*(..))") public Result before(JoinPoint proceedingJoinPoint) { // 攔截獲取入參 Object[] args = proceedingJoinPoint.getArgs(); // 入參校驗 if (args ==null){ return ResultWrapper.fail(); } String input = JSON.toJSON(args).toString(); Map<String, String> map = Splitter.on(",").withKeyValueSeparator(":").split(input); if (map.containsKey("businessId") || map.containsKey("payOrderId")){ System.out.println("key null"); return ResultWrapper.fail(); } if (map.get("businessId")==null || map.get("payOrderId")==null ){ System.out.println("value null"); return ResultWrapper.fail(); } System.out.println("----------前置通知----------"); System.out.println(proceedingJoinPoint.getSignature().getName()); return ResultWrapper.success(); } }
測試類正常呼叫查詢方法
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath*:spring/spring-context.xml"}) public class Test { @Autowired private PayOrderTarget payOrderTarget; @org.junit.Test public void test(){ QueryPaymentDto paymentOrderDto=new QueryPaymentDto(); paymentOrderDto.setPayOrderId(11l); paymentOrderDto.setBusinessId(112L); paymentOrderDto.setPageSize(1); payOrderTarget.testQuery(paymentOrderDto); } }
執行結果可對入參Dto進行攔截,執行before中的校驗邏輯。但即便return fail之後,目標方法還是會被執行到。筆者是想實現引數校驗失敗,則直接返回,不執行接下來查詢db的操作。此時則需要使用Around切入。
Around
@Around("execution(* com.payment.util.springaop.PayOrderTarget.*(..))")
public Result around(ProceedingJoinPoint proceedingJoinPoint) {
// 獲取java陣列
Object[] args = proceedingJoinPoint.getArgs();
JSONArray jsonArray = JSONArray.parseArray(JSONArray.toJSONString(args));
String businessId = null;
String payOrderId = null;
for (int i = 0; i < jsonArray.size(); i++) {
businessId = jsonArray.getJSONObject(0).getString("businessId");
payOrderId = jsonArray.getJSONObject(0).getString("payOrderId");
}
;
//引數校驗
if (businessId == null || payOrderId == null) {
System.out.println("value null");
return ResultWrapper.fail();
}
//執行目標方法
try {
proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("----------Around通知----------");
return ResultWrapper.success();
}
簡單介紹下,before和after切入都是接收JoinPoint物件,該物件可獲取切點(即被代理物件)的入參、方法名等資料。
方法名 | 功能 |
---|---|
Signature getSignature(); | 獲取封裝了署名資訊的物件,在該物件中可以獲取到目標方法名,所屬類的Class等資訊 |
Object[] getArgs(); | 獲取傳入目標方法的引數物件 |
Object getTarget(); | 獲取被代理的物件 |
Object getThis(); | 獲取代理物件 |
而Around接收ProceedingJoinPoint該介面繼承自JoinPoint,新增瞭如下兩個方法,通過呼叫proceedingJoinPoint.proceed方法才控制目標方法的呼叫。則在如上around中,進行引數校驗,不合法則return,未進入到proceedingJoinPoint.proceed()處,達到方法不合規直接返回不呼叫查詢邏輯。
方法名 | 功能 |
---|---|
Object proceed() throws Throwable | 執行目標方法 |
Object proceed(Object[] var1) throws Throwable | 傳入的新的引數去執行目標方法 |
注:本case採用註解宣告,直接可執行。xml中增加如下配置(支援自動裝配@Aspect註解的bean)即可。
<aop:aspectj-autoproxy proxy-target-class="true"/>
應用二:日誌處理
實現將日誌列印抽取,成為一個公共類,切入到目標類的方法入口、出口處列印方法名+引數資訊;這個case重點引用了幾種不同的AOP增強方式,簡單介紹如下:
Before | 在方法被呼叫之前呼叫通知 |
---|---|
Around | 通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為 |
After | 在方法完成之後呼叫通知,無論方法執行是否成功 |
After-returning | 在方法返回結果後執行通知 |
After-throwing | 在方法丟擲異常後呼叫通知 |
/**
* login介面類 被代理介面類
*/
public interface ILoginService {
boolean login(String userName, String password);
void quary() throws Exception;
}
/**
* login實現類,被代理目標類
*/
@Service
public class LoginServiceImpl implements ILoginService {
public boolean login(String userName, String password) {
System.out.println("login:" + userName + "," + password);
return true;
}
@Override
public void quary() throws Exception{
System.out.println("******quary*******");
// 測試方法丟擲異常後,解注After-throwing配置,會執行logArg切入,不拋異常不執行切點
// int i=10/0;
}
}
日誌包裝類,將各類日誌情況進行方法封裝
/**
* 日誌處理類
*/
public interface ILogService {
//無參的日誌方法
void log();
//有參的日誌方法
void logArg(JoinPoint point);
//有參有返回值的方法
void logArgAndReturn(JoinPoint point, Object returnObj);
}
/**
* 日誌實現類
*/
@Component("logService")
@Aspect
public class LogServiceImpl implements ILogService {
@Override
public void log() {
System.out.println("*************Log*******************");
}
@Override
public void logArg(JoinPoint point) {
System.out.println("方法:"+point.getSignature().getName());
Object[] args = point.getArgs();
System.out.println("目標引數列表:");
if (args != null) {
for (Object obj : args) {
System.out.println(obj + ",");
}
}
}
@Override
public void logArgAndReturn(JoinPoint point, Object returnObj) {
//此方法返回的是一個數組,陣列中包括request以及ActionCofig等類物件
Object[] args = point.getArgs();
System.out.println("目標引數列表:");
if (args != null) {
for (Object obj : args) {
System.out.println(obj + ",");
}
System.out.println("執行結果是:" + returnObj);
}
}
}
配置(註釋著重看下):
<aop:config>
<!-- 切入點 LoginServiceImpl類中所有方法都會被攔截,執行增強方法-->
<aop:pointcut expression="execution(* com.payment.util.aoplogger.LoginServiceImpl.*(..))" id="myPointcut" />
<!-- 切面-->
<aop:aspect id="dd" ref="logService">
<!-- LoginServiceImpl類方法執行前,增強執行日誌service的log方法-->
<!--<aop:before method="log" pointcut-ref="myPointcut" />-->
<!-- LoginServiceImpl類方法執行後,增強執行日誌service的logArg方法-->
<!--<aop:after method="logArg" pointcut-ref="myPointcut"/>-->
<!-- LoginServiceImpl類方法執行拋異常後,增強執行日誌service的logArg方法-->
<aop:after-throwing method="logArg" pointcut-ref="myPointcut"/>
<!--<aop:after-returning method="logArgAndReturn" returning="returnObj" pointcut-ref="myPointcut"/>-->
</aop:aspect>
</aop:config>
注意:採用xml配置與使用@註解二選一(都寫上即執行兩次增強方法),對等關係如下:
<aop:aspect ref="advices"> | @Aspect public class Advices |
<aop:pointcut expression="execution(* com.payment.util.springaop.PayOrderTarget.*(..))" id="pointcut1"/> <aop:before method="before" pointcut-ref="pointcut1"/> | @Before("execution(* com.payment.util.springaop.PayOrderTarget.*(..))") |
<aop:after | @After 等同於其他增強方式 |