揭秘AOP和动态代理技术,一次扒个清楚,哪还有神秘可言?

揭秘AOP和动态代理技术,一次扒个清楚,哪还有神秘可言?

AOP 是什么东西?

首先来说 AOP 并不是 框架的核心技术之一,AOP 全称 ,即面向切面的编程。其要解决的问题就是在不改变源代码的情况下,实现对逻辑功能的修改。常用的场景包括记录日志、异常处理、性能监控、安全控制(例如拦截器)等,总结起来就是,凡是想对当前功能做变更,但是又不想修改源代码的情况下,都可以考虑是否可以用 AOP 实现。

为什么要面向切面呢,我直接改源代码不是很好吗?当然没有问题,如果情况允许。但是考虑到下面这些情况,我本来写好了1000个方法,有一天,我想加入一些控制,我想在执行方法逻辑之前,检查一些系统参数,参数检查没问题再执行逻辑,否则不执行。这种情况怎么办呢java 动态代理 cglib,难道要修改这1000个方法吗,那简直就是灾难。还有揭秘AOP和动态代理技术,一次扒个清楚,哪还有神秘可言?,有些线上逻辑执行缓慢,但我又不想重新部署环境,因为那样会影响线上业务,这种情况下,也可以考虑 AOP 方式, 就是这样一个线上性能排查的神器。

AOP 的用法

面向切面编程,名字好像很炫酷,但是使用方式已经被 封装的非常简单,只需要简单的配置即可实现。使用方式不是本文介绍的重点,下面仅演示最简单最基础的使用,实现对调用的方法进行耗时计算,并打印出来。

环境说明: JDK 1.8 , mvc 版本 4.3.2.

1、首先引用 mvc 相关的 maven 包,太多了,就不列了,只列出 -aop 相关的


    org.springframework
    spring-aop
     4.3.2.RELEASE 


    org.aspectj
    aspectjrt
    1.8.9


    org.aspectj
    aspectjweaver
    1.8.9

2、在 mvc 配置文件中增加关于 AOP 的配置java 动态代理 cglib,内容如下:



 
 
           
 

3、创建切面类,并在 kite.lab.. 包下的方法设置切面,使用 @ 注解监控,实现执行时间的计算并输出,内容如下:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Component
@Aspect
public class PerformanceMonitor {
    
    //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
    @Pointcut("execution(* kite.lab.spring.service..*(..))")
    public void aspect(){ }
    @Around("aspect()")
    public Object methodTime(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束
        System.out.println(String.format("方法 %s 耗时 %s ms!",pjp.getSignature().toShortString(), stopWatch.getTotalTimeMillis()));
        return retVal;
    }
}

4、被切面监控的类定义如下:

package kite.lab.spring.service;
public class Worker {
    public String dowork(){
        System.out.println("生活向来不易,我正在工作!");
        return "";
    }
}

5、加载 mvc 配置文件,并调用 类的方法

public static void main(String[] args) {
        String filePath = "spring-servlet.xml"; 
        ApplicationContext ac = new FileSystemXmlApplicationContext(filePath);
        Worker worker = (Worker) ac.getBean("worker");
        worker.dowork();
    }

6、显示结果如下:

java 动态代理 cglib

用法介绍完了,让我们脱下 AOP 华丽的衣衫,不,是揭下她神秘的面罩,来看一看她的芳容,了解一下她的内心。

AOP 原理

AOP 的实现原理就是动态的生成代理类,代理类的执行过程为:执行我们增加的代码(例如方法日志记录)—-> 回调原方法 ——> 增加的代码逻辑。看图比较好理解:

java 动态代理 cglib

AOP 动态代理可能采用 JDK 动态代理或 CGlib 动态生成代理类两种方式中的一种, 决定用哪一种方式的判断标准就是被切面的类是否有其实现的接口,如果有对应的接口,则采用 JDK 动态代理,否则采用 CGlib 字节码生成机制动态代理方式。

java 动态代理 cglib

代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。代理类和委托类实现相同的接口,所以调用者调用代理类和调用委托类几乎感觉不到差别。

是不是看完了定义,感觉正好可以解决切面编程方式要解决的问题。下图是基本的静态代理模式图:

java 动态代理 cglib

而动态代理的意思是运行时动态生成代理实现类,由于 JVM 的机制,需要直接操作字节码,生成新的字节码文件,也就是 .class 文件。

