深入理解SpringAOP中的动态代理

本文详细解析了代理模式的原理,包括静态代理和动态代理的实现方式,探讨了其在代码扩展性和性能优化上的应用,特别介绍了JDK动态代理的底层机制。

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

代理

什么是代理?

买火车票,app就是一个代理;每个明星都有自己的经纪人,经纪人就是代理。

个人理解:为了增强目标类的功能,但是不在目标类内部完成该功能的逻辑代码,根据开发原则单一原则和开闭原则,目标类有自己的主线任务要完成,而通过继承或聚合生成一个代理类,让代理类来完成非主线任务如:生成日志、使用事务等与主线任务无关的任务,实现无侵入式代码扩展。

代理的意义通过生成代理,来避免访问真实的目标类,实现无入侵式的代码扩展,在方法前后做一些事情,并且不修改源码;

代理的两种实现方式

静态代理和动态代理;

静态代理

实现静态代理的方式有两种:继承、聚合

继承
案例:
目标类

public class UserDaoImpl {

	//目标类的主线任务
    public void query(){
        System.out.println("假装查询数据库");
    }
}

现在要给目标类添加打印日志的功能:

public class UserLogDaoProxy extends UserDaoImpl {

    @Override
    public void query() {
        System.out.println("打印日志");
        super.query();
    }
}

外部访问时直接访问代理对象:

public class Test {

    public static void main(String[] args) {
        UserLogDaoProxy userLogDao = new UserLogDaoProxy();
        userLogDao.query();
    }
}

聚合实现
目标类和代理类实现同一个接口,在代理类中创建目标类对象,并调用方法;

public class UserTimerDaoProxy implements Dao {

    public void query(){
        UserDaoImpl userDao = new UserDaoImpl();
        System.out.println("打印时间");
        userDao.query();
    }
}

静态代理的缺点

  • 每一个功能的代理类都需要程序员手动创建,扩展性不好;
  • 如果有很多要添加的功能,产生的类很多,尤其是各个功能之间的排列组合,更会产生大量的代理类。

动态代理技术

自己模拟动态代理

基本思路
不需要手动去创建类文件(因为一旦手动创建,就会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个类产生的类文件成class文件,继而利用URLClassLoader类加载器把这个动态编译的类加载到jvm中,最后通过反射把这个类实例化。

具体实现
原始编写java代码方式

.java -> javac ->.class -> 类加载

创建一个专门生成代理对象的类ProxyUtils,只需提供接口;

  • 通过字符串拼接生成java文件的内容;
  • 使用第三方编译类编译成class;
  • 使用URL类加载器通过反射生成代理类对象。
package com.luban.util;
public class ProxyUtil {

    public static Object newInstance(Object target){
        //1、 通过目标对象反射,添加代理逻辑生成代理类
        //获取目标类实现的接口,也就是代理类要实现的接口
        Class targetInf = target.getClass().getInterfaces()[0];
        //获取接口中声明的方法
        Method[] methods = targetInf.getDeclaredMethods();
        //接口名
        String infName = targetInf.getSimpleName();
        //换行符
        String line="\n";
        //tab符
        String tab="\t";
        //总的文件内容
        String fileContent="";

        //开始构造

        //所在包
        String packageContont = "package com.google;"+line;
        //导入包
        String importContent = "import " + targetInf.getName() + ";" + line;
        String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
        String fieldContent = tab + "private " + infName + " target;" + line;
        String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
                + tab + tab + "this.target=target;" + line
                + tab + "}" + line;

        try {
            String methodContent="";
            for (Method method : methods) {
                //获取返回值类型
                String returnTypeName = method.getReturnType().getSimpleName();
                //获取方法名
                String methodName = method.getName();
                //获取参数类型数组
                Class[] parameterTypes = method.getParameterTypes();
                String argsContent="";
                String paramsContent="";
                int flag=0;
                for (Class parameterType : parameterTypes) {
                    String temp = parameterType.getSimpleName();
                    argsContent+=temp+" p"+flag+",";
                    paramsContent+="p"+flag+",";
                    flag++;
                }
                //取掉参数内容最后的逗号
                if (argsContent.length()>0){
                    argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
                    paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
                }

                methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line
                        //添加的逻辑代码,先以硬编码方式写入
                        +tab+tab+"System.out.println(\"log\");"+line
                        +tab+tab+"target."+methodName+"("+paramsContent+");"+line
                        +tab+"}"+line;
            }
            fileContent=packageContont+importContent+
                    clazzFirstLineContent+fieldContent+
                    constructorContent+methodContent+"}";

            //将内容输出到文件
            File file = new File("d:\\com\\google\\$Proxy.java");
            if (!file.exists()){
                file.createNewFile();
            }
            FileWriter fw = new FileWriter(file);
            fw.write(fileContent);
            fw.flush();
            fw.close();

            //编译生成的java文件生成class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
            Iterable units = fileMgr.getJavaFileObjects(file);

            JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
            t.call();
            fileMgr.close();

            //加载class文件
            URL[] urls = {new URL("file:D:\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class<?> clazz = urlClassLoader.loadClass("com.google.$Proxy");

            //通过构造器创建实例对象
            Constructor<?> constructor = clazz.getConstructor(targetInf);
            Object proxy = constructor.newInstance(target);
            return proxy;
        }catch (Exception e){
            e.printStackTrace();
        }
       return null;
    }
}

测试:

 Dao proxy = (Dao) ProxyUtil.newInstance(new UserDaoImpl());
        proxy.query();

这个代理类生成工具可以代理任何类,不用再一个功能一个类的去创建,当然我们模拟的还不够完善;

  • 不能代理有返回值的方法;
  • 代理逻辑是写死的;

缺点

  • 要生成文件;
  • 要动态编译文件class;
  • 需要一个URLclassLoader类加载器;

先来看看JDK动态代理如何完成
JDK动态代理通过反射机制完成,动态代理把代理类中对目标类的逻辑处理抽象成函数式内部类InvocationHandler,其中的invoke方法进行逻辑处理;代理通过InvocationHandler实现对目标类的工作,不需要像静态代理那样,对每个方法分别的处理;动态代理的调用处理程序必须实现InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类

动态代理解决的问题
在直接访问对象时带来的问题,比如要访问的对象在远程的机器上,在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或需要进程外的访问),直接访问会给使用者或系统结构带来很多麻烦,可以在访问此对象时加上一个此对象的访问层。

JDK动态代理生成代理类的底层实现

使用JDK代理

  Dao proxy = (Dao)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{Dao.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("log");
                method.invoke(new UserDaoImpl(),args);
                return null;
            }
        });
        proxy.query();
  • Test.class.getClassLoader()提供一个类加载器,要使用当前的类加载器,在java中判断两个对象是否相等的前提条件是是否为同一个类加载器所加载;所以当我们在运行过程中产生一个类的时候就需要用当前的类加载器去加载;
  • new Class[]{Dao.class}告诉JDK动态代理要代理哪些接口,然后它会帮我们把接口下的所有方法都进行代理;
  • new InvocationHandler()提供一个代理逻辑的实现,告诉JDK如何代理。

