静态代理与JDK动态代理与CGLIB动态代理(上)

简介: 静态代理与JDK动态代理与CGLIB动态代理(上)

为了理解spring的AOP。 理解JDK动态代理与CGLB动态代理,非常重要。

讲动态代理必讲静态代理。

静态代理与动态代理是一种设计思想


静态代理


静态代理是在编译期将扩展代码织入代理对象

实现方式

1.代理模式:可以理解为硬编码模式。就是代理类里持有被代理对象的引用。 通过调用代理类的实例。达到间接调用被代理对象的目的。

public interface UserInterface {
    void doSomething();
}
public class User implements UserInterface {
    @Override
    public void doSomething() {
        System.out.println("I'm doing something");
    }
}
public class UserProxy implements UserInterface {
    private UserInterface userInterface = null; // 持有被代理对象的引用
    @Override
    public void doSomething() {
        beforeDoSomething();
        if(userInterface  == null){
            userInterface =  new User();
        }
        userInterface .doSomething();
        afterDoSomething();
    }
    private void beforeDoSomething() {
        System.out.println("before doing something");
    }
    private void afterDoSomething() {
        System.out.println("after doing something");
    }
}

2.AspectJ编译织入AspectJ是一种面前切面的技术,通过在编译期把AspectJ内容编译到字节码文件.class (这里不做过多解析推荐文章关于 Spring AOP (AspectJ) 你该知晓的一切

静态代理小结:

  • 静态代理不产生新的Class
  • 静态代理在编译期织入代理对象
  • 静态代理不灵活。


动态代理:


我们熟知的动态代理:JDK动态代理和CGLB动态代理 他们都有一个共同点, 在内存中生成了新的字节码,这点很重要。这节我们从生成的新字节码的角度来看看动态代理的两种方式

因为新生成的字节码在内存中,为了看到这些字节码我们需要其以文件的形式展现出来。我们在main方法里加下面两行代码就可以把内存的字节码拿出来了

//输出JDK动态代理
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
//输出CGLIB动态代理产生的类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglbproxy");
复制代码

我们还是以User类为例看看JDK动态代理生成的字节码文件与CGLB生成的字码文件详情代码参考springlean项目

1.JDK动态代理:

//------------------------------------接口
interface UserInterface {
    void doSomething();
}
//------------------------------------被代理类
class User implements UserInterface {
    @Override
    public void doSomething() {
        System.out.println("我是实现了接口的User");
    }
}
//------------------------------------代理类(也是拦截器)
class JDKProxy implements InvocationHandler {
    // 要被代理的目标对象
    private UserInterface target;
    public JDKProxy(UserInterface target){
        this.target=target;
    }
    public UserInterface createProxy(){
        return (UserInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK前置拦截");
        return method.invoke(target,args);
    }
}
//------------------------------------内存生成的新代理类。
final class $Proxy0 extends Proxy implements UserInterface {
    private static Method m3;
    //省略其他代码
    .....
    public final void doSomething() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    static {
        try {
            m3 = Class.forName("cn.wqd.UserInterface").getMethod("doSomething", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
    ....
}
//------------------------------------调用。
public static void main(String[] args) {
    User a=new User();
        //创建JDK代理
        JDKProxy jdkProxy=new JDKProxy(a);
        //创建代理对象
        UserInterface proxy=jdkProxy.createProxy();
        System.out.println(proxy.getClass().getName());
        //执行代理对象方法
        proxy.doSomething();
}

System.out.println(proxy.getClass().getName()); 输出的是cn.wqd.$Proxy0。 我们没有定义过cn.wqd.$Proxy0类,所以 cn.wqd.$Proxy0就是新生成的代理类。

对比我们User类与$Proxy0类总结下新的代理类的特征

  • 新生成的代理类中定义了与被代理对象相同方法名的方法,如doSomething()方法
  • 新生成的代理类实现了与被代理类相同接口 :UserInterface
  • 新生成的代理类继承了Proxy
  • 新生成的代理类是final类型

当我们调用proxy.doSomething()的时候,指的是cn.wqd.$Proxy0.doSomething() ,其内部调用时父类Proxy的.InvocationHandler的invoke()方法,传入的参数(1)代理类对象 ,(2)目标对象的方法。

InvocationHandler属性参数其实就是我们定义的JDKProxy,其invoke方法最终调用目标对象方法 method.invoke(target,args)。以此达到代理的目的。

小结一下:

  • JDK动态代理通过反射达到代理的目的
  • JDK动态代理会在内存生成一个继承了Proxy,实现了同被代理类实现的接口相同的接口的 代理类。
  • JDK动态代理执行链:代理类方法-->InvocationHandler.invoke()-->目标方法


2.CGLB动态代理:

//====================user
class UserNoInterface{
    public void doSomething() {
        System.out.println("我是没有实现接口的User");
    }
}
//====================拦截器
class CglibProxyIntercepter implements MethodInterceptor {
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("执行前...");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("执行后...");
        return object;
    }
}
//------------------------------------调用。
public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserNoInterface.class);
        enhancer.setCallback(new CglibProxyIntercepter());
        UserNoInterface proxyCGLB= (UserNoInterface)  enhancer.create();
        System.out.println(proxyCGLB.getClass().getName());
        proxyCGLB.doSomething();
}

CGLB动态代理不要求被代理类实现一个接口,就可以进行代理。 CGLB生成三个新的字码码文件,是不是很诡异。


System.out.println(proxyCGLB.getClass().getName()); 打印cn.wqd.UserNoInterface?EnhancerByCGLIB?b3361405看来三个文件中。cn.wqd.UserNoInterface?EnhancerByCGLIB?b3361405才是对应生成的代理类。 我们分析下代理类代码。(为了便于理解,我只贴出有助于理解的代码具体查看详情代码参考springlean项目

public class UserNoInterface?EnhancerByCGLIB?b3361405 extends UserNoInterface implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$doSomething$0$Method;
    private static final MethodProxy CGLIB$doSomething$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("cn.wqd.UserNoInterface?EnhancerByCGLIB?b3361405");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$doSomething$0$Method = ReflectUtils.findMethods(new String[]{"doSomething", "()V"}, (var1 = Class.forName("cn.wqd.UserNoInterface")).getDeclaredMethods())[0];
        CGLIB$doSomething$0$Proxy = MethodProxy.create(var1, var0, "()V", "doSomething", "CGLIB$doSomething$0");
    }
    final void CGLIB$doSomething$0() {
        super.doSomething();
    }
    public final void doSomething() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if(var10000 != null) {
            var10000.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy);
        } else {
            super.doSomething();
        }
    }
  .....
}


