浅谈Java动态代理的实现

一、代理设计模式

1.1 什么是代理

  • 考虑真实的编程场景,项目中存在一个访问其他数据源的接口,包含一个query()方法
  • 我们已经针对这个接口,实现了MySQL、Hive、HBase、MongoDB等作为数据源的实现类
  • 但是,在测试过程中,我们发现这些数据源的查询并不是很稳定
  • 最原始的想法: 在所有实现类query()方法中,代码首部获取startTime,代码尾部获取endTime,通过打印日志的方式,知道每次查询的耗时
long startTime = System.currentTimeMillis();
logger.info("query mysql start:" + new Date(startTime).toLocaleString());
// 具体的query代码
...

long endTime = System.currentTimeMillis();
logger.info(String.format("query mysql end: %s, consumed time: %dms", new Date(endTime).toLocaleString(), (endTime-startTime)));
  • 直接修改已经实现的方法,存在很多缺点:

(1)现在是打印日志,代码非常简单,就算query()方法不是你实现的,你也能很好的完成。
(2)但是如果是其他功能呢?比如,如果查询失败,要求你查询重试

  • 因此,在不改变已经实现好的query()方法前提下,去实现日志打印的功能是最好的方法。
  • 进阶想法: 我为每个实现类创建一个包装类。

(1)这个包装类与实现类一样,实现了相同的接口。
(2)在query()方法中,直接调用实现类的query()方法,并在调用前后进行日志打印
(3)对实现类方法的调用,都改成对包装类方法的调用

long startTime = System.currentTimeMillis();
logger.info("query mysql start:" + new Date(startTime).toLocaleString());
// 使用try-finally
JSONObject[] data = null;
try {
    data = mysql.query();
    return data;
} catch (Exception exception) {
    throw exception;
} finally {
    long endTime = System.currentTimeMillis();
    logger.info(String.format("query mysql end: %s, consumed time: %dms", new Date(endTime).toLocaleString(), (endTime - startTime)));
}
  • 这时,代理模式的概念就变得非常清晰了:不直接调用实现类的某个方法,而是通过实现类的代理去调用。
  • 这样不仅可以实现调用者与被调用者之间的解耦合,还可以在不修改调用者的情况下,丰富功能逻辑

1.2 代理模式入门

代理模式的UML图如下

1.subject: 抽象主题角色,是一个接口,定义了一系列的公共对外方法

2.real subject: 真实主题角色,也就是我刚刚提到的实现类,又称委托类。
委托类实现抽象主题,负责实现具体的业务逻辑

3.proxy: 代理主题角色,简称代理类。它也实现了抽象主题,用于代理、封装,甚至增强委托类。
一般通过内含委托类,实现对委托类的封装

4.client: 当访问具体的业务逻辑时,clinet表面是访问代理类,而实际是访问被代理类封装的委托类

代理模式的应用场景:目前,就我本人所接触的使用场景,就是通过代理去打印日志、增强业务逻辑 😂

二、Java代理的三种实现

2.1 静态代理

  • 所谓的静态动态,是相对于字节码的生成时机来说的:

(1)静态是指字节码,即class文件,在编译时就已经生成。
(2)动态是指字节码在运行时动态生成,而不是编译时提前生成

  • 刚刚,我们针对数据查询的进阶方法,实际就是静态代理
  • 通过为每个委托类创建对应的代理类,然后编译时就可以得到代理类的字节码

下面是一个具体的静态代理实例:

抽象主题:

public interface Animal {
   void eat();
}

委托类:Dog和Cat,实现了抽象接口

public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("I like eating bone");
    }
}

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("I like eating fish");
    }
}

代理类:代理类中含有对应的委托类,通过调用委托类的具体实现,来封装委托类

public class DogProxy implements Animal {
    private Dog dog;

    public DogProxy(Dog dog) {
        this.dog = dog;
    }

    @Override
    public void eat() {
        System.out.print("I'm a "+dog.getClass().getSimpleName() +". ");
        dog.eat();
    }
}

public class CatProxy implements Animal {
    private Cat cat;

    public CatProxy(Cat cat) {
        this.cat = cat;
    }

