前言

由于 Cglib 本身的设计,无法实现在 Proxy 外面再包装一层 Proxy(JDK Proxy 可以),通常会报如下错误:


Caused by: java.lang.ClassFormatError: Duplicate method name "newInstance" with signature ".......... at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ... 10 more

错误来源代码:

net.sf.cglib.proxy.Enhancer#generateClass(ClassVisitor v)

        ......省略代码

        // 以下部分的字节码,每次生成 Proxy 实例都会插入。JVM 验证字节码时则会报错。
if (useFactory || currentData != null) {
int[] keys = getCallbackKeys();
emitNewInstanceCallbacks(e);
emitNewInstanceCallback(e);
emitNewInstanceMultiarg(e, constructorInfo);
emitGetCallback(e, keys);
emitSetCallback(e, keys);
emitGetCallbacks(e);
emitSetCallbacks(e);
}

通过 dump 出来的字节码查看则更为直观:

生成的字节码中,newInstance 方法是重复的。

dump 方法: System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");

如何处理?

实现多重代理,有一种蹩脚的方法,例如 JDK 和 Cglib 组合使用。或者你直接使用 JDK 代理。但有时候,针对类的操作还行不通。

笔者参考 Spring 的做法,实现了一个简单的多重代理。

Spring 的场景是:一个目标方法被多个 AOP 拦截,此时就需要多重代理。

Spring 创建代理的代码位于 :org.springframework.aop.framework.CglibAopProxy#getProxy

Spring AOP 拦截器类:org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor

该类的 intercept 方法是实现多重代理的核心。

每次调用目标方法,都会根据目标方法,和目标方法的多个拦截点生成一个调用对象。

// 生成调用对象
CglibMethodInvocation c = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);
// 调用
c.proceed();

然后调用父类 proceed 方法,其实就是一个过滤器模式:

	public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
} Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Skip this interceptor and invoke the next in the chain. 递归.
return proceed();
}
}
else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

注意最后一行,这里就是调用拦截点的 invoke 方法,这个拦截点的具体实现类:AspectJAroundAdvice。

看下他的 invoke 方法:

	public Object invoke(MethodInvocation mi) throws Throwable {
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
// AOP 里熟悉的 ProceedingJoinPoint 参数!!!!
ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
JoinPointMatch jpm = getJoinPointMatch(pmi);
return invokeAdviceMethod(pjp, jpm, null, null);
}

通常,我们在业务中编写 AOP 拦截代码时,都会接触到这个 ProceedingJoinPoint 参数,然后调用他的 proceed 方法调用目标方法。

这个 ProceedingJoinPoint 类的 proceed 方法最终会回调 DynamicAdvisedInterceptor 对的 proceed 方法。直到所有的拦截点全部执行完毕。最终执行目标类的方法。

所以,你设置的每个被拦截的方法,如果这个方法会被拦截多次,那么就会有多个 MethodInterceptor(不是 cglib 的)实例形成调用链。然后通过 ProceedingJoinPoint 传递给你拦截使用。

铺垫了这么多,我们自己来实现一个简单的,不能像 Spring 这么复杂!!!!

简单实现 Cglib 多重代理

先说一下思路:事实上很简单,只需要再拦截器里放一个过滤器链即可,用户在过滤器里拦截多重调用。这些拦截器,就像你加 @Around 注解的方法,只不过我们这里没有 Spring 那么方便而已。

画个 UML 图:

代码如下:

Test.java & SayHello.java

public class Test {

    public static void main(String[] args) {
Object proxy = ProxyFactory.create().getProxy(new SayHello());
proxy.toString();
} static class SayHello { @Override
public String toString() {
return "hello cglib !";
}
}
}

ProxyFactory.java & Interceptor.java

public class ProxyFactory {
private ProxyFactory() {}
public static ProxyFactory create() {
return new ProxyFactory();
}
public Object getProxy(Object origin) {
final Enhancer en = new Enhancer();
en.setSuperclass(origin.getClass());
List<Chain.Point> list = new ArrayList<>();
list.add(new Point1());
list.add(new Point2());
en.setCallback(new Interceptor(new Chain(list, origin)));
return en.create();
}
private class Interceptor
implements MethodInterceptor {
Chain chain;
public Interceptor(Chain chain) {
this.chain = chain;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
return chain.proceed();
}
}
}

Chain.java & Point.java

public class Chain {
private List<Point> list;
private int index = -1;
private Object target; public Chain(List<Point> list, Object target) {
this.list = list;
this.target = target;
} public Object proceed() {
Object result;
if (++index == list.size()) {
result = (target.toString());
System.err.println("Target Method invoke result : " + result);
} else {
Point point = list.get(index);
result = point.proceed(this);
}
return result;
}
interface Point {
Object proceed(Chain chain);
}
}

Point1.java & Point2.java

public class Point1 implements Chain.Point {

