zoukankan      html  css  js  c++  java
  • Spring AOP项目应用——方法入参校验 & 日志横切

    转载:https://blog.csdn.net/Daybreak1209/article/details/80591566

    应用一:方法入参校验

    由于系统多个方法入参均对外封装了统一的Dto,其中Dto中几个必传参数在每个方法中都会进行相同的校验逻辑。笔者考虑采用Spring AOP进行优化,拦截方法进行参数校验。测试case实现如下:

    Before

    1.  
      /**
    2.  
      * 被代理的目标类
    3.  
      */
    4.  
      @Service
    5.  
      public class PayOrderTarget {
    6.  
      @Autowired
    7.  
      private PaymentOrderService paymentOrderService;
    8.  
       
    9.  
      public Result testQuery(QueryPaymentDto paymentOrderDto){
    10.  
      PaymentOrderDto paymentOrderDto1 = paymentOrderService.queryPaymentOrder(paymentOrderDto);
    11.  
      return ResultWrapper.success(paymentOrderDto1);
    12.  
      }
    13.  
      }
    1.  
      /**
    2.  
      * 通知类,横切逻辑
    3.  
      */
    4.  
      @Component
    5.  
      @Aspect
    6.  
      public class Advices {
    7.  
       
    8.  
      @Before("execution(* com.payment.util.springaop.PayOrderTarget.*(..))")
    9.  
      public Result before(JoinPoint proceedingJoinPoint) {
    10.  
      // 拦截获取入参
    11.  
      Object[] args = proceedingJoinPoint.getArgs();
    12.  
      // 入参校验
    13.  
      if (args ==null){
    14.  
      return ResultWrapper.fail();
    15.  
      }
    16.  
       
    17.  
      String input = JSON.toJSON(args).toString();
    18.  
      Map<String, String> map = Splitter.on(",").withKeyValueSeparator(":").split(input);
    19.  
      if (map.containsKey("businessId") || map.containsKey("payOrderId")){
    20.  
      System.out.println("key null");
    21.  
      return ResultWrapper.fail();
    22.  
      }
    23.  
      if (map.get("businessId")==null || map.get("payOrderId")==null ){
    24.  
      System.out.println("value null");
    25.  
      return ResultWrapper.fail();
    26.  
      }
    27.  
       
    28.  
      System.out.println("----------前置通知----------");
    29.  
      System.out.println(proceedingJoinPoint.getSignature().getName());
    30.  
      return ResultWrapper.success();
    31.  
      }
    32.  
      }

    测试类正常调用查询方法

    1.  
      @RunWith(SpringJUnit4ClassRunner.class)
    2.  
      @ContextConfiguration(locations = {"classpath*:spring/spring-context.xml"})
    3.  
      public class Test {
    4.  
       
    5.  
      @Autowired
    6.  
      private PayOrderTarget payOrderTarget;
    7.  
       
    8.  
      @org.junit.Test
    9.  
      public void test(){
    10.  
      QueryPaymentDto paymentOrderDto=new QueryPaymentDto();
    11.  
      paymentOrderDto.setPayOrderId(11l);
    12.  
      paymentOrderDto.setBusinessId(112L);
    13.  
      paymentOrderDto.setPageSize(1);
    14.  
      payOrderTarget.testQuery(paymentOrderDto);
    15.  
      }
    16.  
      }

    执行结果可对入参Dto进行拦截,执行before中的校验逻辑。但即便return fail之后,目标方法还是会被执行到。笔者是想实现参数校验失败,则直接返回,不执行接下来查询db的操作。此时则需要使用Around切入。

    Around

    1.  
      @Around("execution(* com.payment.util.springaop.PayOrderTarget.*(..))")
    2.  
      public Result around(ProceedingJoinPoint proceedingJoinPoint) {
    3.  
      // 获取java数组
    4.  
      Object[] args = proceedingJoinPoint.getArgs();
    5.  
      JSONArray jsonArray = JSONArray.parseArray(JSONArray.toJSONString(args));
    6.  
      String businessId = null;
    7.  
      String payOrderId = null;
    8.  
      for (int i = 0; i < jsonArray.size(); i++) {
    9.  
      businessId = jsonArray.getJSONObject(0).getString("businessId");
    10.  
      payOrderId = jsonArray.getJSONObject(0).getString("payOrderId");
    11.  
      }
    12.  
      ;
    13.  
      //参数校验
    14.  
      if (businessId == null || payOrderId == null) {
    15.  
      System.out.println("value null");
    16.  
      return ResultWrapper.fail();
    17.  
      }
    18.  
              //执行目标方法
    19.  
      try {
    20.  
      proceedingJoinPoint.proceed();
    21.  
      } catch (Throwable throwable) {
    22.  
      throwable.printStackTrace();
    23.  
      }
    24.  
      System.out.println("----------Around通知----------");
    25.  
      return ResultWrapper.success();
    26.  
      }

    简单介绍下,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 在方法抛出异常后调用通知
    1.  
      /**
    2.  
      * login接口类 被代理接口类
    3.  
      */
    4.  
      public interface ILoginService {
    5.  
      boolean login(String userName, String password);
    6.  
      void quary() throws Exception;
    7.  
      }
    8.  
       
    9.  
      /**
    10.  
      * login实现类,被代理目标类
    11.  
      */
    12.  
      @Service
    13.  
      public class LoginServiceImpl implements ILoginService {
    14.  
       
    15.  
      public boolean login(String userName, String password) {
    16.  
      System.out.println("login:" + userName + "," + password);
    17.  
      return true;
    18.  
      }
    19.  
      @Override
    20.  
      public void quary() throws Exception{
    21.  
      System.out.println("******quary*******");
    22.  
      // 测试方法抛出异常后,解注After-throwing配置,会执行logArg切入,不抛异常不执行切点
    23.  
      // int i=10/0;
    24.  
      }
    25.  
      }

    日志包装类,将各类日志情况进行方法封装

    1.  
      /**
    2.  
      * 日志处理类
    3.  
      */
    4.  
      public interface ILogService {
    5.  
      //无参的日志方法
    6.  
      void log();
    7.  
       
    8.  
      //有参的日志方法
    9.  
      void logArg(JoinPoint point);
    10.  
       
    11.  
      //有参有返回值的方法
    12.  
      void logArgAndReturn(JoinPoint point, Object returnObj);
    13.  
      }
    14.  
       
    15.  
      /**
    16.  
      * 日志实现类
    17.  
      */
    18.  
      @Component("logService")
    19.  
      @Aspect
    20.  
      public class LogServiceImpl implements ILogService {
    21.  
       
    22.  
      @Override
    23.  
      public void log() {
    24.  
      System.out.println("*************Log*******************");
    25.  
      }
    26.  
      @Override
    27.  
      public void logArg(JoinPoint point) {
    28.  
      System.out.println("方法:"+point.getSignature().getName());
    29.  
      Object[] args = point.getArgs();
    30.  
      System.out.println("目标参数列表:");
    31.  
      if (args != null) {
    32.  
      for (Object obj : args) {
    33.  
      System.out.println(obj + ",");
    34.  
      }
    35.  
      }
    36.  
      }
    37.  
      @Override
    38.  
      public void logArgAndReturn(JoinPoint point, Object returnObj) {
    39.  
      //此方法返回的是一个数组,数组中包括request以及ActionCofig等类对象
    40.  
      Object[] args = point.getArgs();
    41.  
      System.out.println("目标参数列表:");
    42.  
      if (args != null) {
    43.  
      for (Object obj : args) {
    44.  
      System.out.println(obj + ",");
    45.  
      }
    46.  
      System.out.println("执行结果是:" + returnObj);
    47.  
      }
    48.  
      }
    49.  
      }

    配置(注释着重看下):

    1.  
      <aop:config>
    2.  
      <!-- 切入点 LoginServiceImpl类中所有方法都会被拦截,执行增强方法-->
    3.  
      <aop:pointcut expression="execution(* com.payment.util.aoplogger.LoginServiceImpl.*(..))" id="myPointcut" />
    4.  
      <!-- 切面-->
    5.  
      <aop:aspect id="dd" ref="logService">
    6.  
      <!-- LoginServiceImpl类方法执行前,增强执行日志service的log方法-->
    7.  
      <!--<aop:before method="log" pointcut-ref="myPointcut" />-->
    8.  
       
    9.  
      <!-- LoginServiceImpl类方法执行后,增强执行日志service的logArg方法-->
    10.  
      <!--<aop:after method="logArg" pointcut-ref="myPointcut"/>-->
    11.  
      <!-- LoginServiceImpl类方法执行抛异常后,增强执行日志service的logArg方法-->
    12.  
      <aop:after-throwing method="logArg" pointcut-ref="myPointcut"/>
    13.  
      <!--<aop:after-returning method="logArgAndReturn" returning="returnObj" pointcut-ref="myPointcut"/>-->
    14.  
      </aop:aspect>
    15.  
      </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  等同于其他增强方式

  • 相关阅读:
    Linux运维工作总结教训
    java-GC
    java设计模式-原形模式
    java-桥接模式
    java-装饰者模式
    java-正则表达式
    java设计模式-建造者模式
    Python 条件与循环
    Python 集合、字典、运算符
    Python 字符串拼接、格式化输出、深浅复制
  • 原文地址:https://www.cnblogs.com/ceshi2016/p/9670733.html
Copyright © 2011-2022 走看看