关于new InvocationHandler()我们可以自己定义一个实现;

public class MyInvocationHandler implements InvocationHandler {

    Object target;
    //我们需要在代理逻辑中通过目标对象调用目标方法调用目标方法
    public MyInvocationHandler(Object target){
        this.target=target;
    }
    /**
     *
     * @param proxy 代理对象
     * @param method 要代理的目标方法
     * @param args 方法需要的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("log");
        return method.invoke(target);
    }
}

 Dao proxy = (Dao)Proxy.newProxyInstance(Test.class.getClassLoader(),
  new Class[]{Dao.class},new MyInvocationHandler(new UserDaoImpl()));
        proxy.query();

将代理逻辑也就是MyInvocationHandler传给代理类,代理类里面调用invoke方法,通过反射将代理对象、要代理的方法及参数传递进去。invoke方法的逻辑我们在外部已经定义好了,有两个任务:执行代理逻辑(我们已经在外部定义好了),调用目标方法,调用目标方法需要通过目标对象来执行,所以我们把目标对象(由我们自己传入)以及方法参数(在代理类中通过反射获取)传入目标方法的调用方法里面去。现在只要将这个动态的代理逻辑代替我们模拟的硬编码逻辑就完成了JDK动态代理的模拟。

接口

public interface UserService {

    void a(int x,int y);
    void b();
    void c();
}

被代理的目标类

public class UserServiceTarget implements UserService {
    public void a(int x,int y) {
        System.out.println("方法a");
    }

    public void b() {
        System.out.println("方法b");
    }

    public void c() {
        System.out.println("方法c");
    }
}
public class TestDynamicProxy {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1. 生成实现接口的代理类的字节码
        // 参数1 String 表示代理类的名字;参数2 Class[] 表示代理类要实现的接口,相当于我们生成的java文件,不会JDK内部直接使用字节码来存储
        final byte[] proxyBytes = ProxyGenerator.generateProxyClass("UserServiceProxy", new Class[]{UserService.class});

        //2. 执行类加载,将生成的代理类字节码加载为类对象
        //生成加载器
        ClassLoader cloader=new ClassLoader(){
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                return defineClass(name,proxyBytes,0,proxyBytes.length);
            }
        };

        //类加载后生成的代理类
        Class c=cloader.loadClass("UserServiceProxy");

        //3. 创建代理类实例对象,不能直接用new 创建

        //获取代理类的构造方法
        Constructor constructor = c.getConstructor(InvocationHandler.class);

        //目标类
        final UserServiceTarget target = new UserServiceTarget();


        //创建代理类实例
        UserService  proxy=(UserService) constructor.newInstance(new InvocationHandler() {
            //指定代理类所干的事情,即测量目标类方法运行时间
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.nanoTime();

                //执行目标类的方法
                method.invoke(target, args);

                long endTime = System.nanoTime();
                System.out.println("方法运行时间:" + (endTime - startTime));
                return null;
            }
        });

    proxy.a(10,20);
    }
}

直接使用Proxy来生成代理类

public class TestDyanmicProxy2 {
    public static void main(String[] args) {
        //直接创建代理类的实例

        //1. 获取类加载器
        ClassLoader cl = UserService.class.getClassLoader();
        //2. 规定代理类要实现的接口
        Class[] interfaces = {UserService.class};
        //3. 给定一个InvocationHandler对象,包含要执行的重复逻辑
        UserServiceTarget target = new UserServiceTarget();

        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long startTime = System.nanoTime();

                //执行目标类的方法
                method.invoke(target, args);

                long endTime = System.nanoTime();
                System.out.println("方法运行时间:" + (endTime - startTime));
                return null;
            }
        };

        //直接生成代理类对象
        UserService u =(UserService) Proxy.newProxyInstance(cl, interfaces, h);
        System.out.println(u.getClass());//class com.sun.proxy.$Proxy0
        u.b();
        u.c();
    }
}

模拟与JDK代理的不同

  • JDK代理中有很多检查的流程,较为严谨;
  • JDK是生成的代理类是字节码数组,由jvm将字节码转换为class(native 方法),再由类加载器加载成实例对象,与我们的相比减少了IO,性能较高;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值