前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法) 因为在 InvocationHandlerinvoke方法中,通过反射,可以直接获取正在调用方法对应的 Method 对象,具体应用的话,比如可以添加调用日志,做事务控制等。

一、动态代理实现

1、执行代理:

public interface ProxyInvoker {

    /**
     * 通过给定的代理对象和方法调用来执行方法。
     */
    Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws 
      InvocationTargetException, IllegalAccessException;
}


@Getter
public abstract class AbstractProxyInvoker implements ProxyInvoker {

    /**
     * object
     */
    private final Object object;

    /**
     * JdkDynamicProxyHandler
     *
     * @param object object
     */
    public AbstractProxyInvoker(final Object object) {
        this.object = object;
    }

}


public class DefaultProxyInvoker extends AbstractProxyInvoker implements ProxyInvoker {


    /**
     * JdkDynamicProxyHandler
     *
     * @param object object
     */
    public DefaultProxyInvoker(Object object) {
        super(object);
    }

    /**
     * 可以在这里做切面
     *
     * @param proxy  proxy
     * @param method method
     * @param args   args
     * @return {@link Object}
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
      															throws InvocationTargetException, IllegalAccessException {
        return method.invoke(getObject(), args);
    }

}


2、被代理方法

public interface BuyHouse {

    void buyHosue();

    int countAdd(int i);
}


public class BuyHouseImpl implements BuyHouse {

    @Override
    public void buyHosue() {
        System.out.println("我要买房");
    }

    @Override
    public int countAdd(int i) {
        return i++;
    }

}

1、JDK 动态代理

只能代理实现了接口的类:这是 JDK 动态代理的一个主要限制。它基于 Java 的反射机制,通过实现一个或多个接口来创建代理实例。这意味着,如果你想要对一个类使用 JDK 动态代理,那么这个类必须至少实现了一个接口。

使用场景:当你需要为实现了接口的类添加一些额外的行为(如日志记录、安全检查等),而不想修改原有类代码时,JDK 动态代理是一个很好的选择。

实现方式JDK 动态代理通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。Proxy类提供了创建代理实例的静态方法,而InvocationHandler接口需要被实现以定义当代理实例的方法被调用时应该执行的操作。

1)代码实现

a、 JdkDynamicProxy

public class JdkDynamicProxy implements IDynamicProxy {

    /**
     * acquireProxy
     *
     * @param interfaceClass interfaceClass
     * @param proxyInvoker invoker
     * @return {@link T}
     */
    @Override
    public <T> T acquireProxy(Class<T> interfaceClass, ProxyInvoker proxyInvoker) {
        ClassLoader classLoader = interfaceClass.getClassLoader();
        InvocationHandler invocationHandler = proxyInvoker::invoke;
        @SuppressWarnings("unchecked")
        T result = (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, invocationHandler);
        return result;
    }

}

b、JdkAspectProxyInvoker

@Slf4j
public class JdkAspectProxyInvoker extends AbstractProxyInvoker {

    /**
     * JdkAspectProxyInvoker
     *
     * @param object object
     */
    public JdkAspectProxyInvoker(Object object) {
        super(object);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
      throws InvocationTargetException, IllegalAccessException {
        log.info("JdkAspectProxyInvoker#invoke before");
        Object result = method.invoke(this.getObject(), args);
        log.info("JdkAspectProxyInvoker#invoke end");
        return result;
    }
}

2、目标接口实现类

public class BuyHouseImpl implements BuyHouse {

    @Override
    public void buyHosue() {
        System.out.println("我要买房");
    }
}

2)验证

⬤ 第一个参数 指定当前目标对象使用的类加载器,获取加载器的

⬤ 第二个参数 指定目标对象实现的接口的类型,使用泛型方式确认类型, 这里的参数就是接口列表

⬤ 第三个参数 指定动态处理器

@Test
public void jdk() throws Exception {
    JdkAspectProxyInvoker dynamicInvoker = new JdkAspectProxyInvoker(new BuyHouseImpl());
    JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy();
    BuyHouse buyHouse = jdkDynamicProxy.acquireProxy(BuyHouse.class, dynamicInvoker);
    buyHouse.buyHosue();


    // byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", buyHouse.getClass().getInterfaces());
    // String code = IOUtils.toString(bytes, "utf-8");
    // System.out.println(code);
    // FileOutputStream fileOutputStream = new FileOutputStream("/Users/healerjean/Desktop/logs/porxy.class");
    // IOUtils.write(bytes, fileOutputStream);
}