JDK 动态代理

JDK 动态代理模式采用 sun 的 的字节码框架。要说明的是揭秘AOP和动态代理技术,一次扒个清楚,哪还有神秘可言?,只有实现了接口的类才能使用 JDK 动态代理技术,实现起来也比较简单。

1、只要实现 接口,并覆写 方法即可。具体实现代码如下:

package kite.lab.spring.aop.jdkaop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * JdkProxy
 *
 * @author fengzheng
 */
public class JdkProxy implements InvocationHandler {
    private Object target;
    /**
     * 绑定委托对象并返回一个代理类
     *
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        //取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
    /**
     * 调用方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        System.out.println("事物开始");
        //执行方法
        result = method.invoke(target, args);
        System.out.println("事物结束");
        return result;
    }
}

Proxy. 方法用于动态生成实际生成的代理类,三个参数依次为被代理类的类加载器、被代理类所实现的接口和当前代理拦截器。

覆写的 中可以加入我们增加的业务逻辑,然后回调原方法。

2、被代理的类仍然用的前面 aop 介绍的那个 类,只不过我们需要让这个类实现自接口,接口定义如下:

package kite.lab.spring.service;
/**
 * IWorker
 *
**/
public interface IWorker {
    String dowork();
}

3、实际调用如下:

 public static void main(String[] args) {
        JdkProxy jdkProxy = new JdkProxy();
        IWorker worker = (IWorker) jdkProxy.bind(new Worker());
        worker.dowork();
}

原理说明: .bind 会生成一个实际的代理类,这个生成过程是利用的字节码生成技术,生成的代理类实现了 接口,我们调用这个代理类的 方法的时候,实际在代理类中是调用了 (也就是我们实现的这个代理拦截器)的 方法,接着执行我们实现的 方法,也就执行了我们加入的逻辑,从而实现了切面编程的需求。

我们把动态生成的代理类字节码文件反编译一下,也就明白了。由于代码较长,只摘出相关部分。

首先看到类的接口和继承关系:

public final class $Proxy0 extends Proxy implements IWorker

代理类被命名为 $ ,继承了 Proxy ,并且实现了 ,这是关键点。

找到 方法,代码如下:

public final String dowork() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

super.h 就是我们实现的 这个类,可以看到调用了这个类的 方法,并且传入了参数 m3 ,再来看 m3 是什么,

m3 = Class.forName("kite.lab.spring.service.IWorker")
               .getMethod("dowork", new Class0);

看到了吧,m3 就是 方法java 动态代理 cglib,是不是流程就明确了。

但是,并不是所有的被代理的类(要被切面的类)都实现了某个接口,没有实现接口的情况下,JDK 动态代理就不行了,这时候就要用到 CGlib 字节码框架了。

CGLIB 动态代理

CGlib库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,它可以在运行时基于一个类动态生成它的子类。厉害了吧,不管有没有接口,凡是类都可以被继承,拥有这样的特点,原则上来说,它可以对任何类进行代码拦截,从而达到切面编程的目的。

CGlib 不需要我们非常了解字节码文件(.class 文件)的格式,通过简单的 API 即可实现字节码操作。

基于这样的特点,CGlib 被广泛用于如 AOP 等基于 代理模式的AOP框架中。

下面就基于 CGlib 实现一个简单的动态代理模式。

1、创建拦截类实现 接口,并覆 方法,在此方法中加入我们增加的逻辑,代码如下:

public class MyAopWithCGlib implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("嘿,你在干嘛?");
        methodProxy.invokeSuper(o, objects);
        System.out.println("是的,你说的没错。");
        return null;
    }

2、被代理的类依然是上面的 类,并且不需要接口。

3、客户端调用代理方法的代码如下:

 public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
        MyAopWithCGlib aop = new MyAopWithCGlib();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Worker.class);
        enhancer.setCallback(aop);
        Worker worker = (Worker) enhancer.create();
        worker.dowork();
    }

代码第一行是要将动态生成的字节码文件持久化到磁盘,方便反编译观察。

利用 CGlib 的 对象,设置它的继承父类,设置回调类,即上面实现的 拦截类,然后用 方法创造一个 类,实际这个类是 类的子类,然后调用方法。执行结果如下:

java 动态代理 cglib

可以看到我们织入的代码起作用了。

