• 由反射引出的Java动态代理与静态代理


    写在开头

    《深入剖析Java中的反射,由浅入深,层层剥离!》这篇文章中我们讲反射时,曾提到过Java的动态代理中使用了反射技术,那么好,今天我们要就着反射的索引,来学习一下Java中的代理!

    代理模式

    在Java中有多达23种的设计模式(后面Java基础更新完后,会找个时间详细的去写写这些设计模式),恰当的设计模式的使用能够提升代码的效率,简化代码的复杂性。

    而今天我们要说的代理模式就是其中之一,所谓代理是为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

    大白话:买房的(客户方),房产销售(代理方),卖房的(委托方)

    在Java中有静态代理和动态代理两种实现方式,继续放下看!!!

    静态代理

    所谓静态代理,一般是针对编译期就已经完成了接口,实现类,代理类的定义,我们对目标对象的增强需要手工去完成,一个目标对象就要有个代理类,非常不灵活。

    静态代理的实现步骤

    1,因为代理类与被目标对象有相似的行为(共同),所以我们先创建一个接口。

    public interface SaleHouse {
    String saleHouse();
    }

    2,提供接口的实现类,当做目标对象

    public class SaleHouseImpl implements SaleHouse{
    @Override
    public String saleHouse() {
    return "我要卖房子啦!!!";
    }
    }

    3,代理类同样也要实现接口,并在目标方法前后做一些控制操作

    public class SaleHouseProxy implements SaleHouse{
    private SaleHouse saleHouse;
    //提供一个包含目标对象的有参构造
    public SaleHouseProxy(SaleHouse saleHouse) {
    this.saleHouse = saleHouse;
    }
    @Override
    public String saleHouse() {
    //调用方法前,我们可以加一些自己的控制
    System.out.println("我要收代理费!!!");
    System.out.println("--------------------");
    String s = saleHouse.saleHouse();
    System.out.println(s);
    System.out.println("--------------------");
    //调用方法后,我们依旧可以操作
    System.out.println("我要拿提成!!!");
    return "这就是静态代理";
    }
    }

    4,客户端调用代理类,并传入目标对象

    public class Test {
    public static void main(String[] args) throws FileNotFoundException {
    //客户端调用静态代理
    SaleHouse saleHouse = new SaleHouseImpl();
    SaleHouseProxy saleHouseProxy = new SaleHouseProxy(saleHouse);
    saleHouseProxy.saleHouse();
    }
    }

    5,控制台查看打印结果

    我要收代理费!!!
    --------------------
    我要卖房子啦!!!
    --------------------
    我要拿提成!!!

    动态代理

    其实无论是静态代理还是静态代理,在我们的日常开发中,使用的都是很多,但对于SpringAop、RPC等框架来说,动态代理发挥着相当大的作用,动态代理具有:运行时控制,灵活性更好的特点。
    那怎么实现动态代理呢?
    如下三种方式:

    JDK 动态代理
    CGLib 动态代理
    使用 Spring aop 模块完成动态代理功能 //今天先不说这个

    JDK动态代理

    实现步骤:
    1,定义一个接口及其实现类;
    代码同静态代理中步骤1,步骤2;

    2,自定义 InvocationHandler (调用处理器)并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;

    public class JdkDynamicInvocationHandler implements InvocationHandler {
    //代理类中的真实对象
    private final Object target;
    public JdkDynamicInvocationHandler(Object target) {
    this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //调用方法前,我们可以加一些自己的控制
    System.out.println("我要收代理费!!!");
    Object invoke = method.invoke(target, args);
    //调用方法后,我们依旧可以操作
    System.out.println("我要拿提成!!!");
    return invoke;
    }
    }

    3,通过 Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) 方法创建代理对象;其实,这一步也可以写在第2步的代码里,不过为了代码的可读性,我们进行解耦实现!
    3.1,定义一个工厂类,在工厂类中通过Proxy.newProxyInstance()方法获取某个类的代理对象

    public class JdkDynamicProxyFactory {
    public static Object getProxy(Object target) {
    return Proxy.newProxyInstance(
    target.getClass().getClassLoader(), // 目标类的类加载器
    target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
    new JdkDynamicInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
    );
    }

    3.2,客户端传入目标对象,实现代理扩展

    //客户端调用静态代理
    SaleHouse proxySaleHouse = (SaleHouse) JdkDynamicProxyFactory.getProxy(new SaleHouseImpl());
    proxySaleHouse.saleHouse();

    4,控制台输出

    我要收代理费!!!
    我要卖房子啦!!!
    我要拿提成!!!

    【扩展】
    关于Proxy类的静态工厂方法newProxyInstance()如何创建代理实例的过程,感兴趣的可以去读源码,或者参考下面这篇文章《代理模式在开源代码中的应用》

    CGLIB 动态代理

    其实在JDK动态代理中有一个弊端,那就是只能代理接口或接口的实现类,那么未实现任何接口的类就不能代理了吗?答案是否定的,因为咱们有CGLIB!

    CGLIB (Code Generation Library) 是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,CGLIB 通过继承方式实现代理。

    实现步骤:
    1,引入cglib依赖
    因为是第三方实现的动态代理,所以在使用前先引入依赖包

    cglib
    cglib
    3.3.0

    2,定义一个类;

    public class Person {
    public void eat(){
    System.out.println("我在吃饭!!!");
    }
    }

    3,自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;

    public class CglibMethodInterceptor implements MethodInterceptor {
    /**
    * @param o 被代理的对象(需要增强的对象)
    * @param method 被拦截的方法(需要增强的方法)
    * @param args 方法入参
    * @param methodProxy 用于调用原始方法
    */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    //调用方法前,我们可以加一些自己的控制
    System.out.println("饭前先洗手");
    Object object = methodProxy.invokeSuper(o, args);
    //调用方法前,我们可以加一些自己的控制
    System.out.println("饭后要擦嘴");
    return object;
    }
    }

    4,创建一个工厂类,用来构建代理对象,通过 Enhancer 类的 create()方法实现;

    public class CglibProxyFactory {
    public static Object getProxy(Class clazz) {
    // 创建动态代理增强类
    Enhancer enhancer = new Enhancer();
    // 设置类加载器
    enhancer.setClassLoader(clazz.getClassLoader());
    // 设置被代理类
    enhancer.setSuperclass(clazz);
    // 设置方法拦截器
    enhancer.setCallback(new CglibMethodInterceptor());
    // 创建代理类
    return enhancer.create();
    }
    }

    5、客户端调用,通过反射传入Person类信息

    public static void main(String[] args) throws FileNotFoundException {
    //客户端调用静态代理
    Person person = (Person) CglibProxyFactory.getProxy(Person.class);
    person.eat();
    }

    6、输出

    饭前先洗手
    我在吃饭!!!
    饭后要擦嘴

    OK,终于码完了动态代理,自己还去看了很久的源码,头昏脑涨!

    结尾彩蛋

    如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

  • 相关阅读:
    网关Gateway的介绍与使用
    javapoet 注释中添加枚举值
    Binder总结
    MySQL数据库增删改查操作和常用命令
    win10系统x64安装java环境以及搭建自动化测试环境
    linux内核模块编译方法详解
    新增分组柱状图,DataEase开源数据可视化分析平台v1.14.0发布
    论文复现《SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM》
    git强制提交本地分支覆盖掉远程分支
    Java集合框架【二容器(Collection)[Vector容器类]】
  • 原文地址:https://www.cnblogs.com/JavaBuild/p/18004316