    @Override
    public void eat() {
        System.out.print("I'm a " + cat.getClass().getSimpleName()+". ");
        cat.eat();
    }
}

静态代理虽然实现简单、不更改原始的业务逻辑,但是仍然存在以下缺点:

1.如果存在多个委托类,则需要创建多个代理类,这样则会产生过多的代理类

2.如果抽象主题增加、删除、修改方法时,委托类和代理类都需要同时修改,不易维护

2.2 Java自带的动态代理

  • 静态代理存在以上缺点,如果我们能在程序运行时,动态生成对应的代理类,则无需创建并维护过多的代理类
  • 使用Java自带的java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler类,可以实现动态代理
  • 从类的全路径可以看出,Java的动态代理实际利用的是反射机制实现的
  • 其中,Proxy用于创建对应接口的代理类。具体代理的是哪个委托类,是由实现InvocationHandler接口的中介类决定的
  • 代理类、委托类、中介类之间的关系如下

Java自带的动态代理具体实现:

1.创建抽象主题 —— 与静态代理类一致,不再展示

2.创建实现类 —— 与静态代理类一致,不再展示

3.实现InvocationHandler接口,创建中介类

public class AnimalInvokeHandler implements InvocationHandler {
    private Animal animal;

    public AnimalInvokeHandler(Animal animal) {
        this.animal = animal;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy class: " + proxy.getClass().getName());
        System.out.printf("proxy instanceof  Animal: %b \n", proxy instanceof Animal);
        System.out.printf("---- Call method: %s, class: %s ----\n", method.getName(), animal.getClass().getSimpleName());
        // 通过反射,调用委托类的方法
        Object result = method.invoke(animal, args);
        return result;
    }
}

4.通过Proxy创建动态代理类,实现对抽象主题的代理

public static void main(String[] args) {
    // 通过Proxy.newProxyInstance创建代理类
    // 将代理类转为抽象主题,可以动态的创建实现了该主题的代理类
    // 必须从实现类去获取需要代理的接口
    // 指定中介类,通过中介类实现代理
    Animal proxy = (Animal) Proxy.newProxyInstance(Main.class.getClassLoader(), Dog.class.getInterfaces(), new AnimalInvokeHandler(new Dog()));
    proxy.eat();
}

执行结果:

Java原生的动态代理,利用反射动态生成代理类字节码ProxyX.class,然后将其强制转化为抽象主题类型,就能实现对该接口的代理

jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口

Java原生动态代理,又叫jdk动态代理,具有以下优缺点

1.优点: jdk动态代理,避免了静态代理需要创建并维护过多的代理类的

2.缺点: jdk动态代理只能代理接口,因为Java的单继承原则:代理类本身已经继承了Proxy类,就不能再继承其他类,只能实现委托类的抽象主题接口。

2.3 cglib实现动态代理

  • jdk动态代理存在只能代理接口的问题,是十分不方便的。
  • 考虑以下场景:

一个类中有两个方法methodA:转账到其他账户,methodB:查询账户余额。
如果用户访问methodA,希望先提示用户检查账户信息是否正确;
如果用户访问methodB,希望在用户查询完余额后,提示用户关注银行的微信公众号。

  • 上述类已经成功用于业务场景了,我们想要实现这些增强功能,最好不要更改其原始代码,而是通过代理实现功能增强
  • 这时,便可以使用cglib实现对类的动态代理 —— 小白看了实现后,可能会倾向于说这就是类似Java web的拦截器 😂

三、cglib动态代理的实现

添加maven依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.11</version>
</dependency>

简化版的银行系统类

public class BankSystem {
    // 转账
    public boolean transferAccount(double amount, String address) {
        System.out.printf("Send %f dollars to account %s", amount, address);
        // 转账成功
        return true;
    }

    // 查询账户余额
    public String queryBalance(){
        System.out.printf("Query account balance success");
        return "Account balance: 2400 dollars";
    }
}

为转账方法创建拦截器

