Java动态代理一览笔录

Wesley13
• 阅读 428

1、什么是代理?

比较经典的含义如销售代理,签订合同的基础上,为委托人(厂商)销售某些特定产品或全部产品的代理商,对价格、条款及其他交易条件可全权处理。我们从销售代理那里购买产品,通常是不知道销售代理背后的委托人(厂商)是谁,也就是 "委托人" 对于我们来说是不可见的。

代理,简单来说,也就是提供代理人,并有代理人全权处理委托人的事务。

在Java中,代理模式,类似的,也就是为某个对象(即委托人)提供一个代理对象(即代理人),并由代理对象(即代理人)全权控制对于原对象(即委托人)的访问。客户对于委托人不再可见,也不能直接操作,而必须通过代理对象间接地操控。

那么我们稍微总结一下,代理的优点:

优点一:隐藏委托类的实现;

优点二:对客户和委托类间进行解耦,在不修改委托类的代码情况下,可以做一些的额外处理,如预处理,过滤,转发等。

------------------------------------------------------------------------------------------------------------

常用的代理运用场景:

1、方法增强,利用反射、动态代理实现AOP切面编程,典型就是Spring AOP

2、远程调用代理(RPC实现的基础),如Java标准库的RMI,其它比如hessian,dubbo,各种webservice框架中的远程调用

-------------------------------------------------------------------------------------------------------------

2、静态代理

静态代理,是指代理类,在程序运行前已编译为.class文件。一般静态代理,要求代理类和委托类实现同一个接口或派生自同一个父类。

举个例子,以提供商品服务为例。

商品提供接口:

public interface GoodsProivder {

    // 提供商品
    public void provider();
}

商品提供者,如具体的厂商,假设是魅族:

public class GoodsProivderImpl implements  GoodsProivder{
    @Override
    public void provider() {
        System.out.println("provider goods : Meizu MX6");
    }
}

销售代理,替代魅族提供商品给客户:

public class SalesProxy implements GoodsProivder {

    private final GoodsProivder proivder;

    public SalesProxy(GoodsProivder proivder) {
        this.proivder = proivder;
    }

    // 提供商品
    public void provider() {
        System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
        this.proivder.provider();
        System.out.println("欢迎您的光临,再见!...");
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        // 这里对于客户而言,销售代理是可见的,厂商是不可见的
        GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
        proxy.provider();
    }
}

输出:

Java动态代理一览笔录

3、动态代理

动态代码,是指指代理类,在程序运行时通过反射动态生成。相比静态代理,动态代理可以更加方便地统一处理委托类的代理过程,而不需要修改代理类,对委托类的诸多函数方法逐个进行代理。

为什么这么说呢?

如上面的静态代理的例子,如果委托接口不仅是一个方法,如果有上百个方法,当我们需要对上百个方法做统一的处理,如预处理、过滤、日志等。我们就必须对代理类上百个方法逐个进行添加或修改逻辑。

因此,接下来我们看看动态代理如何实现。

4、动态代理的实现

动态代理有很多种,包括JDK动态代理、CGLIB、Javassist、ASM等。其中JDK动态代理指的是JDK默认的动态代理实现。CGLIB、Javassist、ASM则需要对应的第三方类库。

Java动态代理一览笔录

为实现动态代理,这里将抽象出 InvocationHandler,专门用于统一调用处理。

动态代理基本的工作流程:自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。

那么为实现动态代理,大约有三种方法:

1、比较直观的方式,Proxy 和 功能实现类 都实现 同一个功能接口。

2、比较隐晦的方式,Proxy 继承 功能实现类,实现多态。

3、实际操作字节码,实现动态代码。(这个就不是以上工作模式的范畴了)

这里JDK采用方法1,CGLIB采用方法2,Javassist和ASM采用方法3。

以下为各类动态代码实现的区别比较:

动态代理

特点

易用性

JDK动态代理

需要绑定接口

简易

CGLIB

不需要绑定接口,基于ASM包装

简易

Javassist

动态生成和改变类结构

相对简单,直接使用java编码,不需要了解虚拟机指令

ASM

操纵字节码

复杂,需要对class组织结构和汇编语言有一定了解

推荐使用CGLIB 或者 Javassist Bytecode方式,具体哪种可以根据JDK版本以及第三方类库版本进行测试对比,进行选择。

(参照第8节)

5、JDK动态代理

JDK动态代理工作流程:

1.  获取 功能实现类XXX 上的所有接口列表;

2.  确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;

3.  根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;

4 .  将对应的字节码转换为对应的class 对象;

5.  创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;

6.  Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象

我们先看看JDK有哪些API:

java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。 

Proxy方法清单:

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 
 
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
 
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) 
 
// 方法 4: 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
    InvocationHandler h)

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个参数是被调用的方法参数。
Object invoke(Object proxy, Method method, Object[] args)

还是刚刚静态代理,提供商品服务的例子,功能接口GoodsProivder、功能实现类GoodsProivderImpl保持不变。

这时候,销售代理SalesProxy类我们可以不写,换成编写销售调用处理器:

import io.flysium.standard.proxy.statics.GoodsProivder;

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

