Java的三种代理实现

it2024-08-01  39

Java代理

1.静态代理

静态代理在开发时需要定义接口或者父类,被代理对象与代理对象一起去实现相同的接口或者是继承相同父类。即程序运行之前,被代理者和代理者的.class文件就已经生成了。

1.1简单实现

写一个简单例子,班级中需要交班费,班长收齐代理同学老师,这时候班长就是学生的代理。

//创建一个Person接口 public interface Person { /** * @param * @description 收班费 * @author gml * @date 2020/10/20 15:57 * @return * @throws */ void giveMoney(int money); } //Student实现Person接口 public class Student implements Person{ public Student(String name) { this.name = name; } private String name; @Override public void giveMoney(int money) { System.out.println(name + " 交班费"+ money +"元"); } } //StudentLeader实现Person接口,并代理学生 public class StudentLeader implements Person { public List<Student> getStudentList() { return studentList; } public void setStudentList(Student student) { if (null == this.studentList){ this.studentList = new ArrayList<>(); } this.studentList.add(student); } private List<Student> studentList; @Override public void giveMoney(int money) { System.out.println("班长开始收班费---"); studentList.forEach(student -> student.giveMoney(money)); System.out.println("班长收费完毕"); } }

下面开始测试一下,看如何使用

public static void main(String[] args) { //被代理的学生 Student student1 = new Student("张三"); Student student2 = new Student("李四"); //生成代理对象 StudentLeader studentLeader = new StudentLeader(); studentLeader.setStudentList(student1); studentLeader.setStudentList(student2); //班长开始收费 studentLeader.giveMoney(50); }

运行结果

班长开始收班费--- 张三 交班费50元 李四 交班费50元 班长收费完毕

此处没有通过学生上交班费,而是通过班长来代理执行了,这就是一种简单的代理模式。

1.2说明

代理模式最主要就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentLeader),代理类中含有具体类的实例,代为执行具体类实例的方法。

代理模式就是在执行实际对象时加入一个中间层(代理),因为这个中间层(代理),可以附加很多用途,这里的中间层指的是不直接调用实际对象的方法,我们就可以在代理过程中加上一些附加用途,比如上次交的班费没人还有剩余10块钱,这次班长就可以让每个人少交10块钱,通过代理模式就很轻松。

public class StudentLeader implements Person { public List<Student> getStudentList() { return studentList; } public void setStudentList(Student student) { if (null == this.studentList){ this.studentList = new ArrayList<>(); } this.studentList.add(student); } private List<Student> studentList; @Override public void giveMoney(int money) { System.out.println("班长开始收班费---"); System.out.println("上次班费还有剩余10块钱"); studentList.forEach(student -> student.giveMoney(money - 10)); System.out.println("班长收费完毕"); } }

执行结果

班长开始收班费--- 上次班费还有剩余10块钱 张三 交班费40元 李四 交班费40元 班长收费完毕

1.3优缺点

优点

代理模式能协调调用者和被调用者,在一定程度上降低了系统的耦合度。代理对象可以在客户端和目标对象之间起到中介的作用的目的

缺点

客户端和目标对象之间增加了代理对象,可能会造成请求的处理速度变慢。实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

2.动态代理

静态模式虽然很好用,但是静态代理还是存在一定的局限性,比如静态代理需要开发者手写很多代码,这个过程比较浪费时间和精力。一旦需要代理类的方法比较多,或者需要同时代理多个对象的时侯,这就会增加复杂度。

代理类在程序运行时创建代理的方式被称为动态代理,上面静态代理的例子中,代理类(StudentLeader)是自己事先定义好的,在程序运行之前就已经编译完成;然而动态代理并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法

2.1简单实现

在JDK的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口就可以生成JDK动态代理类和动态代理对象。

//创建一个接口 public interface IHello { void sysHello(); } //创建一个实现类 public class Hello implements IHello { @Override public void sysHello() { System.out.println("hello dynaic"); } } //创建一个代理类 public class DynaicTest implements InvocationHandler { Object originalObj; Object bind(Class clazz) throws IllegalAccessException, InstantiationException { this.originalObj = clazz.newInstance(); return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this::invoke); } /** * * @param proxy 是代理的对象 * @param method 是IHello的sayHello接口 * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始调用"); //此处不要使用"proxy"来作为方法执行的参数,容易出现死循环现象,此处应传入被代理类的实例 return method.invoke(originalObj, args); } }

下面测试一下,看一下怎么使用

