首页 > 其他分享 >动态代理

动态代理

时间:2023-06-10 13:33:10浏览次数:45  
标签:调用 target invoke 代理 接口 动态 方法

1. 动态代理  95

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

1.1 在内存当中动态生成类的技术常见的包括:95

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

● CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)

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

2. JDK动态代理  96

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

2.1 OrderService接口

package com.powernode.proxy.service;

/**
 * 订单业务接口   96
 **/
public interface OrderService { // 代理对象和目标对象的公共接口。

    String getName();

    /**
     * 生成订单
     */
    void generate();

    /**
     * 修改订单信息
     */
    void modify();

    /**
     * 查看订单详情
     */
    void detail();

}

2.2 接口实现类OrderServiceImpl

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.3 Client客户端程序  96

我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类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动态代理 客户端程序  96
public 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.1  newProxyInstance 翻译为:新建代理对象  96

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

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

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

        第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。

2.3.2 关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用?   96

    第一个参数:ClassLoader loader

        类加载器。这个类加载器有什么用呢?     96

            在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器。所以这里需要指定类加载器。

            并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。

    第二个参数:Class[] interfaces      96

        代理类和目标类要实现同一个接口或同一些接口。

        在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。

    第三个参数:InvocationHandler     h    97

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

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

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

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

        可能会有疑问?

            自己还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会。

            因为这种调用处理器写一次就好。

注意:代理对象和目标对象实现的接口一样,所以可以向下转型。

2.4 InvocationHandler接口的实现类   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接口?   98
            因为一个类实现接口就必须实现接口中的方法。
            以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
            注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。
        2. invoke方法什么时候被调用呢?
            当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。

        3. invoke方法的三个参数:   99
            invoke方法是JDK负责调用的,所以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.1  为什么强行要求你必须实现InvocationHandler接口?   98

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

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

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

2.4.2 invoke方法什么时候被调用呢?98

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

 2.4.3 invoke方法的三个参数:   99

            invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。

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

            第一个参数:Object proxy 代理对象的引用。这个参数使用较少。

            第二个参数:Method method 目标对象上的目标方法。(要执行的目标方法就是它。)

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

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

2.5 客户端测试   100

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动态代理 客户端程序  96
public class Client {
    public static void main(String[] args) {
        //创建目标对象
        OrderService target = new OrderServiceImpl();
        //创建代理对象
        /*
        1. newProxyInstance 翻译为:新建代理对象  96
            也就是说,通过调用这个方法可以创建代理对象。
            本质上,这个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

2.6 我们可以提供一个工具类:ProxyUtil,封装一个方法:101

package com.powernode.proxy.util;

import com.powernode.proxy.service.OrderService;
import com.powernode.proxy.service.TimerInvocationHandler;

import java.lang.reflect.Proxy;

/**
 * 工具类   101
 **/
public class ProxyUtil {

    /**
     * 封装一个工具方法,可以通过这个方法获取代理对象。
     * @param target
     * @return
     */
    public static Object newProxyInstance(Object target){
        // 底层是调用的还是JDK的动态代理。
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));
    }

}
/*OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),
                        new TimerInvocationHandler(target));*/
        // 上面代码通过一个工具类的封装,就简洁了。
        OrderService proxyObj = (OrderService) ProxyUtil.newProxyInstance(target);

动态代理_类加载器_03

标签:调用,target,invoke,代理,接口,动态,方法
From: https://blog.51cto.com/u_15784725/6454476

相关文章

  • CGLIB动态代理
    1. 引入依赖  102CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。使用CGLIB,需要引入它的依赖:<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>2. 具体......
  • IXFH4N100Q-ASEMI代理艾赛斯MOS管IXFH4N100Q
    编辑:llIXFH4N100Q-ASEMI代理艾赛斯MOS管IXFH4N100Q型号:IXFH4N100Q品牌:IXYS/艾赛斯封装:TO-247最大漏源电流:4A漏源击穿电压:1000VRDS(ON)Max:3Ω引脚数量:3工作温度:-55℃~150℃沟道类型:N沟道MOS管、高压MOS管IXFH4N100Q特点低栅极电荷和电容-更易于驾驶-更快的切换国际标准包装RDS低(打开)无......
  • IXFH4N100Q-ASEMI代理艾赛斯MOS管IXFH4N100Q
    编辑:llIXFH4N100Q-ASEMI代理艾赛斯MOS管IXFH4N100Q型号:IXFH4N100Q品牌:IXYS/艾赛斯封装:TO-247最大漏源电流:4A漏源击穿电压:1000VRDS(ON)Max:3Ω引脚数量:3工作温度:-55℃~150℃沟道类型:N沟道MOS管、高压MOS管IXFH4N100Q特点低栅极电荷和电容-更易于驾驶-更快的切换国际标......
  • mybatis-动态构建SQL
    又学习到了,今天翻着队友写的代码,我看到了个注解,起初还以为是自定义的,我搜了下才发现,竟然是mybatis自带的这个注解,我竟然不知道,又是孤陋寡闻的一天,记录一下这个注解。查找了下这个注解发现这个注解是四个:我看了下与@Select有啥区别,这个@SelectProvider是能够加多条件判断的,看下面......
  • IXFA14N85XHV-ASEMI代理艾赛斯MOS管IXFA14N85XHV
    编辑:llIXFA14N85XHV-ASEMI代理艾赛斯MOS管IXFA14N85XHV型号:IXFA14N85XHV品牌:IXYS/艾赛斯封装:TO-263最大漏源电流:14A漏源击穿电压:850VRDS(ON)Max:550mΩ引脚数量:3沟道类型:N沟道MOS管特性:高功率密度、易于安装、节省的空间芯片材质:封装尺寸:如图特性:高压MOS管、N沟道MOS管工作结温:-55℃~1......
  • IXFA14N85XHV-ASEMI代理艾赛斯MOS管IXFA14N85XHV
    编辑:llIXFA14N85XHV-ASEMI代理艾赛斯MOS管IXFA14N85XHV型号:IXFA14N85XHV品牌:IXYS/艾赛斯封装:TO-263最大漏源电流:14A漏源击穿电压:850VRDS(ON)Max:550mΩ引脚数量:3沟道类型:N沟道MOS管特性:高功率密度、易于安装、节省的空间芯片材质:封装尺寸:如图特性:高压MOS管、N沟道MOS管......
  • 用户代理使用
    简介UserAgentUtils是JavaWeb项目中一个非常常用的工具库,用于解析用户浏览器的User-Agent字符串,以获取所访问的设备类型、操作系统类型和版本、浏览器类型和版本等信息。依赖<dependency><groupId>eu.bitwalker</groupId><artifactId>User......
  • 【01-动态规划-01背包问题】
    第一部分什么是动态规划?"动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。由于动态规划并不是某种具体的算法,而是一种解决特定问题的方法,因此它会出现在各式各样的数据结构中,与之相关的题目种类也更为繁杂。在OI中,计数等非最优化问题的递推解......
  • Python+tkinter动态创建与销毁组件小案例
    本文代码演示了如何在tkinter窗体上动态创建组件以及销毁组件的方法。importtkinterimporttkinter.messageboximporttkinter.simpledialogbtnList=[]#动态创建组件,并计算组件在窗体上的位置defplace(n):foriinrange(n):exec('btn'+str(i)+'=tkinter.B......
  • 动态代理:应用、好处
             ......