我不相信你们都知道Java中本机动态代理和CGLIB动态代理的原理!

云栖息地编号信息:[点击查看更多行业信息]

在这里,您可以找到来自不同行业的第一手云信息。你还在等什么?来吧。

动态代理在中, Java中有着广泛的应用,如Spring AOP、Hibernate数据查询、mock、RPC、Java注释对象获取等。静态代理的代理关系在编译时确定,而动态代理的代理关系在编译时确定。静态代理易于实现,适用于代理类少且明确的情况,而动态代理为我们提供了更大的灵活性。

今天我们将讨论Java 中: JDK本地动态代理和CGLIB动态代理中两种常见的动态代理方法。

JDK原生动态代理

让我们从一个直观的例子开始,假设我们有一个接口Hello和一个简单的实现HelloImp:

//接口

界面你好

字符串问候(字符串);

}

//实现

类HelloImp实现了Hello{

@覆盖

公共字符串问候(字符串){

返回“HelloImp:”字符串;

}

}

这是Java中最常见的场景,它使用接口来制定协议,然后使用不同的实现来实现特定的行为。假设您已经获得了上面的类库,如果我们想记录对sayHello()的调用,我们可以使用静态代理来完成:

//静态代理方法

类StaticProxiedHello实现了Hello{

.

列兵你好你好=新的HelloImp();

@覆盖

公共字符串问候(字符串){

日志信息( '您说: '字符串);

回话问候(str);

}

}

中,的静态代理类StaticProxiedHello作为HelloImp的代理,实现了相同的Hello接口。

您可以使用Java动态代理来做到这一点:

首先实现一个InvocationHandler,方法调用被转发到类的invoke()方法。

然后,当您需要使用Hello时,您可以通过JDK动态代理获得Hello的代理对象。

//Java代理

//1。首先实现一个InvocationHandler,方法调用将被转发到这个类的invoke()方法。

