面向切面编程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");
    }
}

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

posted on 2020-01-11 11:19  spiritt  阅读(226)  评论(0编辑  收藏  举报