Java动态代理的实现


  关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式–代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。

一、代理模式

  代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
在这里插入图片描述

二、静态代理

1.什么是静态代理?

  静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

2.静态代理的实现。

举例:班长代理学生上交班费给老师。
Step1:创建公共接口

/**
 * @description:        Person接口 (Subject接口类定义)
 * @author: Liu Wen
 * @create: 2020-03-27 14:42
 **/
public interface Person {
    public void giveMoney();
}

Step2:创建被代理类

/**
 * @description:   Student类实现Person接口。Student可以具体实施上交班费的动作。
 * @author: Liu Wen
 * @create: 2020-03-27 14:42
 **/
public class Student implements Person{
    public String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void giveMoney() {
        System.out.println(name+"上交班费30元");
    }
}

Step3:创建代理类
  StudentProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Person接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

/**
 * @description:   创建代理类
 * @author: Liu Wen
 * @create: 2020-03-27 14:44
 **/
public class StudentProxy implements Person {

    Student student;

    public StudentProxy(Person student) {
        if(student.getClass() == Student.class)
        this.student = (Student) student;
    }

    @Override
    public void giveMoney() {
        System.out.println(student.name+"最近Java的进步很大!");   //方法加强
        student.giveMoney();
    }
}

Step4:测试
  这里并没有直接通过被代理对象stu来执行上交班费的行为,而是通过代理对象stuProxy来代理执行了,这就是代理模式。
  可以看到,在代理类中帮stu上交班费之前,执行了方法加强。这种操作,也是使用代理模式的一个很大的优点。
  最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

/**
 * @description: Good good study,day day up!
 * @author: Liu Wen
 * @create: 2020-03-27 14:46
 **/
public class TestStudentProxy {
    public static void main(String[] args){
        //被代理的学生,他的上交班费由代理对象stuProxy完成。
        Person stu = new Student("稳稳");
        //生成代理对象,并将stu传给代理对象。
        Person stuProxy = new StudentProxy(stu);
        //代理对象stuProxy完成上交班费动作。
        stuProxy.giveMoney();
    }
}

输出:
在这里插入图片描述

三、动态代理

1.什么是动态代理?

  代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

2.动态代理的实现。

  在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

  直接上代码,仍然以班长代理学生提交班费(加提交作业)为例:

Step1:创建公共接口

/**
 * @description:  创建Person接口
 * @author: Liu Wen
 * @create: 2020-03-27 15:03
 **/
public interface Person {
    public void giveMoney();
    public void submitHomework();
}

Step2:创建被代理类

import java.util.concurrent.TimeUnit;
/**
 * @description: 创建需要被代理的实际类
 * @author: Liu Wen
 * @create: 2020-03-27 15:04
 **/
public class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void giveMoney() {
        try {
            TimeUnit.SECONDS.sleep(1);    //数钱用时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"上交班费30元!");
    }

    @Override
    public void submitHomework() {
        try {
            TimeUnit.SECONDS.sleep(5);   //写作业用时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"提交作业");
    }
}

Step3:创建StuInvocationHandler类
  创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

package com.liuwen.JVM虚拟机.动态代理;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @description:   创建StuInvocationHandler类
 * @author: Liu Wen
 * @create: 2020-03-27 15:07
 **/
public class StuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler持有的被代理对象
    T target;

    public StuInvocationHandler(T target) {
        this.target = target;
    }
    /**
     * proxy: 代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     * @date 20.3.27 15:09
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" +method.getName() + "方法");
//        System.out.println(proxy.getClass());
        System.out.println("方法前加强!");
        Object result = method.invoke(target, args);
        System.out.println("方法后加强!");
        return result;
    }
}

  jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会依次递增)的代理类,这个类文件是放在内存中的。我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
  我们可以把InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
Step4:测试

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
 * @description: Good good study,day day up!
 * @author: Liu Wen
 * @create: 2020-03-27 15:10
 **/
public class TestDynamicProxy {
    public static void main(String[] args){
        //创建一个实例对象,这个对象是被代理的对象
        Person stu = new Student("稳稳");
        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(stu);
        //创建一个代理对象stuProxy来代理stu,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
        //代理执行上交班费的方法 ,代理执行提交作业方法!
        stuProxy.giveMoney();
        stuProxy.submitHomework();
    }
}

输出
在这里插入图片描述
  总结:可以看出:动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
  可以看到,在代理类中帮stu上交班费之前,执行了方法加强。这种操作,也是使用代理模式的一个很大的优点。
  最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
  这就是AOP的一个简单实现,可以在目标对象的方法执行之前和执行之后进行相应的加强处理(这里仅仅打印输出语句)。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。

四、动态代理原理分析

  直接点开Proxy.newProxyInstance()看源码:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        //获取公共接口的clone
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        //*生成代理类————动态代理的关键*
        Class<?> cl = getProxyClass0(loader, intfs);
        
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //获取代理类的构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //返回代理实例
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,可以发现它只是封装了创建动态代理类的步骤(看上述源码中注释)。
创建一个动态代理对象的底层步骤:
step1:创建一个InvocationHandler对象;

 InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);

step2:使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass;

  Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});

step3:获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor;

Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);	

step4:通过构造器constructor来创建一个动态实例stuProxy。

Person stuProxy = (Person) cons.newInstance(stuHandler);

五、最后

事实上,在invoke方法中可以通过proxy.getClass()获取代理类:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass());
        Object result = method.invoke(target, args);
        return result;
    }

在这里插入图片描述
  可以看出,jdk会为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

  我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

  代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

发布了54 篇原创文章 · 获赞 18 · 访问量 2709
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览