类逻辑定位处理程序实现调用处理程序{

.

列兵你好你好;

公共登录位置处理程序(您好,您好){

this.hello=hello

}

@覆盖

公共对象调用(对象代理、方法方法、对象[]参数)抛出可抛出的{

if('sayHello '。等于(method . GetName()){

logger.info( '您说的是: '数组. toString(参数));

}

return method.invoke(hello,args);

}

}

//2。然后当您需要使用Hello时,通过JDK动态代理获取Hello的代理对象。

你好你好=(你好)代理

Getclass()。getclassloader(),//1。类装入器

新班级?[]{你好,同学们},//2。代理需要实现多个接口

新的逻辑定位处理程序(新的HelloImp());//3。方法调用的实际处理程序

你好,说你好( '我爱你! ');

运行以上代码输出结果:

我不相信你们都知道Java中本机动态代理和CGLIB动态代理的原理!

上述代码的关键是代理。方法,它根据指定的参数动态地创建代理对象。这三个参数的含义如下:

加载器,指定代理对象的类加载器;

接口,代理对象需要实现的接口,可以同时指定多个接口;

处理程序、方法调用的实际处理程序以及代理对象的方法调用都在这里转发(*注1)。

NewProxyInstance()返回一个实现指定接口的代理对象,对该对象的所有方法调用都被转发到InvocationHandler.invoke()方法。理解上面的代码需要对Java反射机制有一定的理解。动态代理的神奇之处在于:

代理对象是在程序运行时生成的,而不是在编译时。

对代理对象的所有接口方法调用都将被转发到InvocationHandler.invoke()方法。在invoke()方法中,我们可以添加任何逻辑,如修改方法参数、添加日志函数、安全检查函数等。之后,我们以某种方式执行真正的方法体。例如,中通过反射调用Hello对象的相应方法,也可以通过RPC调用远程方法。

我不相信你们都知道Java中本机动态代理和CGLIB动态代理的原理!

如果深入挖掘JDK代理之后的对象类型,可以看到以下信息:

#您好代理对象类型信息

class jdkproxy类。$Proxy0

超类=Java . lang . reflect . proxy类

接口:

接口jdkproxy。你好

invocationHandler=jdkproxy。LogInvocationHandler @ a09ee92

代理对象的类型是jdkproxy。$Proxy0,这是一个动态生成的类型,类名采用$Proxy的形式;父类是java.lang.reflect.Proxy,所有JDK动态代理都将继承这个类。同时,实现了Hello接口,这些接口是由我们的接口列表中扩展:Java问题内容聚合指定的

如果您仍然对jdkproxy的实现感兴趣。$Proxy0,看起来像这样:

//JDK代理类实现

公共最终类$Proxy0扩展了代理实现Hello

{

.

public $ Proxy 0(InvocationHandler invocationHandler)

{

super(invocation handler);

}

.

@覆盖

公共最终字符串问候(字符串){

.

返回super.h.invoke(this,m3,新对象[]{ str });//将方法调用转发给invocationhandler

.

}

.

}

这些逻辑并不复杂,但是它们是在运行时动态生成的,不需要手动编写。详情请参考:

我不相信你们都知道Java中本机动态代理和CGLIB动态代理的原理!

Java动态代理为我们提供了非常灵活的代理机制,但是Java动态代理是基于接口的。如果对象没有实现接口,我们应该如何代理?舞台上的CGLIB。

CGLIB动态代理

代码生成库是一个基于内存的字节码生成库,它允许我们在运行时修改和动态生成字节码。CGLIB通过继承实现代理。

例如,假设我们有一个类HelloConcrete,它不实现任何接口:

公共类HelloConcrete {

公共字符串问候(字符串){

返回“HelloConcrete:”字符串;

}

}

因为没有实现接口,这个类不能使用JDK代理,它是通过如下的CGLIB代理实现的:

首先实现一个MethodInterceptor,方法调用将被转发到类的intercept()方法。

然后,当您需要使用HelloConcrete时,通过CGLIB动态代理获取代理对象。

//CGLIB动态代理

//1。首先实现一个MethodInterceptor,方法调用将被转发到类的intercept()方法。

类MyMethodInterceptor实现了MethodInterceptor{

.

@覆盖

公共对象拦截(对象对象、方法方法、对象[]参数、方法代理代理)抛出可抛出的{

logger.info( '您说的是: '数组. toString(参数));

返回proxy.invokeSuper(obj,args);

}

}

//2。然后当你需要使用HelloConcrete时,通过CGLIB动态代理获取代理对象。

增强子增强子=新增强子();

enhancer . SetSuperClass(HelloConCrete . class);

enhancer.setCallback(新的MyMethodInterceptor());

HelloConcrete hello=(HelloConcrete)增强器. create();

你好,说你好( '我爱你! ');

运行以上代码输出结果:

我不相信你们都知道Java中本机动态代理和CGLIB动态代理的原理!

对于上面的代码中,我们使用CGLIB的增强器来指定要代理的目标对象和实际处理代理逻辑的对象。最后,我们通过调用create()方法获得代理对象。对该对象的非最终方法的所有调用都将被转发到MethodInterceptor.intercept()方法。在intercept()方法中,我们可以添加任何逻辑,如修改方法参数、添加日志功能、安全检查功能等。

通过调用MethodProxy.invokeSuper()方法,我们将把调用转发给原始对象,在这种情况下,具体地说,就是HelloConcrete的特定方法。cglig 中方法拦截器与JDK的中InvocationHandler非常相似,后者是中的方法调用中转站。

我不相信你们都知道Java中本机动态代理和CGLIB动态代理的原理!

如果您深入研究CGLIB代理之后的对象类型,您可以看到以下信息:

# HelloConcrete代理对象的类型信息

class=class cglib。HelloConcrete $ $ ENhancerbycglib $ $ e 3734 e 52

超类lh类。HelloConcrete

接口:

接口net.sf.cglib.proxy.Factory

invocationHandler=不是java代理类

我们看到,使用CGLIB代理后的对象类型是CGLIB。hello Concerte $ $ EnhancerbyCGLIB $ $ E 3734 E52,它是由Cglib动态生成的类型;父类是HelloConcrete,这证明了CGLIB是通过继承来实现代理的。同时,实现了CGLIB自己添加的包含一些工具和方法的net.sf.CGLIB.proxy.Factory接口。

请注意,因为它是继承,所以必须考虑最终的问题。我们知道最终类型不能有子类,所以CGLIB不能表示最终类型。在这种情况下,将引发如下异常:

无法子类化最终类cglib。HelloConcrete

同样,最终方法不能重载,所以它们不能被CGLIB代理。在这种情况下,不会抛出异常,但是最终方法将被跳过,并且只代理其他方法。

如果您还对代理类CGLIB的具体实现感兴趣。你好混凝土$ $ ENHANCERBYCGLIB $ $ E3734E52,它看起来像这样:

//CGLIB代理类实现

公共类HelloConcrete $ $ EnhancerByCGLIB $ $ e 3734 e 52

扩展HelloConcrete

工具工厂

{

.

private MethodInterceptor CGLIB $ CALLBACK _ 0;//~~

.

公共最终字符串问候(字符串参数字符串)

{

.

method Interceptor tmp 17 _ 14=CGLIB $ CALLBACK _ 0;

if (tmp17_14!=null) {

//将请求转发给MethodInterceptor.intercept()方法。

返回(字符串)tmp17_14 .截取(此,

CGLIB $说你好$ 0 $方法,

新对象[] {参数字符串},

CGLIB $ sayHello $ 0 $代理);

}

返回super.sayHello(参数字符串);

}

.

}

从上面的代码中我们可以看到,当调用代理对象的sayHello()方法时,我们将首先尝试将其转发给MethodInterceptor.intercept()方法。如果没有MethodInterceptor,我们将执行父类的sayHello()。这些逻辑并不复杂,但是它们是在运行时动态生成的,不需要手动编写。

有关CGLIB的更多信息,请参考:

我不相信你们都知道Java中本机动态代理和CGLIB动态代理的原理!

结语

本文介绍了Java中两种常见的动态代理机制的用法和原理。JDK本地动态代理受Java支持,不需要任何外部依赖,但它只能基于接口进行代理。CGLIB通过继承来委托,不管目标对象是否实现接口,但不能处理最后的情况。

动态代理是面向方面编程的实现。理解动态代理的原理对理解Spring AOP有很大帮助。

[云起在线教室]产品和技术专家每天分享!

课程地址:https://yqh.aliyun.com/live

立即加入社区,与专家面对面,并了解课程的最新进展!

[云栖在线课堂社区]https://c.tb.cn/F3.Z8gvnK

发布者:2020-04-12

作者:木匠李

本文来源于“互联网建筑师微信公众号”。如果你知道相关信息,你可以关注“互联网建筑师”