3)生成的代理类

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements BuyHouse {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int countAdd(int var1) throws  {
        try {
            return (Integer)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void buyHosue() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse").getMethod("countAdd", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse").getMethod("buyHosue");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4)总结

问题1:jdk动态代理 是什么原理?

答案:jdk动态代理是由 java 内部的反射机制来实现的 ,使用动态代理的对象必须实现一个或多个接口。

问题2:jdk 动态代理只能对接口进行代理,那么为什么呢?

答案:Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理。动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的 class 文件,并加载 class 文件运行的过程,通过反编译被生成的 $Proxy0.class文件发现:

class类定义为:

public final class $Proxy0 extends Proxy implements Interface {

	public $Proxy0(InvocationHandler paramInvocationHandler) { 
		super(paramInvocationHandler); 
}

$Proxy0 继承了需要被代理的类,由于 java 的单继承动态生成的代理类已经继承了 Proxy 类的,就不能再继承其他的类所以只能靠实现被代理类的接口的形式,故 JDK 的动态代理必须有接口

2、gclib 动态代理

针对类实现代理:与 JDK 动态代理不同,CGLIBCode Generation Library)可以代理没有实现接口的类。它通过继承目标类来创建代理对象,并覆盖其中的方法。

使用场景:当你需要代理的类没有实现接口,或者你想要在代理中访问目标类的私有成员时,CGLIB 是一个很好的选择。

实现方式CGLIB 在运行时动态生成目标类的子类,并在子类中覆盖目标类的方法。当调用代理对象的方法时,实际上会调用到CGLIB 生成的子类中的方法,这些方法会进一步调用 MethodInterceptor 接口(或类似的回调接口)的实现,从而允许你在方法调用前后添加自定义的行为。

1)代码实现

1、CglibDynamicProxy

public class CglibDynamicProxy implements IDynamicProxy {


    @Override
    public <T> T acquireProxy(Class<T> interfaceClass, ProxyInvoker proxyInvoker) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(interfaceClass);
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> 
                             proxyInvoker.invoke(o, method, objects));
        @SuppressWarnings("unchecked")
        T proxy = (T) enhancer.create();
        return proxy;
    }

}

2)验证

@Test
public void cglib() throws Exception {

    DefaultProxyInvoker dynamicInvoker = new DefaultProxyInvoker(new BuyHouseImpl());

    CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy();
    BuyHouse buyHouse = cglibDynamicProxy.acquireProxy(BuyHouse.class, dynamicInvoker);
    buyHouse.buyHosue();
}

3)总结

CGLib 是一个强大、高性能的 Code 生产类库,可以实现运行期动态扩展 java 类,Spring在运行期间通过 CGlib 继承要被动态代理的类,重写父类(被代理的类)的方法,实现 AOP 面向切面编程。

JDK 的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。 Cglib 不能对 final 类以及 final 方法进行代理。因为他是继承被代理类的子类,如果被代理类使用了 final 就不能被继承

3、Javassist

Javassist 在性能上与其他两种技术相比没有绝对的显著优势或劣势。其性能表现主要取决于具体的使用方式和场景。在需要进行复杂字节码操作或动态修改类定义的场景中,Javassist 可能会表现出更好的灵活性和适应性。然而,在选择使用哪种技术时,还需要综合考虑其他因素(如易用性、维护成本等)来做出决策

适用于需要更复杂的字节码操作或动态修改类定义的情况

⬤ 当 JDK 动态代理和 CGLib无法满足需求时,可以考虑使用 Javassist

1)代码实现

public class JavassistDynamicProxy implements IDynamicProxy {

