AOP详解

AOP简介

AOP的底层原理是动态代理。

AOP也就是面向切面,意思是做出一些通用的功能,如打印日志等,应用于所有需要的地方。传统的面向对象,在每个对象里进行打印日志,重复且无意义。

AOP实现的思路,拦截住一些需要使用打印日志功能的方法,使用动态代理,对原本的方法加入了打印日志的操作。

AOP重要概念

  1. 通知(增强,Advice):需要添加的功能叫做通知,比如说打印日志的操作
  2. 连接点(Join point):就是允许使用通知的地方,基本上每个方法前、后、抛异常时都可以是连接点(也就是before、after)
  3. 切点(Poincut):需要添加新功能的地方,比如需要在类A的方法 f() 前后打印日志, f() 就是切点
  4. 切面(Aspect):其实就是通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行
  5. 引入(Introduction):在无需修改现有类的前提下,让它们具有新的行为和状态。针对方法,而织入重点是实现方式为代理,针对对象。
  6. 目标(target):被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。
  7. 织入(Weaving):把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:编译期(切面在目标类编译时被织入,这种方式需要特殊的编译器)、类加载期(切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码)、运行期(切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的)

AOP具体使用方法

先定义一个切点,也就是需要拦截的地方。
围绕着切点,进行日志打印,共有五种执行日志打印的时机:

  1. before(前置通知): 在方法开始执行前执行
  2. after(后置通知): 在方法执行后执行
  3. around(环绕通知): 在方法执行前和执行后都会执行(优先于before和after)
  4. afterReturning(返回后通知): 在方法返回后执行
  5. afterThrowing(异常通知): 在抛出异常时执行

AOP的具体使用方法见:
传送门

拦截的东西

package com.aili.poem.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class LoveController {

    @RequestMapping(value = "sayLove")
    @ResponseBody
    /**
     * 这里必须加@ResponseBody,否则会出现白页
     */
    public String sayLove(String name){
        System.out.println("方法运行,参数为:" + name);
        return "求爱";
    }
}

拦截后进行的操作

@Aspect
@Component
public class ControllerAspect {

    //切点
    @Pointcut("execution(* com.aili.poem.controller..*(..))")
    public void pointcut(){
    }

    //开始之前做点事
    @Before("pointcut()")
    public void doSomethingBefore(JoinPoint joinPoint){
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println("开始对" + args[0].toString() + "进行求爱");
        System.out.println("Buy follower");
        System.out.println("Buy gift");
        System.out.println("Buy order");
    }

    //结束之后做点事
    @After("pointcut()")
    public void doSomethingAfter(){
        System.out.println("Show friend");
    }

    //前后都做点事(在最前面)
    //这里注意必须返回Object
    @Around("pointcut()")
    public Object doSomethingAround(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("love you,baby");
        //在这条语句之前,相当于@Before,之后则相当于@After
        Object obj = joinPoint.proceed();
        System.out.println("love you,baby");
        return obj;
    }

    //返回结果后做点事
    @AfterReturning(value = "pointcut()", returning = "returnValue")
    public void doSomethingAfterReturn(JoinPoint joinPoint, Object returnValue){
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println("完成对" + args[0].toString() + "的求爱");
        System.out.println("对她进行了" + returnValue.toString());
    }

    //出现意外做点事
    @AfterThrowing("pointcut()")
    public void doSomethingAfterThrow(){
        System.out.println("求爱失败");
    }
}

AOP原理

AOP是Spring的一个特性,是Spring通过动态代理实现的功能。它主要使用两种动态代理方式:

  1. 当目标类实现了至少一个接口时,Spring会优先使用JDK动态代理
  2. 否则使用CGLIB动态代理
1. JDK动态代理
  • 应用场景:当目标类实现了至少一个接口时
  • 实现原理:基于Java反射机制,运行时生成代理类($Proxy开头的类),实现目标对象的接口。
  • 特点:只能代理接口方法,生成的代理类继承Proxy类。
  • 性能开销:较低,只需生成接口代理类
2. CGLIB动态代理
  • 应用场景:当目标类没有实现接口时
  • 实现原理:基于ASM字节码操作框架,通过继承目标类生成子类方法重写覆盖父类方法实现增强
  • 特点:可以代理普通类,通过方法重写实现代理,不能代理final类和方法
  • 性能开销:较高,需要生成子类,字节码操作复杂

什么场景下AOP会失效

  • 方法内部调用:在同一个类中,一个方法调用另一个方法时,AOP代理不会生效。AOP通常通过代理对象实现(如JDK动态代理或CGLIB代理),而内部调用是直接调用目标方法,绕过了代理。
  • 静态方法:AOP无法拦截静态方法的调用。
  • CGLIB代理的情况下,Final方法或类:AOP无法拦截final方法或final类的调用。原因:CGLIB代理通过生成子类来实现代理,而final方法或类无法被继承。
  • Private方法:AOP代理无法访问private方法。
  • 非Spring管理的Bean:AOP依赖于Spring的代理机制,只有Spring管理的Bean才能被代理。如果是我们自己通过 new 实现的对象,就不会被切面切到。
  • 异步方法:异步方法通常在新线程中执行,而AOP的上下文可能无法传递到新线程。
  • AOP拦截顺序问题:多个切面同时拦截同一个方法时,可能因顺序问题导致某些切面未生效。
  • AOP与反射调用:反射调用直接操作目标方法,绕过了AOP代理。

AOP的应用场景

  1. 日志记录
  2. 事务管理‌
  3. 权限管理
  4. 埋点监控

通过注解实现AOP

切点除了目录之外,还可以通过注解的形式:
定义一个注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

    /**
     * 测试
     * 
     * @return s
     */
    String testName() default "";
}

定义注解的切面:

@Aspect
@Component
@Order(1)
public class TestAnnotationAspect {

    /**
     * 定义一个环绕通知
     */
    @Around("@annotation(com.xxx.TestAnnotation)")
    public Object aroundTestAnnotationMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        
        try {
            // 执行被注解的方法
            Object res = joinPoint.proceed();
            // 执行要切面的内容,例如打日志
            
            return res;
        } catch (Throwable e) {
            throw e;
        } finally {
            // do something
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_43751710

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

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

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

打赏作者

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

抵扣说明:

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

余额充值