AOP、代理模式、Spring AOP

本文深入讲解面向切面编程(AOP)的基本概念和技术细节,包括AOP的特点、核心概念及其实现方式,如静态代理和动态代理。此外还介绍了Spring AOP与AspectJ的区别和联系,以及如何在实际项目中应用AOP进行代码增强。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AOP

AOP(Aspect-OrientedProgramming,面向切面编程)
  
在不修改源代码的情况下,可以实现功能的增强。
  
基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 。
  
可以做到将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

特点

1、降低模块之间的耦合度
2、使系统容易扩展
3、更好的代码复用。

概念

  • Aspect(切面):通常是一个类,里面可以定义切入点和通知
  • JointPoint(连接点):被拦截到的每个点,spring中指被拦截到的每⼀个⽅法,spring aop⼀个连接点即代表⼀个⽅法的执⾏。
  • Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
  • Pointcut(切入点):对连接点进⾏拦截的定义(匹配规则定义 规定拦截哪些⽅法,对哪些⽅法进⾏处理),spring 有专⻔的表达式语⾔定义。
  • AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
  • Target(⽬标对象):被代理的⽬标对象
  • . Weave(织⼊):将切⾯应⽤到⽬标对象并⽣成代理对象的这个过程即为织⼊

通知方法

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

关于代理模式

在学习spring中aop之前,先简单了解一下代理模式

为某个对象提供一个代理,以控制对这个对象的访问。在不修改源代码的基础上做方法增强,代理模式是一种设计模式,又简单的分为两种。

静态代理

代理类和委托类在代码运行前关系就确定了,也就是说在代理类的代码一开始就已经存在了。
缺点,由于我们需要事先实现代理类,那么每个方法我都都需要去实现。如果我们要实现很多的代理类,那么工作量就太大了。动态代理的产生就是这样而来的。

动态代理:

动态代理类的字节码在程序运行时的时候生成。
AOP的核心就是采用了动态代理机制。

JDK动态代理

InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理,示例如下

@Slf4j
public class ProxyTest implements InvocationHandler {
    Class<?> target;
    Object real;

    public ProxyTest(Class<?> target) {
        //必须传入接口类型,因为Proxy.newProxyInstance传入的参数必须是接口类型的
        this.target = target;
    }

    public Object bind(Object real) {
        this.real = real;
        return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("proxy before......");
        method.invoke(real, args);
        log.info("proxy after.....");
        return null;
    }

    public static void main(String[] args) {
        ProxyTest proxyTest = new ProxyTest(ITestService.class);
        TestService testService = new TestService();
        ITestService proxy = (ITestService)proxyTest.bind(testService);
        proxy.service();
    }

}

其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用,动态代理是根据被代理的接口生成所有的方法。
即被代理类必须实现一个接口,但是现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。

JDK动态代理小结
现在我们对JDK代理有个简单的源码级别的认识,理清楚一下思路:JDK会帮我们在运行时生成一个代理类,这个代理类实际上就是我们需要代理的接口的实现类。实现的方法里面会调用InvocationHandler类中的invoke方法,并且同时传入自身被调用的方法的的Method对象和参数列表方便我们编码实现方法的调用。比如我们调用reduce方法,那么我们就可以通过Method.Invoke(Object obj, Object… args)调用我们具体的实现类,再在周围做一些代理做的事儿。就实现了动态代理。我们对JDK的特性做一些简单的认识:

  • JDK动态代理只能代理有接口的类,并且是能代理接口方法,不能代理一般的类中的方法
  • 提供了一个使用InvocationHandler作为参数的构造方法。在代理类中做一层包装,业务逻辑在invoke方法中实现
  • 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法
  • 在invoke方法中我们甚至可以不用Method.invoke方法调用实现类就返回。这种方式常常用在RPC框架中,在invoke方法中发起通信调用远端的接口等
CGLIB动态代理

JDK中提供的生成动态代理类的机制有个鲜明的特点是:某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。那么如果一个类没有实现接口怎么办呢?这就有CGLIB的诞生了,前面说的JDK的代理类的实现方式是实现相关的接口成为接口的实现类,那么我们自然而然的可以想到用继承的方式实现相关的代理类。CGLIB就是这样做的。一个简单的CGLIB代理是这样实现的:

@Slf4j
public class CGLIBTest {
    @Test
    public void test(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TestService.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                log.info("proxy before....");
                method.invoke(new TestService(),objects);
                log.info("proxy after.....");
                return null;
            }
        });
        TestService testService = (TestService) enhancer.create();
        testService.service();
    }
}

CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类.
由于是继承方式,不能对final修饰的类,final、private、static的方法进行代理

AspectJ

简介

它不属于spring;
AspectJ是一个AOP的框架;
定义了AOP语法;
有一个专门的编译器用来生成遵守Java字节编码规范的Class文件

什么是AspectJ
AspectJ是使用面向切面的一个框架
它扩展了Java语言(它本身也是一种语言)
支持原生Java代码 有自己的编译器
将代码翻译成Java字节码文件 是为了方便编写AOP代码而出现的
使用AOP编程的三个重点 通知 切点 织入

Spring AOP

Spring AOP 与ApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP 并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题。因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势,但同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。(当然,也可以使用spring整合ApectJ的方式,但是那就是另一种方案了)

Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理(基于接口代理),在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理(基于类代理)

示例代码

//定义切面
@Aspect
@Component
//可以使用@order注解指定切面的优先级,值越小优先级越高
@Order(1)
public class WebLogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);

    /**
     * 切⼊点:
     * 匹配规则。规定什么⽅法被拦截、需要处理什么⽅法
     * 定义切⼊点
     * @Pointcut("匹配规则")
     * Aop 切⼊点表达式简介     *代表所以,set*,代表以set开头的所有方法.
     * https://blog.csdn.net/jinnianshilongnian/article/details/84156354?
     * 1. 执⾏任意公共⽅法:
     * execution(public *(..))
     * 2. 执⾏任意的set⽅法
     * execution(* set*(..))
     * 3. 执⾏com.xxxx.service包下任意类的任意⽅法
     * execution(* com.xxxx.service.*.*(..))
     * 4. 执⾏com.xxxx.service 包 以及⼦包下任意类的任意⽅法
     * execution(* com.xxxx.service..*.*(..))
     *@within:用于匹配所以持有指定注解类型内的方法;
     *@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
     *@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
     *@annotation:用于匹配当前执行方法持有指定注解的方法;
     * 下面表达式代表com.macro.mall.controller包下所有类的所有方法,第⼀个*代表的是⽅法的修饰符为所有(可选值:private、protected、public、*)
     */
    @Pointcut("execution(public * com.macro.mall.controller.*.*(..))||execution(public * com.macro.mall.*.controller.*.*(..))")
    public void webLog() {
    }
     
    /**
    * 之前执行
    */ 
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }

    /**
     * 正常返回后执行
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }

    /**
     * do something....
     * joinPoint.proceed()  //执行目标方法
     * do something....
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        LOGGER.info("before.......");
        Object result = joinPoint.proceed();//执行目标方法
        LOGGER.info("after.......");
    }
  
}

参考

https://blog.csdn.net/JinXYan/article/details/89302126?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值