    /**
     * acquireProxy
     *
     * @param interfaceClass interfaceClass
     * @param proxyInvoker invoker
     * @return {@link T}
     */
    @Override
    public <T> T acquireProxy(Class<T> interfaceClass, ProxyInvoker proxyInvoker) {
        ProxyFactory factory = new ProxyFactory();
        factory.setInterfaces(new Class[]{interfaceClass});
        MethodHandler methodHandler = (self, thisMethod, proceed, args) -> 
          proxyInvoker.invoke(self, thisMethod, args);
        try {
            @SuppressWarnings("unchecked")
            T proxy = (T) factory.create(new Class[0], new Object[0], methodHandler);
            return proxy;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2)验证

@Test
public void javassist() throws Exception {
  DefaultProxyInvoker dynamicInvoker = new DefaultProxyInvoker(new BuyHouseImpl());

  JavassistDynamicProxy cglibDynamicProxy = new JavassistDynamicProxy();
  BuyHouse buyHouse = cglibDynamicProxy.acquireProxy(BuyHouse.class, dynamicInvoker);
  buyHouse.buyHosue();
}

3)总结

1、代理接口实现类

Javassist 用于代理实现了接口的类时,其性能可能与 JDK 动态代理相近。JDK 动态代理本身就是为了高效地代理接口而设计的,它通过动态生成代理类并利用Java反射机制来实现对接口方法的拦截。Javassist 虽然提供了更底层的字节码操作能力,但在仅需要代理接口的场景下,其优势并不明显。

2、代理未实现接口的类

对于未实现接口的类,JDK 动态代理无法直接使用,而 CGLibJavassist 都可以通过不同的方式来实现代理。CGLib 通过继承目标类来生成代理类,这种方式在某些情况下可能会引入额外的性能开销,因为代理类需要继承目标类的所有方法和属性。而 Javassist则可以通过直接修改目标类的字节码来实现代理,这种方式在某些场景下可能会更加高效,但也可能因为字节码操作的复杂性而引入性能瓶颈。

3、复杂字节码操作

Javassist 的强大之处在于它提供了丰富的 API 来直接操作 Java 字节码。这使得 Javassist 能够在运行时动态地修改类的定义、添加或删除方法、修改方法体等。当需要进行复杂的字节码操作时,Javassist 可能会比其他两种技术更加灵活和强大。然而,这种灵活性也可能导致性能上的不确定性,因为字节码操作的复杂性和效率往往难以预测。

4、实际应用场景

在实际应用中,Javassist 的性能表现往往取决于具体的使用场景。例如,在 AOP (面向切面编程)场景中,Javassist 可以通过在运行时动态地向类中添加或修改方法来实现横切关注点的注入(如日志记录、性能监控等)。这种方式可能会比传统的静态 AOP 实现更加灵活和高效,因为它不需要在编译时生成额外的代理类或字节码

二、动态代理总结

1、jdk 代理和 CGLIB 代理的区别

1)JDKCGLib动 态代理性能对比-教科书上的描述

1、CGLib 所创建的动态代理对象在实际运行时候的性能要比 ` JDK` 动态代理高不少,有研究表明,大概要高10倍;

2、但是 CGLib 在创建对象的时候所花费的时间却比 JDK 动态代理要多很多,有研究表明,大概有8倍的差距;

3、因此,对于 singleton 的代理对象或者具有实例池(单例)的代理,因为无需频繁的创建代理对象,所以比较适合采用 CGLib 动态代理,反之,则比较适用 JDK 动态代理。

2)适用场景不一样

在 1.6 和 1.7 的时候,JDK 动态代理的速度要比 CGLib 动态代理的速度要慢,但是并没有教科书上的 10 倍差距,在 JDK1.8 的时候,JDK 动态代理的速度已经比 CGLib 动态代理的速度快很多了,但是 JDK动态代理和 CGLib动态代理的适用场景还是不一样的哈!

1、JDK 动态代理适用于实现了接口的类,通过接口来定义代理的行为。

2、CGLIB代理适用于没有实现接口的类,通过继承目标类来创建代理对象,并覆盖其中的方法。

2、Spring在选择用 JDK还是 CGLiB 的依据

1)目标对象是否实现了接口(@aspect 会自动识别)

  • JDK 动态代理:当 Spring 配置的目标对象实现了至少一个接口时,Spring 默认使用 JDK 动态代理来创建代理。这是因为 JDK 动态代理是基于接口实现的,它利用了 Java 的反射机制在运行时动态地创建代理类。使用 JDK 动态代理,Spring 可以方便地为目标对象的方法调用添加前置、后置、环绕等增强处理。
  • CGLIB 代理:当目标对象没有实现任何接口时,Spring 将使用 CGLIB 来创建代理。CGLIB 是一个强大的高性能代码生成库,它通过在运行时动态创建目标类的子类来实现代理。与 JDK 动态代理相比,CGLIB 可以代理没有实现接口的类,并且可以覆盖父类的方法。

2) 性能和资源消耗

  • JDK 动态代理:由于 JDK 动态代理是基于 Java 反射机制实现的,其性能损耗相对较小,生成的代理类也较为轻量级,不需要生成额外的字节码文件,而是在运行时通过动态生成字节码的方式完成代理逻辑。这样可以减少类加载的时间和占用的内存空间。
  • CGLIB 代理CGLIB在创建代理对象时需要生成更多的字节码,因此在性能上可能会比 JDK 动态代理稍差。但是,CGLIB 提供的代理能力更为强大,可以代理没有实现接口的类,并且可以覆盖父类的方法。

3) 显式配置

  • 尽管 Spring默认根据目标对象是否实现了接口来选择代理方式,但开发者也可以通过显式配置来指定使用哪种代理方式。例如,在Spring的配置文件中,可在 Java 配置中,则可以通过@EnableAspectJAutoProxy(proxyTargetClass = true)来实现相同的效果。

ContactAuthor