总结下特征

  • 代理类继承了被代理类,
  • 代理类为每个目标类的方法都生成了两个方法,以doSomething()为例,一个是重写方法doSomething(),另一个是CGLIB$doSomething$0()CGLIB$doSomething$0()方法内部调用的是super.doSomething()也就是目标方法的doSomething()方法
  • 代理类会获得所有在父类继承来的方法,并且会创建一个MethodProxy类型属性与之对应。以doSomething()例子,CGLIB$doSomething$0$Method获取的父类方法,CGLIB$doSomething$0$Proxy与之对应的MethodProxy
CGLIB$doSomething$0$Method = ReflectUtils.findMethods(new String[]{"doSomething", "()V"}, (var1 = Class.forName("cn.wqd.UserNoInterface")).getDeclaredMethods())[0];
CGLIB$doSomething$0$Proxy = MethodProxy.create(var1, var0, "()V", "doSomething", "CGLIB$doSomething$0");

因为拿到的proxyCGLB是代理类对象实例,所以proxyCGLB.doSomething()调用的就是

public final void doSomething() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if(var10000 != null) {
            var10000.intercept(this, CGLIB$doSomething$0$Method, CGLIB$emptyArgs, CGLIB$doSomething$0$Proxy);
        } else {
            super.doSomething();
        }
    }


我们看看这段逻辑

首先,检查(MethodInterceptor)CGLIB$CALLBACK_0是否为null,看到MethodInterceptor我们应该明白了这个地方的CGLIB$CALLBACK_0应该就是上文定义的CglibProxyIntercepter 。

不为null。则执行CglibProxyIntercepter.intercept()方法,入参分别为:

  • this:代理类对象,
  • CGLIB$doSomething$0$Method目标对象方法
  • CGLIB$emptyArgs方法入参
  • CGLIB$doSomething$0$Proxy代理方法。

CglibProxyIntercepter.intercept()方法内部通过调用MethodProxy.invokeSuper(sub, objects)。


相关文章
|
22天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
17 0
[Java]静态代理与动态代理(基于JDK1.8)
|
1月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
20 1
|
3月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
214 0
|
2月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
321 3
|
3月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
57 1
|
3月前
|
Oracle Java 关系型数据库
Mac安装JDK1.8
Mac安装JDK1.8
694 4
|
4月前
|
Java Linux
Linux复制安装 jdk 环境
Linux复制安装 jdk 环境
108 3
|
4天前
|
Ubuntu Java
Ubuntu之jdk安装
以下是Ubuntu之jdk安装的详细内容
12 0
|
1月前
|
Oracle Java 关系型数据库
jdk17安装全方位手把手安装教程 / 已有jdk8了,安装JDK17后如何配置环境变量 / 多个不同版本的JDK,如何配置环境变量?
本文提供了详细的JDK 17安装教程,包括下载、安装、配置环境变量的步骤,并解释了在已有其他版本JDK的情况下如何管理多个JDK环境。
676 0
|
3月前
|
Java 开发工具
开发工具系列 之 同一个电脑上安装多个版本的JDK
这篇文章介绍了如何在一台电脑上安装和配置多个版本的JDK,包括从官网下载所需JDK、安装过程、配置环境变量以及如何查看和切换当前使用的JDK版本,并提到了如果IDEA和JDK版本不兼容时的解决方法。
开发工具系列 之 同一个电脑上安装多个版本的JDK