AOP学习

1、什么是AOP?

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

为什么使用AOP编程范式?

  • 分离功能性需求和非功能性需求
  • 集中处理某一关注点
  • 侵入性少,增强代码可读性及可维护性

AOP应用场景

  • 权限控制、缓存控制、事务控制、分布式追踪、异常处理等

举个栗子

  • 如果你要在Service层的某些特定方法需加上权限验证,使用OOP思想的话只能在方法内部添加验证身份的代码,例如
Java
public void insert() {
  checkUserAdmin.check();    //加入权限验证方法
  repository.insert();        //调用dao层插入数据库一条记录
}

这样看起来功能是实现了,但如果service层有很多insert和delete方法呢?这样插入代码的方式不易于我们去统一管理,且修改了原代码,具有侵入性。
那么使用了AOP之后呢?你可以建一个切面类,对要进行权限验证的方法进行切入。即在程序运行时,动态地将代码切入到类的指定方法或位置上的思想,就是面向切面编程。

现在主流的有SpringAOP和AspectJ

  • AspectJ 是一个基于 Java 语言的 AOP 框架,提供了强大的 AOP 功能,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。
  • Spring AOP 是Spring实现的AOP框架,采用动态代理的模式进行切面的织入,目前Spring 同时支持原生的Spring AOP与AspectJ的AOP

在这里插入图片描述

1.1 SpringAOP

Spring AOP是通过动态代理的形式实现切面的织入的,调用方通过Proxy代理对象间接地与目标对象进行交互,如下图所示
在这里插入图片描述

1.1.1 Spring AOP的动态代理模式

Spring AOP的动态代理有2种实现方式

  • JDK动态代理:基于接口实现,底层基于反射
  • CGLIB动态代理:基于继承实现
    在这里插入图片描述
    Spring 如何选择动态代理方式 ?(以Spring 4.3.17为例)

源码见:DefaultAopProxyFactory - > createAopProxy

  • 当代理对象实现了接口时,Spring默认使用JDK的动态代理。
  • 当Bean没有实现接口时,Spring默认使用CGLIB动态代理。
  • 当代理对象实现了接口时,也可以强制使用CGLIB(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)
1.1.2 Spring AOP 获取代理对象时序图

在这里插入图片描述

1.2 AspectJ AOP

我们看一下更强大的AspectJ是如何实现的

1.2.1 AspectJ的织入过程

AspectJ利用了特殊的编译器AJC(AspectJ Compiler),在编译时或编译后进行织入(除了LTW)
在这里插入图片描述

2、AOP 术语

要想使用面向对象编程的思想,首先要了解几个专有名词

  • Target:目标类,即需要被代理的类。例如:UserService
  • Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
  • PointCut 切入点:已经被增强的连接点。例如:addUser()
  • Advice 通知/增强,增强代码。例如:after、before
  • Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
  • Proxy 代理类
  • Aspect(切面): 是切入点pointcut和通知advice的结合
    在这里插入图片描述

3、Advice-五种增强方式

例如在执行某个特定方法的时候,我们可以选择不同的增强方式(如前置通知/增强,在方法运行前执行),达到我们织入后的不同效果。

  • 前置通知:在我们执行目标方法之前运行(@Before)
  • 后置通知:在我们目标方法运行结束之后 ,不管有没有异常(@After)
  • 返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)
  • 异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
  • 环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法执行之前相当于前置通知, 执行之后就相当于我们后置通知(@Around)

4、切面表达式

在这里插入图片描述

execution(
    修饰符pattern
    返回值pattern
    描述包名
    方法名(参数)
    方法抛出异常pattern
)

5、实例

5.1 通过注解打印每个方法花费的时间

  • 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogCostTime {

}
  • 实现
@Aspect
@Component
public class LogCostTimeAop {
    final static Logger LOG = LoggerFactory.getLogger(LogCostTimeAop.class);

    @Value("${log.cost.time.enable:true}")
    private boolean logCostTimeEnable;


    @Pointcut("@annotation(com.york.common.annotation.LogCostTime)")
    public void costTimePointCut(){

    }

    @Around("costTimePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //判断开关
        if(!logCostTimeEnable){
           return point.proceed();
        }
        //记录开始时间
        long startTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - startTime;
        logCostTime(point, time);
        return result;
    }

    private void logCostTime(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String className = joinPoint.getTarget().getClass().getName();
        LOG.info("{}.{} cost:{}ms", className, signature.getName(), time);
    }

}

5.2 环绕打印日志

@Aspect
@Component
@Slf4j
public class LogAop {
    @Before(value = "execution(public * com.jktest.controller.*.*(..))")
    public void before(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getName();
        String classMethod = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        StringBuilder params = new StringBuilder();
        for (Object arg : args) {
            params.append(arg).append(" ");
        }
        log.info("{} # {} # {}", className, classMethod, params);
    }

    @Around(value = "execution(public * com.jktest.controller.*.*(..))")
    public Object catchException(ProceedingJoinPoint joinPoint) {
        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            String className = joinPoint.getTarget().getClass().getName();
            String classMethod = joinPoint.getSignature().getName();
            log.info("在{}类中{} 方法 发生了错误", className, classMethod);
            HashMap<String, Object> ret = new HashMap<>(2);
            ret.put("code", "4xx");
            ret.put("msg", "异常");
            return ret;
        }
    }

    @AfterReturning(value = "execution(public * com.jktest.controller.*.*(..))", returning = "returnVal")
    public void afterReturn(JoinPoint joinPoint, Object returnVal) {
        String className = joinPoint.getTarget().getClass().getName();
        String classMethod = joinPoint.getSignature().getName();
        log.info("{} # {} # {}", className, classMethod, JSON.toJSON(returnVal));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还能坚持

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值