Java动态代理

简介: Java动态代理详解

要学习Java中的动态代理,首先需要了解的是一种常用的设计模式--代理模式

一 代理模式

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

二 静态代理

2.1 概念

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

2.2 静态代理简单实现

举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。

首先,我们创建一个Person接口。这个接口就是学生(被代理类/委托类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。

/**
 * 创建Person接口
 */
public interface Person {
   
   
    //上交班费
    void giveMoney();
}

Student类实现Person接口。Student可以具体实施上交班费的动作。

public class Student implements Person {
   
   
    private String name;
    public Student(String name) {
   
   
        this.name = name;
    }

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

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

/**
 * 学生代理类,也实现了Person接口,保存一个学生实体,这样就可以代理学生产生交费行为
 *
 */
public class StudentsProxy implements Person{
   
   
    //被代理的学生
    Student stu;

    public StudentsProxy(Person stu) {
   
   
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
   
   
            this.stu = (Student)stu;
        }
    }

    //代理上交班费,调用被代理学生的上交班费行为
    public void giveMoney() {
   
   
        //根据实际需求,上交班费前可以做一些事情
        stu.giveMoney();
        //根据实际需求,上交班费后也可以做一些事情
    }
}

下面测试一下,使用代理模式:

public class StaticProxyTest {
   
   
    public static void main(String[] args) {
   
   
        //被代理的学生张三,他的班费上交由代理对象monitor(班长)完成
        Person zhangsan = new Student("张三");

        //生成代理对象,并将张三传给代理对象
        Person monitor = new StudentsProxy(zhangsan);

        //班长代理上交班费
        monitor.giveMoney();
    }
}

结果:
image.png

这里并没有直接通过张三(被代理对象/委托类)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。

代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,代理类(StudentsProxy)里的方法(giveMoney)中,在调用委托类(Student)的方法(giveMoney)前后都可以执行其他的一些操作
image.png

这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

三 动态代理

3.1 概念

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

 public void giveMoney() {
   
   
        //调用被代理方法前加入处理方法
        beforeMethod();
        stu.giveMoney();
}

这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果除了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。那看看下面动态代理如何实现。

3.2 动态代理简单实现

目前java动态代理的实现分为两种

1.基于JDK的动态代理

2.基于CGILB的动态代理

3.2.1 基于JDK的动态代理

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

创建一个动态代理对象步骤:

  1. 创建一个InvocationHandler对象
//创建一个与代理对象相关联的InvocationHandler
 InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
  1. 使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {
   
   Person.class});
  1. 获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
  1. 通过构造器constructor来创建一个动态实例stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);

至此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化:

//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
  Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{
   
   Person.class}, stuHandler);

完整动态代理代码
还是上面静态代理的例子,班长需要帮学生代交班费

首先是定义一个Person接口:

public interface Person {
   
   
    //上交班费
    void giveMoney();
}

创建需要被代理的类:

public class Student implements Person {
   
   
    private String name;
    public Student(String name) {
   
   
        this.name = name;
    }

    @Override
    public void giveMoney() {
   
   
        try {
   
   
          //假设数钱花了一秒时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        }
       System.out.println(name + "上交班费50元");
    }
}

再定义一个检测方法运行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

public class MonitorUtil {
   
   

    private static ThreadLocal<Long> tl = new ThreadLocal<>();

    public static void start() {
   
   
        tl.set(System.currentTimeMillis());
    }

    //结束时打印耗时
    public static void finish(String methodName) {
   
   
        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
    }
}

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

再再invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。

public class StuInvocationHandler<T> implements InvocationHandler {
   
   
   //invocationHandler持有的被代理对象
    T target;

    public StuInvocationHandler(T target) {
   
   
       this.target = target;
    }

    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   
        System.out.println("代理执行" +method.getName() + "方法");
     */   
        //代理过程中插入监测方法,计算该方法耗时
        MonitorUtil.start();
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());
        return result;
    }
}

做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简化的方式创建动态代理对象:

public class ProxyTest {
   
   
    public static void main(String[] args) {
   
   

        //创建一个实例对象,这个对象是被代理的对象
        Person zhangsan = new Student("张三");

        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);

        //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{
   
   Person.class}, stuHandler)//代理执行上交班费的方法
        stuProxy.giveMoney();
    }
}

结果:
image.png

我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法

案例二:
此处我们不做业务场景,就直接通过代码来理解动态代理

  1. 创建一个接口
public interface Subject {
   
   
    void hello(String param);
}
  1. 创建被代理类,实现接口
public class SubjectImpl implements Subject {
   
   
    @Override
    public void hello(String param) {
   
   
        System.out.println("hello  " + param);
    }
}
  1. 创建SubjectImpl的代理类