public class TransferInterceptor implements MethodInterceptor {
    /**
    *
    * @param o,表示要增强的对象,其实就是代理中的委托类
    * @param method,被拦截的方法,其实就是委托类中被代理的方法
    * @param objects,被拦截方法的入参,如果是基本数据类型需要传入包装类型
    * @param methodProxy,对method的代理,通过invokeSuper(),实现对method的调用
    * @return java.lang.Object
    * @author sunrise
    * @date 2021/5/23 10:43 上午
    **/
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before(objects);
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }

    private void before(Object[] args){
        System.out.printf("Please check: you will send %.2f dollar to account %s.\n", args[0], args[1]);
    }
}

为余额查询方法创建拦截器

public class QueryBalanceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 调用预发插叙方法
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }

    private void after() {
        System.out.printf("Please pay attention to WeChat official account.\n");
    }
}

创建filter,实现方法与拦截器的映射

public class BankFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("transferAccount".equals(method.getName())) {
            // 使用拦截器列表中的第1个拦截器
            return 0;
        }
        // 使用拦截器列表中的第2个拦截器
        return 1;
    }
}

使用cglib实现动态代理 —— 我认为就是方法拦截 😂

public static void main(String[] args) {
    // 新建拦截器
    TransferInterceptor transferInterceptor = new TransferInterceptor();
    QueryBalanceInterceptor queryBalanceInterceptor = new QueryBalanceInterceptor();

    // 创建工具类
    Enhancer enhancer = new Enhancer();
    // 设置委托类,在cglib中,这是cglib需要继承的超类
    enhancer.setSuperclass(BankSystem.class);
    // 设置多个拦截器
    enhancer.setCallbacks(new Callback[]{transferInterceptor, queryBalanceInterceptor});
    // 实现拦截器和方法的映射,即为不同的方法配置不同的拦截器
    enhancer.setCallbackFilter(new BankFilter());

    // 创建代理类
    BankSystem proxy = (BankSystem) enhancer.create();

    // 执行转账,调用TransferInterceptor
    boolean ok = proxy.transferAccount(1024.28, "lucy");
    System.out.println("transfer money success: " + ok);
    // 查询余额,调用QueryBalanceInterceptor
    proxy.queryBalance();
}

cglib总结:

1.优点1: cglib基于实现动态代理,通过ASM字节码框架动态生成委托类的子类,并使用方法拦截器实现对委托类方法的拦截。

2.优点2: 基于ASM字节码框架动态生成代理类,比jdk动态生成代理类更加高效

3.缺点: 通过继承委托类创建动态代理类,因此不能代理final委托类或委托类中的final方法

四、面试常见问题

java中代理的实现

共有三种方法:静态代理、JDK动态代理、cglib动态代理

  • 静态代理: 为每个实现类都创建一个对应的代理类,需要创建并维护大量的代理类
  • jdk动态代理: 通过Proxy.newProxyInstance()为抽象主题创建代理类,被代理的委托类包含在InvocationHandler类中,由InvocationHandler类的invoke方法通过反射实现对委托类方法的调用
  • cglib动态代理:通过ASM字节码框架,继承委托类以创建代理类。代理类通过方法拦截器,实现对委托类方法的拦截

三种代理方式的比较

1.静态代理,需要创建并维护大量的委托类

2.jdk动态代理,避免了静态类的上述缺点,但只能代理接口(Java单继承原则,代理类已经继承了Proxy类)

3.cglib动态代理,可以实现对类的代理,并通过方法拦截器实现对委托类(父类)方法的拦截;使用强大的ASM字节码框架,更加高效;通过继承实现对类的代理,使其无法代理final类或类中的final方法

为何调用代理类的方法,会自动进入InvocationHandlerinvoke()方法?

  • 通过newProxyInstance()创建代理类时,会为代理类设置InvocationHandlerh
  • 动态生成的代理类字节码,通过反编译可以发现,它实现了抽象主题中的每个方法
  • 方法的实现,是调用内部成员h.invoke()方法
this.h.invoke(this, method, args);

因此,调用代理类的方法时,实际上会调用InvocationHandlerinvoke()方法