4、上面功能比较简单,它会横向切入被代理类的所有方法中,下来我们稍微做的复杂一点。控制一下,让有些方法被织入代码,有些不被织入代码,模仿 aop ,我们新增一个注解,用于注解哪些方法要被横向切入。注解如下:

package kite.lab.spring.aop.AopWithCGlib;
import java.lang.annotation.*;
/**
 * CGLIB
 *
 * @author fengzheng
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CGLIB {
    String value() default "";
}

5、然后再 中增加一个方法,并应用上面的注解

package kite.lab.spring.service;
import kite.lab.spring.aop.AopWithCGlib.CGLIB;
/**
 * Worker
 *
 * @author fengzheng
 */
public class Worker  {
    public String dowork(){
        System.out.println("生活向来不易,我正在工作!");
        return "";
    }
    @CGLIB(value = "cglib")
    public void dowork2(){
        System.out.println("生活如此艰难,我在奔命!");
    }
}

我们在 上应用了上面的注解

6、在拦截方法中加入注解判断逻辑,如果加了上面的注解,就加入织入的代码逻辑,否则不加入,代码如下:

@Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Annotation[] annotations = method.getDeclaredAnnotations();
        boolean isCglib = false;
        for(Annotation annotation: annotations){
            if (annotation.annotationType().getName().equals("kite.lab.spring.aop.AopWithCGlib.CGLIB")){
                isCglib = true;
            }
        }
        if(isCglib) {
            System.out.println("嘿,你在干嘛?");
            methodProxy.invokeSuper(o, objects);
            System.out.println("是的,你说的没错。");
        }
        return null;
    }

7、调用方法如下:

public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
        MyAopWithCGlib aop = new MyAopWithCGlib();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Worker.class);
        enhancer.setCallback(aop);
        Worker worker = (Worker) enhancer.create();
        worker.dowork();
        worker.dowork2();
    }

执行结果应该为 不执行被织入的逻辑, 执行被织入的代码逻辑,执行结果如下:

java 动态代理 cglib

另外说一下,CGlib 不支持 final 类, CGlib 的执行速度比较快,但是创建速度比较慢,所以如果两种动态代理都适用的场景下,有大量动态代理类创建的场景下,用 JDK 动态代理模式,否则可以用 CGlib 。

标准的 MVC 框架,一般都是一个服务接口类对应一个实现类,所以根据 AOP 的判断逻辑,应该大部分情况下都是使用的 JDK 动态代理模式。当然也可以手动改成 CGlib 模式。

最后

掌握 AOP 的实现原理,你就掌握了又一个核心科技。回过头一看,害,也挺简单。

公告:
1. 本站所有资源来源于用户上传和网络,如有侵权请联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系站长处理!
6. 本站不售卖代码,资源标价只是站长收集整理的辛苦费!如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
7. 站长QQ号码 2205675299

资源库 - 资源分享下载网 » 揭秘AOP和动态代理技术,一次扒个清楚,哪还有神秘可言?

常见问题FAQ

关于资源售价和售后服务的说明?
本站所有资源的标价均为本站收集资源的辛苦费,不代表资源本身的价值。软件是高智慧高价值的商品,不可能是白菜价。本站资源标价只是赞助费用,收取的赞助费仅用来维持本站的日常运营!毕竟收集整理资料需要投入云计算资源和站长大量的精力。
代码有没有售后服务和技术支持?
由于代码的运行具有不可预见性,本站不保证代码完整可运行,不提供技术支持和售后服务。 本站原创代码都是站长自己开发的,可以有偿提供技术支持服务。 网站里标明【亲测】的代码都是站长亲测过的,其他的代码由于精力有限,没有一一测试,不能保证代码就一定能够使用,更没有技术支持服务,下载前请自行斟酌。
有没有搭建服务?
由于搭建服务比较费时费力,所以本站除了原创代码外均不提供搭建服务。本站分享代码纯属兴趣爱好,不以盈利为目的,请勿咨询有没有搭建服务,谢谢理解。
链接地址失效了怎么办?
请带上资源链接地址联系客服,工作时间内我们看到后将第一时间回复。
关于解压密码
本站资源一般都没有加密,如果发现需要解压密码的,那么就输入 hao.35dc.com 试试。

发表评论

资源库,由老程序员细心甄别、精心筛选,只为提供优质的源码资源

关于我们 联系我们