public class SubjectProxy implements InvocationHandler {
   
   
    private Subject subject;

    public SubjectProxy(Subject subject) {
   
   
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   
        System.out.println("--------------begin-------------");
        Object invoke = method.invoke(subject, args);
        System.out.println("--------------end-------------");
        return invoke;
    }
}

invoke方法的说明:
image.png

  1. 编写代理类实际的调用,利用Proxy类创建代理之后的Subject类。
public class Main {
   
   
    public static void main(String[] args) {
   
   
        Subject subject = new SubjectImpl();
        InvocationHandler subjectProxy = new SubjectProxy(subject);
        Subject proxyInstance = (Subject) Proxy.newProxyInstance(subjectProxy.getClass().getClassLoader(), subject.getClass().getInterfaces(), subjectProxy);
        proxyInstance.hello("world");
    }
}

输出:

--------------begin-------------
hello world
--------------end-------------

看这个结果,实际上在Subject类中只会输出一条hello world,但是在被代理之后,实际调用的方法是SubjectProxy的invoke方法,这样可以在不修改业务类的情况下对业务类增加一些日志等其他操作,甚至可以直接修改有返回值方法的返回值。

3.2.2 于CGILB的动态代理

用的比较多的是CGLIB的动态代理,因为基于JDK的动态代理一定要继承一个接口,而绝大部分情况是基于POJO类的动态代理,那么CGLIB就是一个很好的选择,在Hibernate框架中PO的字节码生产工作就是靠CGLIB来完成的。还是先看代码。

  1. 引入CGLIB的jar包

  2. 创建被代理类(委托类)

public class CGsubject {
   
   
    public void sayHello(){
   
   
        System.out.println("hello world");
    }
}

如果直接对这个类创建对象,那么调用sayHello方法,控制台就会输出hello world,现在我们还是要对输出添加前置和后置的log输出。来打印输出前和输出后的时间。

  1. 创建代理类,实现MethodInterceptor接口,对方法进行拦截处理
public class HelloInterceptor implements MethodInterceptor{
   
   
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
   
   
        System.out.println("begin time -----> "+ System.currentTimeMillis());
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("end time -----> "+ System.currentTimeMillis());
        return o1;
    }
}

image.png

  1. 创建代理类的调用类
public class Main {
   
   

    public static void main(String[] args) {
   
   
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CGsubject.class);
        enhancer.setCallback(new HelloInterceptor());
        CGsubject cGsubject = (CGsubject) enhancer.create();
        cGsubject.sayHello();
    }

}

利用Enhancer来生产被代理类,这样可以拦截方法,对方法进行前置和后置log的添加。

输出:

begin time -----> 1534836443741
hello world
end time -----> 1534836443786

这样普通的java类也能被代理,没必要像jdk代理那样非得有接口才能被代理;

相关文章
|
1月前
|
Java
JAVA 静态代理 & 动态代理
【11月更文挑战第14天】静态代理是一种简单的代理模式实现,其中代理类和被代理类的关系在编译时已确定。代理类实现与被代理类相同的接口,并持有被代理类的实例,通过调用其方法实现功能增强。优点包括代码结构清晰,易于理解和实现;缺点是对于多个被代理类,需为每个类编写相应的代理类,导致代码量大增,维护成本高。动态代理则在运行时动态生成代理类,更加灵活,减少了代码冗余,但可能引入性能损耗和兼容性问题。
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
38 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
Java
深入理解Java动态代理
深入理解Java动态代理
94 1
|
2月前
|
Java
Java代码解释静态代理和动态代理的区别
### 静态代理与动态代理简介 **静态代理**:代理类在编译时已确定,目标对象和代理对象都实现同一接口。代理类包含对目标对象的引用,并在调用方法时添加额外操作。 **动态代理**:利用Java反射机制在运行时生成代理类,更加灵活。通过`Proxy`类和`InvocationHandler`接口实现,无需提前知道接口的具体实现细节。 示例代码展示了两种代理方式的实现,静态代理需要手动创建代理对象,而动态代理通过反射机制自动创建。
|
2月前
|
设计模式 缓存 Java
从源码学习Java动态代理|8月更文挑战
从源码学习Java动态代理|8月更文挑战
|
5月前
|
缓存 Java 测试技术
day27:Java零基础 - 动态代理
【7月更文挑战第27天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
46 2
day27:Java零基础 - 动态代理
|
4月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
307 0
|
4月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
211 0
|
5月前
|
开发框架 Java Android开发
Java中的类反射与动态代理详解
Java中的类反射与动态代理详解
|
5月前
|
Java 数据安全/隐私保护
Java中的动态代理机制详解
Java中的动态代理机制详解