转载:https://blog.csdn.net/Daybreak1209/article/details/80591566
应用一:方法入参校验
由于系统多个方法入参均对外封装了统一的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 等同于其他增强方式 |