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");
        }
    }
    

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

  • 相关阅读:
    Effective C# Item6:明辨值类型和引用类型的使用场合
    Effective C# Item15:利用using和try/finally语句来清理资源
    Effective C# Item12:变量初始化器优于赋值语句
    Effective C# Item19:定义并实现接口优于继承类型
    Effective C# Item14:利用构造器链
    Effective C# Item18:实现标准Dispose模式
    Effective C# Item17:尽量减少装箱和拆箱
    Effective C# Item7:将值类型尽可能实现为具有常量性和原子性的类型
    Effective C# Item10:理解GetHashCode()方法的缺陷
    Effective C# Item20:明辨接口实现和虚方法重写
  • 原文地址:https://www.cnblogs.com/seasail/p/12179403.html
Copyright © 2011-2022 走看看