/**
 * 销售代理调用器
 *
 * @author Sven Augustus
 */
public class SalesInvocationHandler implements InvocationHandler {

    private Object target; // 委托类对象;

    public SalesInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 这里可以做一下预处理
//        if(!proxy instanceof  GoodsProivder || !"provider".equals(method.getName())) {
//            throw new UnsupportedOperationException("不支持");
//        }
        System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
        Object result = method.invoke(this.target, args);
        System.out.println("欢迎您的光临,再见!...");
        return result;
    }
}

测试类:

    public static void main(String[] args) {
//        GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
        // 利用 功能实现类,以及调用器,生成代理类实例
        GoodsProivder proxy = (GoodsProivder) Proxy.newProxyInstance(GoodsProivder.class.getClassLoader(),
                new Class[]{GoodsProivder.class},
                new SalesInvocationHandler(new GoodsProivderImpl()));
        proxy.provider();
    }

我们可以看到关键点:

1、编写一个InvocationHandler实现,定义如何调用和处理 功能实现类。

2、使用Proxy的api,加载功能实现类接口定义,以及InvocationHandler实例,创建代理类实例。

6、CGLIB

CGLIB,全称Code Generation Library 。与JDK动态代理不同的是,不需要绑定接口。

源码: https://github.com/cglib/cglib

工作流程:

1.  查找XXX上的所有非final 的public类型的方法定义;

2.  将这些方法的定义转换成字节码;

3.  将组成的字节码转换成相应的代理的class对象;

4.  实现 MethodInterceptor接口,用来处理 对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

CGLIB的包结构:

  • net.sf.cglib.core 底层字节码处理类。

  • net.sf.cglib.transform 该包中的类用于class文件运行时转换或编译时转换。

  • net.sf.cglib.proxy 该包中的类用于创建代理和方法拦截。

  • net.sf.cglib.reflect 该包中的类用于快速反射,并提供了C#风格的委托。

  • net.sf.cglib.util 集合排序工具类。

  • net.sf.cglib.beans JavaBean工具类。

使用cglib,Maven:

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

主要依赖ASM、apache的ant。

我们先看看cglib有哪些主要API:

net.sf.cglib.proxy.Enhancer    主要的增强类。

//设置产生的代理对象的父类
void setSuperclass(java.lang.Class superclass) 。
//设置CallBack接口的实例。一般为 MethodInterceptor
void setCallback(Callback callback) 
//设置多个CallBack接口的实例。一般为 MethodInterceptor
void setCallbacks(Callback[] callbacks) 
//设置方法回调过滤器。
void setCallbackFilter(CallbackFilter filter) 
//使用默认无参数的构造函数创建目标对象。
Object create() 
//使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。
Object create(Class[], Object[]) 

net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,它是Callback接口的子接口,需要用户实现。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是被代理类的对象实例,第二个参数是被调用的方法对象
// 第三个参数是被调用的方法参数,第四个参数是代理类实例。
Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable

net.sf.cglib.proxy.CallbackFilter 可以有选择的对一些方法使用回调。

还是刚刚静态代理,提供商品服务的例子,功能接口GoodsProivder、功能实现类GoodsProivderImpl保持不变。

这时候,销售代理SalesProxy类我们可以不写,换成编写MethodInterceptor:

import io.flysium.standard.proxy.statics.GoodsProivder;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

/**
 * 销售方法拦截器接口
 *
 * @author Sven Augustus
 */
public class SalesMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
 throws Throwable {
        // 这里可以做一下预处理
//        if(!(target instanceof GoodsProivder) || !"provider".equals(method.getName())) {
//            return null;
//        }
        System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
        //Object result=proxy.invoke(target, args);
        Object result = proxy.invokeSuper(target, args);// 表示调用原始类的被拦截到的方法。
        System.out.println("欢迎您的光临,再见!...");
        return result;
    }
}

测试类:

import net.sf.cglib.proxy.Enhancer;

public static void main(String[] args) {
//        GoodsProivder proxy = new SalesProxy(new GoodsProivderImpl());
        // 利用 功能实现类,以及调用器,生成代理类实例
//        GoodsProivder proxy = (GoodsProivder) Proxy.newProxyInstance(GoodsProivder.class.getClassLoader(),
//                new Class[]{GoodsProivder.class},
//                new SalesInvocationHandler(new GoodsProivderImpl()));
        // cglib 中加强器
        Enhancer enhancer = new Enhancer();
        // 设置要创建动态代理的类
        enhancer.setSuperclass(GoodsProivderImpl.class);
        // 设置回调,这里相当于是对于代理类上所有方法的调用
        enhancer.setCallback(new SalesMethodInterceptor());
        // 创建动态代理类实例
        GoodsProivder proxy = (GoodsProivder) enhancer.create();
        proxy.provider();
    }

我们可以看到关键点:

1、编写一个MethodInterceptor实现,定义如何调用和处理 功能实现类。

2、创建Enhancer,并设置动态被代理的类,设置回调MethodInterceptor的实例,然后创建代理类实例。

7、Javassist

使用javassist,Maven:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.21.0-GA</version>
</dependency>

