zoukankan      html  css  js  c++  java
  • 面向切面编程AOP的最佳入门示例

    1.AOP简单上手

           AOP(Aspect Oriented Programming),意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接、打开事务/关闭事务记录日志等。AOP可以降低耦合,把通用的业务提出来,提高代码的可重用性,同时提高开发的效率,使开发人员只关心真正的业务逻辑.

    1. 引入AOP的依赖
    	<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
         </dependency>
    

           @EnableAspectJAutoProxy已经默认开启,不需要在启动类上添加.
           而当我们需要使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true.但是注意:高版本spring自动根据运行类选择JDK或CGLIB代理,也就是当运行类没有继承接口,spring也会自动使用CGLIB代理。我们无需设置proxy-target-class属性,JDK动态代理是模拟接口实现的方式,cglib是模拟子类继承的方式,一般采用前者,因为前者效率高。

    2 简单示例

    @Aspect
    @Component
    public class LogAspect {
    
        @Around(value = "execution(* com.jun.test.microservice..*(..))")
        public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
    
            Object returnVal = null;
            final Logger log = getLog(proceedingJoinPoint);
            final String methodName = proceedingJoinPoint.getSignature().getName();
    
            try {
                    final Object[] args = proceedingJoinPoint.getArgs();
                    final String arguments;
                    if (args == null || args.length == 0) {
                        arguments = "";
                    } else {
                        arguments = Arrays.deepToString(args);
                    }
                    log.info("Entering method [" + methodName + " with arguments [" + arguments + "]");
                returnVal = proceedingJoinPoint.proceed();
                return returnVal;
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            } finally {
                    log.info("Leaving method [" + methodName + "] with return value [" 
                    + (returnVal != null ? returnVal.toString() : "null") + "].");
            }
            return null;
        }
    
        protected Logger getLog(final JoinPoint joinPoint) {
            final Object target = joinPoint.getTarget();
    
            if (target != null) {
                return LoggerFactory.getLogger(target.getClass());
            }
    
            return LoggerFactory.getLogger(getClass());
        }
    
    }
    
    

    注意

    1.当有多个切面针对同一个切点时,可以使用@order()来指定执行顺序,可以理解成多个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。

    2.@Order的作用于可以在类,方法,字段上,但是他指定的是容器中bean的加载顺序.

    2.自定义注解+AOP

    可以通过自定义注解,来更针对性的定义切点.
    2.1 首先自定义注解

    @Target({ElementType.PARAMETER, ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface SystemServiceLog {
        String description()  default "";
    }
    

    2.2 定义切面类
    实现注有@SystemServiceLog的方法抛出异常时,记录日志。

    @Aspect
    @Component
    public class AnnotationAspect {
    
        //Service层切点,拦截添加@SystemServiceLog的
        @Pointcut("@annotation(com.jun.test.microservice.aspect.SystemServiceLog)")
        public  void serviceAspect() {
        }
    
        @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
        public  void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
            //获得request和session
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            HttpSession session = request.getSession();
            //获取请求ip
            String ip = request.getRemoteAddr();
            //获取用户请求方法的参数并序列化为JSON格式字符串
            String params = "";
            if (joinPoint.getArgs() !=  null && joinPoint.getArgs().length > 0) {
                for ( int i = 0; i < joinPoint.getArgs().length; i++) {
                    params += JsonUtils.object2Json(joinPoint.getArgs()[i]) + ";";
                }
            }
            Logger log = getLog(joinPoint);
            try {
                /*==========数据库日志=========*/
                log.info("异常处理");
                log.info(e.getMessage());
                log.info((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
                log.info(params);
                log.info(ip);
                //保存数据库
                //logService.add(log);
                System.out.println("=====异常通知结束=====");
            }  catch (Exception ex) {
                //记录本地异常日志
                log.error("==异常通知异常==");
                log.error("异常信息:{}", ex.getMessage());
            }
            /*==========记录本地异常日志==========*/
            log.error("异常方法:{}异常信息:{}参数:{}", joinPoint.getTarget().getClass().getName() 
            + joinPoint.getSignature().getName(), e.getMessage(), params);
    
        }
    
    
        protected Logger getLog(final JoinPoint joinPoint) {
            final Object target = joinPoint.getTarget();
    
            if (target != null) {
                return LoggerFactory.getLogger(target.getClass());
            }
    
            return LoggerFactory.getLogger(getClass());
        }
    
    
    }
    

    2.3 在需要的方法上添加自定义注解

    @Service
    public class UsrServiceImpl implements IUserService {
    
        @SystemServiceLog(description = "查询用户")
        public UserAccount getUser(String id){
            int i = 0/0;
            return new UserAccount(id,"hello");
        }
    }
    

           以上即可.结合自定义注解和模板,可以实现很多方便的功能,如记录系统操作日志(添加,删除等等),记录异常等.对一些方法都有的逻辑可以提出来,如参数校验,限制访问频率等.

  • 相关阅读:
    systemctl无法停掉keepalived
    python小工具
    python pip
    linux下安装python3
    python process
    python socket模块
    python logging日志模块
    板邓:C#的声明数组和赋值
    板邓:解决jquery中全选点击第二次不生效的问题
    板邓:php+mayql分页原理及案例
  • 原文地址:https://www.cnblogs.com/seasail/p/12179403.html
Copyright © 2011-2022 走看看