public static void main(String[] args) throws InstantiationException, IllegalAccessException { IHello hello = (IHello) new DynaicTest().bind(Hello.class); hello.sysHello(); }

执行结果

开始调用 hello dynaic

2.1原理分析

动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了,上述代码里,唯一的黑匣子就是Proxy.neyProxyInstance()方法,除此以外在没有任何特殊之处。

在JDK动态代理中涉及以下几种角色:

业务接口Interface、业务实现类target、业务处理类Handler(实现InvocationHandler接口)、JVM在内存中生成的动态代理类$Proxy(N)(这个N是从0开始的数字)

说明:

Proxy通过传递给它的参数(interfaces/invocationHandler)生成代理类$Proxy0;Proxy通过传递给它的参数(ClassLoader)来加载生成的代理类$Proxy的字节码文件。

下面我们看一下代理类的代码是什么样的,生成字节码文件代码如下

public class ProxyGeneratorUtils { public static void writeProxyClassToDisk(String path){ byte[] generateProxyClass = ProxyGenerator.generateProxyClass("$Proxy11", Hello.class.getInterfaces()); FileOutputStream out = null; try { out = new FileOutputStream(path); out.write(generateProxyClass); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { ProxyGeneratorUtils.writeProxyClassToDisk("D:\\$Proxy11.class"); } }

将class文件反编译后呈现的结果如下

public final class $Proxy11 extends Proxy implements IHello { private static Method m1; private static Method m2; private static Method m3; private static Method m0; /** *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白 *super(paramInvocationHandler),是调用父类Proxy的构造方法。 *父类持有:protected InvocationHandler h; *Proxy构造方法: * protected Proxy(InvocationHandler h) { * Objects.requireNonNull(h); * this.h = h; * } * */ public $Proxy11(InvocationHandler var1) { super(var1); } public final boolean equals(Object var1) { 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() { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } /** * DynaicTest.invoke(Object proxy, Method method, Object[] args) 方法会代理被代理对象的所有方法,因此有可能造成死循环 * 由此可知死循环调用路径为:DynaicTest.main内DynaicTest.sayHello() * (Hello即动态生成的$Proxy11类实例)-->$Proxy11.invoke方法内this.h.invoke(this, m3, new Object[] { paramInvocation }) * -->DynaicTest.invoke方法内paramMethod.invoke(paramObject, paramArrayOfObject) * -->$Proxy11.invoke方法内this.h.invoke(this, m3, new Object[] { paramInvocation }) * -->DynaicTest.invoke方法内paramMethod.invoke(paramObject, paramArrayOfObject) -->.... * * 解决:传入被代理类的实例,避免死循环 */ public final void sysHello() { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() { try { return (Integer)super.h.invoke(this, m0, (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"); m3 = Class.forName("com.gml.test.proxy.dynamic.IHello").getMethod("sysHello"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }

父类Proxy源码大略

public class Proxy implements java.io.Serializable { private static final Class<?>[] constructorParams = { InvocationHandler.class }; private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); protected InvocationHandler h; private Proxy() { } protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } //略 }

代理类的代码也很简单,它把接口中的每个方法和Object中集成来的equals()、hashCode()、toString()方法都生成了对应的实现,并且统一通过InvocationHandler实现类的invoke()方法(this.h就是指父类Proxy中成员变量InvocationHandler实例变量)来实现这些方法的内容,每个方法的区别就是传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪个方法,实际都是在执行InvocationHandler.invoke()中的方法。

2.3优缺点

优点

动态代理可以很方便的对代理类的函数进行统一的处理,不用修改每个代理类的方法

缺点

由生成代理类的源码可见,动态代理类都继承了 Proxy 类,实现了代理的Interface,而且Java的类是单继承的,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理

3.cglib代理

CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate同样使用CGLIB来代理多对一和一对一关联。

CGLIB底层使用ASM来操作字节码生成新的类,处理CGLIB外,脚本语言——Groovy和BeanShell同样使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。不推荐直接使用ASM,它需要对Java字节码的格式足够了解。

3.1简单实现

public class CglibTest { public String test(){ System.out.println("hello cglib"); return "haha"; } }

测试一下看一下如何使用

public static void main(final String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(CglibTest.class); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before method run---"); Object result = methodProxy.invokeSuper(obj, objects); System.out.println("after method run ---"); return result; } }); CglibTest cglibTest = (CglibTest) enhancer.create(); cglibTest.test(); }

运行结果

before method run--- hello cglib after method run ---

Enhancer是CGLIB最常用的一个类,和JDK动态代理的Proxy差不多。

最新回复(0)