当前位置: 首页 > 图灵资讯 > 技术篇> 动态代理

动态代理

来源:图灵教育
时间:2023-06-11 09:19:16

1.动态代理95

在程序运行阶段,为了减少代理的数量,在内存中动态生成代理,称为动态代理。解决代码重用的问题。

1.1动态生成技术在内存中很常见,包括:95

●JDK动态代理技术:只能代理接口。

●CGLIB动态代理技术:CGLIB(CodeGenerationLibrary)这是一个开源项目。它是一个强大、高性能、高质量的Code生成类库,可以在运行期间扩展Java类,实现Java接口。它既可以代理接口,也可以代理接口,底层是通过继承来实现的。性能优于JDK动态代理。(底层有一个小而快的字节码处理框ASM。)

●Javassist动态代理技术:Javassist是一个开源分析、编辑和创建Java字节码的类库。它是由东京工业大学数学与计算机科学系的ShigeruChiba(千叶滋)创建的。通过使用Javassist对字节码操作为Jboss实现动态,加入了开源代码JBoss应用服务器项目AOP"框架。

2.JDK动态代理96

我们仍然使用静态代理的例子:一个接口和一个实现类。

2.1orderservice接口
package com.powernode.proxy.service;/** * 订单业务界面   96 **/public interface OrderService { // 代理对象和目标对象的公共接口。    String getName();    /**     * 生成订单     */    void generate();    /**     * 修改订单信息     */    void modify();    /**     * 查看订单细节     */    void detail();}
2.2接口实现OrderServiceimplepl
package com.powernode.proxy.service;/** * 接口实现类  96 **/public class OrderServiceImpl implements OrderService{ // 目标对象    @Override    public String getName() {        System.out.println("getName()执行方法“);        return "张三";    }    @Override    public void generate() { // 目标方法        // 模拟生成订单的耗时        try {            Thread.sleep(1234);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(”订单已经生成.");    }    @Override    public void modify() { // 目标方法        // 模拟修改订单的耗时        try {            Thread.sleep(456);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(”订单已修改.");    }    @Override    public void detail() { // 目标方法        // 模拟查询订单的耗时        try {            Thread.sleep(111);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(”请参阅订单详情.");    }}
2.3Client客户端程序966

当我们在静态代理时,除了上述接口和实现类别外,我们是否想写一个代理UserserviceProxy!在动态代理中,可以动态生成UserserviceProxy代理。这个类别不需要写。我们可以直接写客户端程序

package com.powernode.mall;import com.powernode.mall.service.OrderService;import com.powernode.mall.service.impl.OrderServiceImpl;import java.lang.reflect.Proxy;动态代理//jdk 客户端程序  96public class Client {    public static void main(String[] args) {        // 第一步:创建目标对象:        OrderService target = new OrderServiceImpl();        // 第二步:创建代理对象:        OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);        // 第三步:调用代理对象的代理方法        orderServiceProxy.detail();        orderServiceProxy.modify();        orderServiceProxy.generate();    }}

以上第二步创建代理对象需要大家理解:

OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);

这行代码做了两件事:

●第一件事:代理字节码在内存中生成

●第二件事:创建代理对象:

2.3.1newproxyinstance翻译为新代理对象96

也就是说,代理对象可以通过调用这种方法来创建。

本质上,这个Proxy.newProxyInstance()执行方法,做了两件事:

第一件事:代理字节码class在内存中动态生成。

第二件事:new对象。代理对象是通过内存中生成的代理代码实例化的。

2.3.2关于newproxyinstance()方法的三个重要参数,每个参数的含义和用途是什么?96

第一个参数:ClassLoaderloader

类加载器。这种类型的加载器有什么用?96

内存中生成的字节码也是class文件,必须先加载到内存中才能执行。加载类需要类加载器。因此,这里需要指定类加载器。

而且JDK要求目标类加载器必须与代理类加载器相同。

第二个参数:Class[]interfaces96

代理类和目标类应实现相同的接口或相同的接口。

当代理类在内存中生成时,这个代理类需要你告诉它实现了什么接口。

第三个参数:InvocationHandlerh97

InvocationHandler被翻译成:调用处理器。这是一个接口。

编写在调用处理器接口中的是:增强代码。

JDK动态代理技术是猜不到的,因为要增强什么代码。没那么神圣。

既然是接口,就要写接口的实现类。

可能有问题吗?

你必须手写调用处理器接口的实现类。这不会爆炸吗?不会的。

因为这个调用处理器可以写一次。

注:代理对象与目标对象实现的界面相同,因此可以向下转换。

2.4InvocationHandler接口实现类97

所以接下来我们要写java.lang.reflect.InvocationHandler接口的实现类,以及接口中的实现方法,代码如下:

package com.powernode.proxy.service;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 一个专门负责计时的调用处理器对象。   97 * 在此调用处理器中编写与计时相关的增强代码。 * 这个调用处理器只需要写一个。 **/public class TimerInvocationHandler implements InvocationHandler {    // 目标对象    private Object target;    public TimerInvocationHandler(Object target) {        // 赋值给成员变量。        this.target = target;    }    /*        1. 为什么要求你实现InvocationHandler接口?        this.target = target;    }    /*        1. 为什么要求你实现InvocationHandler接口?   98            因为一个类实现接口必须实现接口中的方法。            以下方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。            注:invoke方法不是我们程序员调用的,而是JDK调用的。        2. invoke方法什么时候调用?            当代理对象调用代理方法时,在InvocationHandler调用处理器中注册的invoke()方法被调用。        3. invoke方法的三个参数:   99            JDK负责调用invoke方法,因此JDK调用此方法时会自动将这三个参数传输给我们。            我们可以直接使用invoke方法的大括号。            第一个参数:Object proxy 引用代理对象。该参数使用较少。            第二个参数:Method method 目标对象上的目标方法。(要实现的目标方法就是它。)            第三个参数:Object[] args 实参的目标方法。            在invoke执行过程中,使用method调用目标对象的目标方法。         */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        // 这个接口的目的是给你一个写增强代码的地方。   99-100        //System.out.println(增强1);        long begin = System.currentTimeMillis();        // 调用目标对象上的目标方法        // 方法四要素:哪个对象,哪个方法,传输哪些参数,返回的价值是什么?        Object retValue = method.invoke(target, args);        //System.out.println(增强2);        long end = System.currentTimeMillis();        System.out.println("耗时"+(end - begin)+"毫秒");        // 注意invoke方法的返回值,如果代理对象在调用代理方法后需要返回结果,invoke方法必须继续返回目标对象的目标方法执行结果。        return retValue;    }}
2.4.为什么强行要求你实现InvocationHandler接口?98

因为一个类实现接口必须实现接口中的方法。

以下方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。

注:invoke方法不是我们程序员调用的,而是JDK调用的。

2.4.什么时候调用invoke方法?98

当代理对象调用代理方法时,在InvocationHandler调用处理器中注册的invoke()方法被调用。

2.4.三个参数3invoke方法:99

JDK负责调用invoke方法,因此JDK调用此方法时会自动将这三个参数传输给我们。

我们可以直接使用invoke方法的大括号。

第一个参数:引用Objectproxy代理对象。该参数使用较少。

第二个参数:Methodmethod目标对象上的目标方法。

第三个参数:Object[]args目标方法的实参。

在invoke执行过程中,使用method调用目标对象的目标方法。

2.5客户端测试1000
package com.powernode.proxy.client;import com.powernode.proxy.service.OrderService;import com.powernode.proxy.service.OrderServiceImpl;import com.powernode.proxy.service.TimerInvocationHandler;import java.lang.reflect.Proxy;动态代理//jdk 客户端程序  96public class Client {    public static void main(String[] args) {        ///创建目标对象        OrderService target = new OrderServiceImpl();        ///创建代理对象        /*        1. newProxyInstance 翻译为:新代理对象:  96            也就是说,您可以通过调用此方法创建代理对象。            本质上,这个Proxy.newProxyInstance()执行方法,做了两件事:                第一件事:代理字节码class在内存中动态生成。            本质上,这个Proxy.newProxyInstance()执行方法,做了两件事:                第一件事:代理字节码class在内存中动态生成。                第二件事:new对象。代理对象是通过内存中生成的代理代码实例化的。        2. newproxyinstance()方法的三个重要参数是什么意思和用途?   96            第一个参数:ClassLoader loader                类加载器。这种类型的加载器有什么用?     96                    在内存中生成的字节码也是class文件,必须先加载到内存中才能执行。加载类需要类加载器。因此,这里需要指定类加载器。                    而且JDK要求目标类加载器必须与代理类加载器相同。            第二个参数:Class[] interfaces      96                代理类和目标类应实现相同的接口或相同的接口。                当代理类在内存中生成时,这个代理类需要你告诉它实现了什么接口。            第三个参数:InvocationHandler h    97                InvocationHandler 翻译成:调用处理器。它是一个接口。                编写在调用处理器接口中的是:增强代码。                JDK动态代理技术是猜不到的,因为需要增强哪些代码。没那么神。                既然是接口,就要写接口的实现类。                可能有问题吗?                    你必须手写调用处理器接口的实现类。这不会爆炸吗?不会。                    因为这个调用处理器可以写一次。             注:代理对象与目标对象实现的界面相同,因此可以向下转换。         */        OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),                        target.getClass().getInterfaces(),                        new TimerInvocationHandler(target));        ////调用代理对象的代理方法        // 注:调用代理对象的代理方法时,如果要加强,必须保证目标对象的目标方法的实施。        proxyObj.generate();        proxyObj.modify();        proxyObj.detail();        String name =  proxyObj.getName();        System.out.println(name);    }}

动态代理_代理类

在这里学习可能会感到有点困惑,辗转反侧很长一段时间,最后这不是写一个接口实现类吗?没有节省精力啊?

这样想你就错了!!!!

我们可以看到,无论你有多少Service接口和业务类别,这个TimerinvocationHandler接口是否只需要写一次,代码是否被重用!!

最重要的是,在未来,程序员只需要关注核心业务的编写,而不需要关注这样的统计时间代码。因为这个统计时间的代码只需要在调用处理器中编写一次。

在这里,JDK动态代理的原理就结束了。

但是,我们看到以下代码确实有点繁琐,对于客户端来说,使用起来并不方便:

动态代理_代理类_02

上一篇:

CGLIB动态代理

下一篇:

mybatis的注解开发