GoF之代理模式(动态代理)

动态代理

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数 量。解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
● JDK动态代理技术:只能代理接口。
● CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
● Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
我们还是使用静态代理中的例子:一个接口和一个实现类。

package com.powernode.proxy.service;

/**
 * 订单接口
 * @author 江洋
 * @version 1.0
 * @className OrderService
 * @since 1.0
 **/
public interface OrderService {
    /**
     * 返回张三
     *
     * @return
     */
    String Return();
    /**
     * 生成订单
     */
    void generate();

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

    /**
     * 修改订单
     */
    void modify();
}

package com.powernode.proxy.service;


/**
 * @author 江洋
 * @version 1.0
 * @className OrderServiceImpl(目标对象)
 * @since 1.0
 **/
public class OrderServiceImpl implements OrderService {
    @Override
    public String Return() {
        System.out.println("Return方法执行");
        return "张三1";
    }

    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类UserServiceProxy呀!在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:

package com.powernode.proxy.client;

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

import com.powernode.proxy.service.TimeInvocationHandler;
import com.powernode.proxy.util.ProxyUtil;

import java.lang.reflect.Proxy;

/**
 * @author 江洋
 * @version 1.0
 * @className Client
 * @since 1.0
 **/
public class Test {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
/*
        1. newProxyInstance翻译为:新建代理对象
           也就是说,通过调用这个方法可以创建代理对象。
           本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事:
           第一件事:在内存中动态的生成了一个代理类的字节码class。
           第二件事: new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
        2.关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用?
           第一个参数: ClassLoader loader
           类加载器。这个类加载器有什么用呢?
           在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器。所以这里需要指定类加载器。
           并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。

           第二个参数:Class<?>[] interfaces
           代理类和目标类要实现同一个接口或同一些接口。
           在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。

           第三个参数: InvocationHandler h
            InvocationHandler被翻译为:调用处理器。是一个接口。
            在调用处理器接口中编写的就是:增强代码。
            因为具体要增强什么代码,JDK动态代理技术它是猜不到的。没有那么神。既然是接口,就要写接口的实现类。
            可能会有疑问?
            自已还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会。因为这种调用处理器写一次就好。
            注意:代理对象和目标对象实现的接口一样,所以可以向下转型。
        */
       /* OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimeInvocationHandler(target));*/
        OrderService proxyObj = (OrderService)ProxyUtil.newProxyInstance(target);
        // 调用代理对象的代理方法
        proxyObj.generate();
        proxyObj.Return();
    }

}

以上第二步创建代理对象是需要大家理解的:
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
这行代码做了两件事:
● 第一件事:在内存中生成了代理类的字节码
● 第二件事:创建代理对象
Proxy类全名:java.lang.reflect.Proxy。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
其中newProxyInstance()方法有三个参数:
● 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
● 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
● 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
所以接下来我们要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

package com.powernode.proxy.service;

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

public class TimeInvocationHandler implements InvocationHandler {
    private  Object target;
    public TimeInvocationHandler( Object target){
        this.target = target;
    }
    /*
        1.为什么强行要求你必须实现InvocationHandler接口?
        因为一个类实现按口就必须实现接口中的方法。
        以下这个方法必颈是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。
        2. invoke方法什么时候被调用呢?
        当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。
        3. invoke方法的三个参数:
        invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。我们可以在invoke方法的大括号中直接使用。
        第一个参数:Object proxy代理对象的引用。这个参数使用较少。
        第二个参数:Method method目标对象上的目标方法(要执行的目标方法就是它。)
        第三个参数:Object[] args目标方法上的实参。

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

*/

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("增强1");

        //调用目标对象上的目标方法
        //方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
        Object retValue = method.invoke(target,args);

        System.out.println("增强2");
        return retValue;
    }
}

封装的工具类

package com.powernode.proxy.util;

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

import java.lang.reflect.Proxy;

public class ProxyUtil {
    public static Object newProxyInstance(Object target){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimeInvocationHandler(target));
    }
}

运行的结果

D:\JavaTwo\jdk-17.0.5\bin\java.exe "-javaagent:D:\JavaTwo\IntelliJ IDEA 2022.2.1\lib\idea_rt.jar=60166:D:\JavaTwo\IntelliJ IDEA 2022.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\spring6\spring6\dynamic-proxy-jdk\target\classes com.powernode.proxy.client.Test
增强1
订单已生成
增强2
增强1
Return方法执行
增强2

Process finished with exit code 0

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值