javassist.util.proxy.MethodHandler 提供了类似的方法调用处理器。

// 第一个参数Javassist动态生成的代理类实例,第二个参数是被调用的方法对象
// 第三个参数是为生成的代理类对方法的代理引用,第四个参数是被调用的方法参数
Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable

还是刚刚静态代理,提供商品服务的例子,功能接口GoodsProivder、功能实现类GoodsProivderImpl保持不变。

这时候,销售代理SalesProxy类我们可以不写,换成编写SaleMethodHandler:

import javassist.util.proxy.MethodHandler;

import java.lang.reflect.Method;

/**
 * @author Sven Augustus
 */
public class SaleMethodHandler implements MethodHandler {

    public Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable {
        System.out.println("欢迎您光临本店,您可以随意浏览和购买,祝您购物愉快...");
        //Object result = m.invoke(delegate, args);
        Object result = proceed.invoke(self, args); // execute the original method.
        System.out.println("欢迎您的光临,再见!...");
        return result;
    }
}

测试类:

import javassist.util.proxy.MethodFilter;

import javassist.util.proxy.Proxy;

import javassist.util.proxy.ProxyFactory;

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        ProxyFactory f = new ProxyFactory();
        // 设置被代理类
        f.setSuperclass(GoodsProivderImpl.class);
        // 设置方法过滤器
        f.setFilter(new MethodFilter() {
            public boolean isHandled(Method m) {
                // ignore finalize()
                return !m.getName().equals("finalize");
            }
        });
        // 创建代理类
        Class c = f.createClass();
        GoodsProivder proxy = (GoodsProivder) c.newInstance();
        // 设置方法调用处理器
        ((Proxy) proxy).setHandler(new SaleMethodHandler());
        proxy.provider();
    }

8、性能对比

分为两轮多次测试

第一轮:

JDK-1.6.0_45, CGLIB-3.2.5, JAVAASSIST-3.21.0.GA

结果:

Create JDK Proxy: 9 ms
Create CGLIB Proxy: 140 ms
Create JAVAASSIST Proxy: 62 ms
Create JAVAASSIST Bytecode Proxy: 98 ms
----------------
Run JDK Proxy Average: 776.0 ms
Run CGLIB Proxy Average: 223.4 ms
Run JAVAASSIST Proxy Average: 828.8 ms
Run JAVAASSIST Bytecode Proxy Average: 76.0 ms

可以看到JDK6下的JAVAASSIST Bytecode最好,CGLIB动态代理性能也不错,然后是JDK和JAVAASSIST差不多的。

第二轮:

使用的版本分别为:

JDK-1.7.0_80, CGLIB-3.2.5, JAVAASSIST-3.21.0.GA

结果:

Create JDK Proxy: 19 ms
Create CGLIB Proxy: 256 ms
Create JAVAASSIST Proxy: 124 ms
Create JAVAASSIST Bytecode Proxy: 127 ms
----------------
Run JDK Proxy Average: 117.6 ms
Run CGLIB Proxy Average: 129.2 ms
Run JAVAASSIST Proxy Average: 259.4 ms
Run JAVAASSIST Bytecode Proxy Average: 76.2 ms

可以看到JDK7下的JAVAASSIST Bytecode最好,JDK动态代理性能也不错,然后是CGLIB的。

------------------------------------------------------------------------------------------------

测试结论:

1、JAVAASSIST Bytecode字节码生成方式非常快,是CGLIB的5倍。

2、JDK6下 CGLIB次之,是JDK自带的两倍。JDK7下呢两者差不多。

3、JAVAASSIST提供者动态代理接口最慢,比JDK自带的还慢。

推荐:

如果对字节码操作比较熟悉的,首选JAVAASSIST Bytecode字节码方式实现动态代理。

否则就选择CGLIB吧。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
代理商有三种合作方式供选择
1.股权式合作  股权式合作,也就是厂家以入股的方式来控制代理商的渠道和网络。如格力空调西南销售公司就是由格力集团同代理商共同入股组成的。股权式合作是厂家与代理商合作水平很高的一种,其联系也为紧密。◆由代理商和厂家双方直接组成董事会来监管经销业务,由职业经理去具体操作。这种合作方式,使双方成员的独立性部分丧失,整个渠道的活动都将全部受制于董事
Wesley13 Wesley13
2年前
java的动态代理
1\.什么是动态代理代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色。Java的动态代理比代理的思想更前进了一步,它可以动态地创建并代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相
搞懂设计模式——代理模式 + 原理分析
举个栗子,众所周知,我们是可以在京东上购买机票的。但机票是航司提供的,我们本质上是代理销售而已。那为什么航司要让我们代理销售呢?我们又是如帮他做代理的呢?别急,本文将展开说说他们之间的关系。。。
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
Java动态代理机制解析
动态代理是指在运行时动态生成代理类。不需要我们像静态代理那个去手动写一个个的代理类。生成动态代理类有很多方式:Java动态代理,CGLIB,Javassist,ASM库等。这里主要说一下Java动态代理的实现。Java动态代理InvocationHandler接口Java动态代理中,每一个
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这