写点什么

Java 代理模式,一次复习完 4 种动态代理实现方式

  • 2022 年 4 月 25 日
  • 本文字数:3170 字

    阅读完需:约 10 分钟

以上就实现了一个简单的静态代理,很明显,静态代理需要为每个真实主题定义一个形式上完全一样的封装类,


如果真实主题方法有所修改,那代理类也需要跟着修改,不利于系统的维护。

2、动态代理实现

与静态代理相比,动态代理有更多优势,动态代理不仅不需要定义代理类,甚至可以在运行时指定代理类的执行逻辑,


从而大大提升系统的灵活性。


目前动态代理类的生成方法有很多,有 JDK 自带的动态代理、CGLIB、Javassist 和 ASM 库等。


  • JDK 动态代理:内置在 JDK 中,不需要引入第三方 jar,使用简单,但功能比较弱。

  • CGLIB/Javassist:这两个都是高级的字节码生成库,总体性能比 JDK 动态代理好,且功能强大。

  • ASM:低级字节码生成工具,近乎使用 bytecode 编码,对开发人员要求最高。当然性能也是最好(相比前几种也不是很大的提升,这里不做具体介绍)。


以下实例依然以SmsSenderISender作为被代理对象和接口进行试验。


1) JDK 动态代理


JDK 的动态代理需要实现一个处理方法调用的 Handler,用于实现代理方法的内部逻辑,实现InvocationHandler接口。


public class JdkProxyHandler implements InvocationHandler {


private Object target;


public JdkProxyHandler(Object target){


this.target = target;


}


public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


System.out.println("处理前");


Object result = method.invoke(target,args);


System.out.println("处理后");


return result;


}


}


客户端调用:


@Test


public void testJdkProxy(){


ISender sender = (ISender) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),


new Class[]{ISender.class},


new JdkProxyHandler(new SmsSender()));


boolean result = sender.send();


System.out.println("代理对象:" + sender.getClass().getName());


System.out.println("输出结果:" + result);


}


输出结果:


处理前


sending msg


处理后


代理对象:com.sun.proxy.$Proxy4


输出结果:true


这样实现一个简单的 AOP 就完成了,我们看到代理类的类型是com.sun.proxy.$Proxy4。那 JDK 是如何创建代理类?


首先从 Proxy.newProxyInstance 入手,来研究 JDK 是如何生成代理类:


public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)


该方法有 3 个参数:


  • loader:用哪个类加载器去加载代理对象,生成目标对象的代理需要确保其类加载器相同,所以需要将目标对象的类加载器作为参数传递。

  • interfaces:代理类需实现的接口列表,JDK 动态代理技术需要代理类和目标对象都继承自同一接口,所以需要将目标对象的接口作为参数传递。

  • h:调用处理器,调用实现了 InvocationHandler 类的一个回调方法,对目标对象的增强逻辑在这个实现类中。


具体代码如下:


public static Object newProxyInstance(ClassLoader loader,


Class<?>[] interfaces,


InvocationHandler h) throws IllegalArgumentException {


//1.检查


Objects.requireNonNull(h);


final Class<?>[] intfs = interfaces.clone();


final SecurityManager sm = System.getSecurityManager();


if (sm != null) {


checkProxyAccess(Reflection.getCallerClass(), loader, intfs);


}


/*


  • Look up or generate the designated proxy class.


*/


//获取代理类类型


Class<?> cl = getProxyClass0(loader, intfs);


/*


  • Invoke its constructor with the designated invocation handler.


*/


try {


if (sm != null) {


checkNewProxyPermission(Reflection.getCallerClass(), cl);


}


//通过反射创建代理对象


final Constructor<?> cons = cl.getConstructor(constructorParams);


final InvocationHandler ih = h;


if (!Modifier.isPublic(cl.getModifiers())) {


AccessController.doPrivileged(new PrivilegedAction<Void>() {


public Void run() {


cons.setAccessible(true);


return null;


}


});


}


return cons.newInstance(new Object[]{h});


} catch (IllegalAccessException|InstantiationException e) {


throw new InternalError(e.toString(), e);


} catch (InvocationTargetException e) {


Throwable t = e.getCause();


if (t instanceof RuntimeException) {


throw (RuntimeException) t;


} else {


throw new InternalError(t.toString(), t);


}


} catch (NoSuchMethodException e) {


throw new InternalError(e.toString(), e);


}


}


总结:具体代码细节就不在这里深究,但可以明显的看出,JDK 的动态代理底层是通过 Java 反射机制实现的,并且需要目标对象继承自一个接口才能生成它的代理类


2) CGLIB(Code Generation Library)动态代理


使用 CGLIB 动态代理前需要引入依赖:


<dependency>


<groupId>cglib</groupId>


<artifactId>cglib</artifactId>


<version>3.3.0</version>


</dependency>


和 JDK 代理不同,CGLib 动态代理技术不需要目标对象实现自一个接口,只需要实现一个处理代理逻辑的切入类,并实现MethodInterceptor接口。


定义真实主题实现类:


public class BdSender {


public boolean send() {


System.out.println("sending msg");


return true;


}


}


代理类逻辑处理类:


public class CglibProxyInterceptor implements MethodInterceptor {


private Enhancer enhancer = new Enhancer();


/**


  • 获取代理类

  • @param clazz

  • @return


*/


public Object getProxy(Class clazz) {


enhancer.setSuperclass(clazz);


enhancer.setCallback(this);


return enhancer.create();


}


public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {


System.out.println("处理前");


Object result = methodProxy.invokeSuper(object,args);


System.out.println("处理后");


return result;


}


}


客户端调用:


@Test


public void testCglibProxy(){


BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class);


boolean result = sender.send();


System.out.println("代理对象:" + sender.getClass().getName());


System.out.println("输出结果:" + result);


}


输出结果:


处理前


sending msg


处理后


代理对象:org.yd.proxy.BdSender



d65f9e34


输出结果:true


总结 CgLib 的特点:


  • 使用 CGLib 实现动态代理,完全不受代理类必须实现接口的限制

  • CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,比使用 Java 反射效率要高

  • CGLib 不能对声明为 final 的方法进行代理,因为 CGLib 原理是动态生成被代理类的子类


3) Javassist 动态代理


Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库,可以直接编辑和生成 Java 生成的字节码。


相对于 bcel, asm 等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。


使用 avassist 动态代理前需要引入依赖:


<dependency>


<groupId>org.javassist</groupId>


<artifactId>javassist</artifactId>


<version>3.27.0-GA</version>


</dependency>


使用 Javassist 生成动态代理可以有以下两种方式:


  • 代理工厂创建:需要实现MethodHandler用于代理逻辑处理,实现与 CGLib 非常类似

  • 动态代码创建:可通过 Java 代码 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 生成字节码,这种方式创建的动态代理非常灵活,甚至可以在运行时生成业务逻辑


代理工厂创建 — 代理逻辑处理类


public class JavassistProxyHandler implements MethodHandler {


private ProxyFactory proxyFactory = new ProxyFactory();


/**


  • 获取代理对象

  • @param clazz 被代理类

  • @return

  • @throws Exception


*/


public Object getProxy(Class clazz) throws Exception {


proxyFactory.setSuperclass(clazz);


Class<?> factoryClass = proxyFactory.createClass();


Object proxy = factoryClass.newInstance();


((ProxyObject)proxy).setHandler(this);


return proxy;


}

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
Java代理模式,一次复习完4种动态代理实现方式_Java_爱好编程进阶_InfoQ写作社区