到此这篇关于浅谈Java动态代理的实现的文章就介绍到这了,更多相关Java动态代理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java JDK动态代理(AOP)用法及实现原理详解

    Java-JDK动态代理(AOP)使用及实现原理分析 第一章:代理的介绍 介绍:我们需要掌握的程度 动态代理(理解) 基于反射机制 掌握的程度: 1.什么是动态代理? 2.动态代理能够做什么? 后面我们在用Spirng和Mybatis的时候,要理解怎么使用的. 1.什么是代理? 代理,在我们日常生活之中就有体现,代购,中介,换ip,商家等等. 比如有一家美国的大学,可以对全世界招生.留学中介(代理 ) 留学中介(代理):帮助这家美国的学校招生,中介是学校的代理中介是代替学校完成招生功能 代理特点

  • Java动态代理静态代理实例分析

    代理模式:为其他对象提供一种代理以控制某个对象的访问.用在:在某些情况下,一个客户不想或者不能直接访问另一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用,代理对象还可以完成它附加的操作. 例子:就像房东.租客.中介的关系.中介(代理对象)为房东(真实对象)出租房子,租客(客户)通过中介(代理对象)来找房子租房子,中介完成了租房以后可以收取中介费(附加操作). 先看看静态代理模式,通过上面对代理模式的理解,可以了解到代理模式:即不直接通过new一个真实对象来调用方法,而是通过代理对象来

  • Java基础之动态代理Cglib详解

    一.前言 经测试,jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码.cglib执行速度略大于jdk,所以比较适合单例模式.另外由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中.如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常.spring默认使用jdk动态代理,如果类没有接口,则使用cglib. 二.服务 package proxy.cglib; /** * @Description: <br/>

  • Java静态代理和动态代理的深入讲解

    代理模式 代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问. 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上.在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层. 代理模式的元素是:共同接口.代理对象.目标对象. 代理模式的行为:由代理对象执行目标对象的方法.由代理对象扩展目标对象的方法. 代理模式的

  • Java cglib动态代理原理分析

    本文分下面三个部分来分析cglib动态代理的原理. cglib 动态代理示例 代理类分析 Fastclass 机制分析 一.cglib 动态代理示例 public class Target{ public void f(){ System.out.println("Target f()"); } public void g(){ System.out.println("Target g()"); } } public class Interceptor implem

  • Java两种方式实现动态代理

    一.JDK动态代理 Java 在 java.lang.reflect 包中有自己的代理支持,该类(Proxy.java)用于动态生成代理类,只需传入目标接口.目标接口的类加载器以及 InvocationHandler 便可为目标接口生成代理类及代理对象.我们称这个Java技术为:动态代理 @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, Invoca

  • Java使用JDK与Cglib动态代理技术统一管理日志记录

    Java中动态代理主要有JDK和CGLIB两种方式. 区别主要是jdk是代理接口,而cglib是代理类. 优点:这种方式已经解决我们前面所有日记需要的问题.非常的灵活.而且可以方便的在后期进行维护和升级. 缺点:当然使用jdk动态代理,必需要有接口.如果没有接口.就无法使用jdk动态代理技术. 计算接口 Calculate.java public interface Calculate { /** * 加法运算 * @param num1 参数 1 * @param num2 参数 2 * @r

  • Java简单实现动态代理模式过程解析

    基本知识:JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能生成代理对象. 步骤: 1.首先建一个接口类,并提供一个实现类: public interface ISubject { public int add(int a, int b); } public class Subject implements ISubject { @Override public int add(int a, int b) { return a + b; } } 2.创建一

  • 浅谈Java动态代理的实现

    一.代理设计模式 1.1 什么是代理 考虑真实的编程场景,项目中存在一个访问其他数据源的接口,包含一个query()方法 我们已经针对这个接口,实现了MySQL.Hive.HBase.MongoDB等作为数据源的实现类 但是,在测试过程中,我们发现这些数据源的查询并不是很稳定 最原始的想法: 在所有实现类query()方法中,代码首部获取startTime,代码尾部获取endTime,通过打印日志的方式,知道每次查询的耗时 long startTime = System.currentTimeM

  • 浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)

    一.代理是Java常用的设计模式,代理类通过调用被代理类的相关方法,并对相关方法进行增强.加入一些非业务性代码,比如事务.日志.报警发邮件等操作. 二.jdk静态代理 1.业务接口 /** * 业务接口 * @author pc * */ public interface UserService { // 增加一个用户 public void addUser(); // 编辑账户 public void editUser(); } 2.业务实现类 /** * 业务实现类 * @author pc

  • 浅谈Java反射与代理

    Java反射机制与动态代理,使得Java更加强大,Spring核心概念IoC.AOP就是通过反射机制与动态代理实现的. 1 Java反射 示例: User user = new User(); user.setTime5Flag("test"); Class<?> cls = Class.forName("com.test.User"); //接口必须public,无论是否在本类内部使用!或者使用cls.getDeclaredMethod(),或者遍历修

  • 浅谈Java 代理机制

    目录 一.常规编码方式 二.代理模式概述 三.静态代理 3.1.什么是静态代理 3.2.代码示例 四.Java 字节码生成框架 五.什么是动态代理 六.JDK 动态代理机制 6.1.使用步骤 6.2.代码示例 七.CGLIB 动态代理机制 7.1.使用步骤 7.2.代码示例 八.什么情况下使用动态代理 九.静态代理和动态代理对比 十.总结 一.常规编码方式 在学习代理之前,先回顾以下我们的常规编码方式:所有 interface 类型的变量总是通过向上转型并指向某个实例的. 1)首先,定义一个接口

  • 浅谈JAVA设计模式之代理模式

    代理模式 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能.这种类型的设计模式属于结构型模式. 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口. 介绍 意图: 为其他对象提供一种代理以控制对这个对象的访问. 主要解决: 在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上.在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时

  • 浅谈Java中static和非static的区别

    关于static和非static变量的区别 1. static 修饰的变量称为类变量或全局变量或成员变量,在类被加载的时候成员变量即被初始化,与类关联,只要类存在,static变量就存在.非static修饰的成员变量是在对象new出来的时候划分存储空间,是与具体的对象绑定的,该成员变量仅为当前对象所拥有的. 2. static修饰的变量在加载的时候先于main方法加载在内存中的数据共享区-------方法区,而非static的变量在加载的时候,是要创建变量才加载在堆内存中的. 3. 一个stat

  • 浅谈Java中的class类

    Class 类是在Java语言中定义一个特定类的实现.一个类的定义包含成员变量,成员方法,还有这个类实现的接口,以及这个类的父类.Class类的对象用于表示当前运行的 Java 应用程序中的类和接口. 比如:每个数组均属于一个 Class 类对象,所有具有相同元素类型和维数的数组共享一个Class 对象.基本的 Java 类型(boolean, byte, char, short,int, long, float 和 double) 和 void 类型也可表示为 Class 对象. 以下示例使用

  • 浅谈Java中Unicode的编码和实现

    Unicode的编码和实现 大概来说,Unicode编码系统可分为编码方式和实现方式两个层次. 编码方式 字符是抽象的最小文本单位.它没有固定的形状(可能是一个字形),而且没有值."A"是一个字符,"€"也是一个字符.字符集是字符的集合.编码字符集是一个字符集,它为每一个字符分配一个唯一数字. Unicode 最初设计是作为一种固定宽度的 16 位字符编码.也就是每个字符占用2个字节.这样理论上一共最多可以表示216(即65536)个字符.上述16位统一码字符构成基

  • 浅谈java+内存分配及变量存储位置的区别

    Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识.一般Java在内存分配时会涉及到以下区域: ◆寄存器:我们在程序中无法控制 ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象) ◆堆:存放用new产生的数据 ◆静态域:存放在对象中用static定义的静态成员 ◆常量池:存放常量 ◆非RAM存储:硬盘等永久

  • 浅谈Java泛型让声明方法返回子类型的方法

    泛型典型的使用场景是集合.考虑到大多数情况下集合是同质的(同一类型),通过声明参数类型,可免去类型转换的麻烦.本文将讨论本人阅读Spring Security源码时遇到的一个关于泛型递归模式的问题. 声明方法返回子类型 在Spring Security的源码里有一个ProviderManagerBuilder接口,声明如下 public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> ext

随机推荐