    @Override
public Object proceed(Chain chain) {
System.out.println("point 1 before");
Sleep.sleep(20);
Object result = chain.proceed();
Sleep.sleep(20);
System.out.println("point 1 after");
return result;
}
}
public class Point2 implements Chain.Point { @Override
public Object proceed(Chain chain) {
System.out.println("point 2 before");
Sleep.sleep(20);
Object result = chain.proceed();
Sleep.sleep(20);
System.out.println("point 2 after");
return result;
}
}

运行 Test main 结果:

符合预期。

使用 Cglib 实现多重代理的更多相关文章

  1. cglib 多重 代理示例-2

    from:  http://thinkinjava.cn/2018/10/%E4%BD%BF%E7%94%A8-Cglib-%E5%AE%9E%E7%8E%B0%E5%A4%9A%E9%87%8D%E ...

  2. 详解Java动态代理机制(二)----cglib实现动态代理

    上篇文章的结尾我们介绍了普通的jdk实现动态代理的主要不足在于:它只能代理实现了接口的类,如果一个类没有继承于任何的接口,那么就不能代理该类,原因是我们动态生成的所有代理类都必须继承Proxy这个类, ...

  3. cglib实现动态代理简单使用

    Boss: package proxy.cglib; public class Boss{ public void findPerson() { System.out.println("我要 ...

  4. Spring进阶之路(10)-Advice简单介绍以及通过cglib生成AOP代理对象

    Advice简单介绍 1. Before:在目标方法运行之前运行织入.假设Before的处理中没有进行特殊的处理.那么目标方法终于会运行,可是假设想要阻止目标方法运行时.能够通过抛出一个异常来实现.B ...

  5. 使用CGLIB实现动态代理

    参考:https://blog.csdn.net/yhl_jxy/article/details/80633194#comments CGLIB动态代理 定义:CGLIB(code genaratio ...

  6. JDK 和 CGLib 实现动态代理和区别

    JDK 和 CGLib 实现动态代理和区别 在日常的开发中,Spring AOP 是一个非常常用的功能.谈到 AOP,自然离不开动态代理. 那么,基于 JDK 和 CGLib 如何实现动态代理,他们之 ...

  7. 利用CGLib实现动态代理实现Spring的AOP

    当我们用Proxy 实现Spring的AOP的时候, 我们的代理类必须实现了委托类的接口才能实现. 而如果代理类没有实现委托类的接口怎么办? 那么我们就可以通过CGLib来实现 package cn. ...

  8. jdk与cglib的动态代理

    JDK动态代理中包含一个类和一个接口: InvocationHandler接口: public interface InvocationHandler { public Object invoke(O ...

  9. cgLib生成动态代理

    package com.stono.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import ...

随机推荐

  1. 使用Eclipse的代码追踪功能

    在使用Java编写复杂一些的程序时,你会不会常常对一层层的继承关系和一次次方法的调用感到迷惘呢?幸亏我们有了Eclipse这么好的IDE可以帮我们理清头绪--这就要使用Eclipse强大的代码追踪功能 ...

  2. 【ProtoBuffer】windows上安装ProtoBuffer3.1.0 (附已编译资源)

    ------- 17.9.17更新  --- 以下这些方法都是扯淡,对我的机器不适用,我后来花了最后成功安装并亲测可用的方法不是靠vs编过的,vs生成的库引入后函数全部报undefine refere ...

  3. Kotlin基础语法学习

    Kotline简介 Kotlin是一门与Swift类似的静态类型JVM语言,由JetBrains设计开发并开源.与Java相比,Kotlin的语法更简洁.更具表达性,而且提供了更多的特性,比如,高阶函 ...

  4. 浅谈如何检查Linux中开放端口列表

    给大家分享一篇关于如何检查Linux中的开放端口列表的详细介绍,首先如果你想检查远程Linux系统上的端口是否打开请点击链接浏览.如果你想检查多个远程Linux系统上的端口是否打开请点击链接浏览.如果 ...

  5. some knowledge of language

    1:编译型语言2:解释型语言编译型:编译形成结果,再整体运行解释型:运行产生结果,边解释运行java 特殊(.class)再解释3:脚本语言是解释语言它的优点是方便阅读,不需要写非常多的类型相关的代码 ...

  6. Android中弹出dialog后无法捕捉back键

    一.需求 在Android开发过程中,弹出dialog后无法捕捉back键,点击back按键无响应. 二.解决方案 原因:弹出dialog后,activity失去焦点,dialog获得当前焦点. 解决 ...

  7. Python OR & AND

    These are the Boolean operations, ordered by ascending priority: Operation Result Notes x or y if x ...

  8. java.lang.OutOfMemoryError: PermGen space (jvm内存泄漏解决办法)

    2.在myeclipse根目录 打开myeclipse.ini 3.在myeclipse中配置内存

  9. 【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权

    [.NET Core项目实战-统一认证平台]开篇及目录索引 上篇文章我们介绍了网关使用Redis进行缓存,并介绍了如何进行缓存实现,缓存信息清理接口的使用.本篇我们将介绍如何实现网关自定义客户端授权, ...

  10. 封装一个简易版的ajax操作对象

    /** * 发送ajax请求 * @type {Object} * 使用方法如下: * $ajax.request( * method: "post", //请求方式 * url: ...