Java高级特性反射与动态代理模式

    科技2022-07-13  135

    文章目录

    前言一、了解反射二、继续了解反射(哈哈哈)1. 每一个类对应的class放在哪里?2. 这个class里面都保存了什么3. 如何使用?3.1 获取类加载器3.2 获取构造器对象3.3 获取方法并执行相对应的方法3.4 通过反射访问成员变量 三、动态代理模式1.静态代理模式2. 动态代理模式3.Proxy是怎么帮助我们生成的代理类的呢? 总结


    前言

    动态代理模式,也是Java中常见的一种设计模式,其中动态代理模式,也是用到了反射,所以我们反射和动态代理一起学一学

    一、了解反射

    反射看名字很高大上哈,其实搞明白了也很简单,反射之中包括了一个反字,所以了解反射我们先从正开始,正常情况下呢,我们调用一个类来做方法的时候,是从main方法里声明一个,然后再实例化这个类,然后再调用里面的方法

    public class RefleDemo { public static void main(String[] args) { //实例化对象的标准用法,也就是所谓的正 Servant servant=new Servant(); servant.service("Hello"); } }

    反射则是一开始并不知道我要初始化对象它是什么类,自然也就无法使用new 关键字来创建对象了,这时候,我们使用JDK提供的反射API进行反射调用,这时候小伙伴们可能就有疑问,什么情况下,我们不知道我们要初始化的类是什么呢? 各种框架,比如说我们Android中使用了动态代理的网络通讯的框架Retrofit

    Class servantRefle=Class.forName("rfle.Servant"); Servant servantClass= (Servant) servantRefle.newInstance(); servantClass.service("hello");

    反射就是在运行的时候才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法 反射是Java被视为动态语言的关键,根据反射我们可以根据类信息,操作类中的所有方法和属性

    二、继续了解反射(哈哈哈)

    1. 每一个类对应的class放在哪里?

    大家都知道,Java语音是一个面向对象的语言,在面向对象的世界里,万事万物皆对象,那我们写的class类是不是对象呢,是的,他们都是 java.lang.Class 的对象 我们写的每一个类,都是这个Class它的一个对象,我们在写类的时候,并没有显式的写这个对象,那么它放在哪里了呢,我们都知道,我们运行的时候,我们写的类会编译成一个类,生成一个class文件,而编译器就把Class的这个对象存放在class文件的末尾,里面保存了类的元数据信息,这些元数据信息都包括什么呢? ``

    2. 这个class里面都保存了什么

    保存了类的所有信息,比如它是类还是接口 集成 和实现了那些类和接口,有什么属性 ,有什么方法 我们在new一个对象的时候,可以new 很多对象,但是这个类生成的class对象只能有一个(在不同的类加载器,可能有多个,这里涉及到虚拟机的知识了,emmm…) 我们在实例化Servant这个的类对象的时候,虚拟机会去检查,在虚拟机里面,这个类有没有被加载过,如果没有,虚拟机会先加载Servant对应的这个class对象,加载完之后,才会轮到Servant实例化本身的对象

    3. 如何使用?

    我们怎么获取这个Class 对象呢? 我们有好几种方法

    通过类名获取 Class servantClass1=Servant.class; 通过实例来获取 Servant servant=new Servant(); servant.service("Hello"); Class servantClass2=servant.getClass(); 通过这个类的全限定名称 (包名+这个类的类名)Class.forName来获取 Class servantRefle=Class.forName("rfle.Servant");

    前面两种获取Class 的方式,都得需要确切的知道Servant是存在的有的,对于各种框架来说,它们常用的是第三种方式来获取Class 对象 获取了这个Class 对象,我们就可以做很多很多事情,能做什么呢 比如获取 Servant的实例,

    Class servantRefle=Class.forName("rfle.Servant"); Servant servantClass= (Servant) servantRefle.newInstance(); servantClass.service("hello");

    但是单单获取实例远远不够滴,

    3.1 获取类加载器

    package rfle; public class TestClassLoader { public static void testClassLoader() throws ClassNotFoundException { //1、获取一个系统的类加载器(可以获取,当前这个类就是它加载的) ClassLoader classLoader=ClassLoader.getSystemClassLoader(); System.out.println(classLoader); //2丶获取系统类加载器的父类加载器(扩展加载器,可以获取) classLoader=classLoader.getParent(); System.out.println(classLoader); //3丶获取扩展类加载器(引导类加载器,不可获取) classLoader=classLoader.getParent(); System.out.println(classLoader); //4、测试当前类是那个类加载器加载的(系统加载器) classLoader=Class.forName("rfle.RelectionTest").getClassLoader(); System.out.println(classLoader); //5、测试JDK提供的Object类 由那个类加载器加载的(引导类) classLoader=Class.forName("java.lang.Object").getClassLoader(); System.out.println(classLoader); } } package rfle; public class RelectionTest { public static void main(String[] args) throws ClassNotFoundException { TestClassLoader.testClassLoader(); } }

    运行结果

    3.2 获取构造器对象

    这是一个Person类

    public class Person { String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; System.out.println("this is setName"); } public int getAge() { return age; } public void setAge(int age) { this.age = age; System.out.println("this is setAge"); } } /*** * 包含一个带参的构造方法和不带参的构造方法 * @param name * @param age */ public Person(String name, int age) { this.name = name; this.age = age; } public Person() { } //私有方法 private void privateMethod(){ System.out.println("这是一个私有方法"); } } public static void testConstructor() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { String className="rfle.Person"; Class<Person> clazz= (Class<Person>) Class.forName(className); System.out.println("获取全部Constructor对象-----"); Constructor<Person>[] constructors= (Constructor<Person>[]) clazz.getConstructors(); for (Constructor<Person> constructor:constructors) { System.out.println(constructor); } System.out.println("获取某一个Constructor对象 需要参数列表----"); Constructor<Person> constructor= clazz.getConstructor(String.class,int.class); System.out.println(constructor); System.out.println("调用Constructor的newInstance方法创建对象----"); Person person1= constructor.newInstance("赵Sir",18); System.out.println(person1.getName()); }

    运行结果 这里需要说一下因为我们的构造方法的参数类型是int型的,所以我们再获取构造器的时候传入的参数一定是int.class 而不能是Integer.class,不然会报没有找到方法异常

    3.3 获取方法并执行相对应的方法

    public class TestMethod { /** * 方法相关 */ public void testMethod() throws Exception{ String className="rfle.Person"; Class clazz=Class.forName(className); System.out.println("获取clazz对应类中的所有方法," + "不能获取private方法,且获取从父类继承来的所有方法"); Method[] methods= clazz.getMethods(); for (Method method:methods) { System.out.println(method.getName()+"()"); } System.out.println("__________________________________"); System.out.println("获取所有方法,包括私有方法" + "所有声明的方法,且获取当前类方法"); methods= clazz.getDeclaredMethods(); for (Method method:methods) { System.out.println(method.getName()+"()"); } System.out.println("__________________________________"); System.out.println("获取指定方法,和获取构造器的差不多,需要方法名称 和参数列表 无参则不写"); Method method=clazz.getDeclaredMethod("setName",String.class); System.out.println(method); method=clazz.getDeclaredMethod("setAge",int.class); System.out.println(method); System.out.println("__________________________________"); System.out.println("执行我们获取的方法"); Object object=clazz.newInstance(); //第一个参数 这个方法所在类的实例,可变参数 参数列表 method.invoke(object,18); System.out.println("__________________________________"); System.out.println("执行私有方法—"); method=clazz.getDeclaredMethod("privateMethod"); //在执行私有方法之前 一定要 执行这句代码。把Accessible设成true method.setAccessible(true); method.invoke(object); } }

    运行结果 执行私有方法的时候一定要注意,一定要在执行之前,将Accessible设成true,不仅是方法,访问私有域的时候也得设置一下

    3.4 通过反射访问成员变量

    public class TestFiled { /*** * 域相关 */ public void testFiled() throws Exception{ String className="rfle.Person"; Class clazz=Class.forName(className); System.out.println("获取共有和私有的所有字段,但不能获取父类字段"); Field[] fields= clazz.getDeclaredFields(); for (Field field:fields) { System.out.println(field.getName()); } System.out.println("__________________________________"); System.out.println("获取指定字段"); Field field=clazz.getDeclaredField("name"); System.out.println(field.getName()); System.out.println("__________________________________"); System.out.println("获取指定字段的值"); Person person=new Person("铭儿",18); //第一个参数 这个方法所在类的实例 Object object= field.get(person); System.out.println(field.getName()+"="+object); System.out.println("__________________________________"); System.out.println("设置指定对象的值"); field.set(person,"小铭同学"); System.out.println(field.getName()+"="+person.getName()); //访问私有字段 field=clazz.getDeclaredField("age"); field.setAccessible(true); field.get(person); field.set(person,20); System.out.println(field.getName()+"="+person.getAge()); } }

    运行结果 反射很强大,它很灵活,可以突破权限修饰符直接进行访问,所以使用的时候一定要小心 因为反射是在运行的时候,临时创建的类,在调用的效率上肯定比我们new的时候要慢

    三、动态代理模式

    1.静态代理模式

    定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用 目的:

    通过引入代理对象方式,来间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性通过代理对象对原有的业务增强 通俗点讲所谓的代理对象就相当于,我们日常生活中的中介 用品公司,就是目标对象,mark代购就是代理对象,张三 就是访问着,代理不单单提供工厂的售卖服务,还提供售前咨询,售后服务等等,但是用代码怎么实现呢 目标对象和 代理对象都要实现一个共同的接口服务 代理对象 包含目标对象 我们用代码实现一下这种模式 /*** * 代理对象 和工厂共同的业务接口 */ public interface ManToolsFactory { void saleManTools(String size); } /*** * 目标对象 工厂 */ public class AaFactory implements ManToolsFactory{ @Override public void saleManTools(String size) { System.out.println("按需求定制了一个size为"+size+"的女model"); } } /*** * 代理对象 mark */ public class Mark implements ManToolsFactory { public Mark(ManToolsFactory manToolsFactory) { this.manToolsFactory = manToolsFactory; } public ManToolsFactory manToolsFactory; @Override public void saleManTools(String size) { doSthBefore(); manToolsFactory.saleManTools(size); doSthAfter(); } /*** * 后置处理器 */ private void doSthAfter(){ System.out.println("精美包装,快递一条龙服务"); } /*** * 前置处理器 */ private void doSthBefore(){ System.out.println("根据需求进行市场调研和产品分析"); } } /*** *张三 */ public class Client { public static void main(String[] args) { //静态代理模式 ManToolsFactory factory=new AaFactory(); Mark mark=new Mark(factory); mark.saleManTools("D"); } }

    这样的话,张三和工厂没有任何联系,而是通过mark这个代理买来了要给size是D的女model 这就是静态代理模式,因为真实对象,和代理对象是一对一存在的 静态代理模式有什么缺点呢 违反开闭原则:扩展能力差,可维护性差

    2. 动态代理模式

    业务扩展了,多了一个张三的老婆 ,张三和张三的老婆要求肯定是不一样的,都是好朋友 不都得帮忙 他们的需求不一样,人也不一样,怎么办呢,如果用静态代理维护,要么Mark再添加一个业务,女人用品的服务 要么把他推荐给更懂女人的Alvin

    public class Alvin implements WomanToolsFactory { public WomanToolsFactory womanToolsFactory; @Override public void saleWomenTools(float length) { womanToolsFactory.saleWomenTools(length); } /*** * 后置处理器 */ private void doSthAfter(){ System.out.println("精美包装,快递一条龙服务"); } /*** * 前置处理器 */ private void doSthBefore(){ System.out.println("根据需求进行市场调研和产品分析"); } }

    按同样的流程走一遍,如果业务全包给mark,每次增加一个业务是不是都得更改mark,这就违反了开闭原则(对修改关闭,对新增开放)

    这个海外代购公司,就是动态的生成代理,张三来了就安排mark去接待,他老婆来了就派Avlin去接待

    /*** * Mark代购公司 */ public class MarkCompany implements InvocationHandler { /*** * 持有真实对象 */ private Object factory; public Object getFactory() { return factory; } public void setFactory(Object factory) { this.factory = factory; } /*** * 通过Proxy获取动态代理对象 */ public Object getProxyInstance(){ return Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(),this); } /*** * 通过动态代理对象方法进行增强 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { doSthBefore(); Object result =method.invoke(factory,args); doSthAfter(); return result; } /*** * 后置处理器 */ private void doSthAfter(){ System.out.println("精美包装,快递一条龙服务"); } /*** * 前置处理器 */ private void doSthBefore(){ System.out.println("根据需求进行市场调研和产品分析"); } } public class Client { public static void main(String[] args) { //静态代理模式 ManToolsFactory factory=new AaFactory(); Mark mark=new Mark(factory); mark.saleManTools("D"); //动态代理模式 ManToolsFactory aaToolsFactory=new AaFactory(); MarkCompany markCompany=new MarkCompany(); markCompany.setFactory(aaToolsFactory); //张三来了 ManToolsFactory emploee1= (ManToolsFactory) markCompany.getProxyInstance(); emploee1.saleManTools("E"); //张三老婆来了 WomanToolsFactory bbToolsFactory=new BbFactory(); markCompany.setFactory(bbToolsFactory); WomanToolsFactory emploee2= (WomanToolsFactory) markCompany.getProxyInstance(); emploee2.saleWomenTools(1.8f); //我们再编码过程中没有声明任何代理类就完成了两次 海外代购 } }

    这样就可以动态的生成代理类了,无需再去声明Mark和Alvin了,同样的完成了海外代购服务

    3.Proxy是怎么帮助我们生成的代理类的呢?

    我们debug的时候看一下啊 emploee的信息 在后边的类信息里,emplyee1 是、$Proxy0 这个就是jdk为我们生成的代理对象,$Proxy0 是怎么生成的呢,了解这一个问题之前 先了解一下类的生命周期 看我们在生成动态代理的时候,是没有走编写java源文件这一步的,jdk是怎么实现的呢 在Proxy的newProxyInstance方法里 看我画圈圈的地方Class Class什么,前面我们说了是每个类的对应的class对象,这个肯定是有用的,然后接着往下看 然后拿到了这个类的构造方法,返回了 cl对应类的一个实例,然后我们继续去看这个cl是怎么生成的去看getProxyClass0这个方法 看到这个代码,发现了一个规矩好像,在实现接口的时候,接口数不能超过65535,字节码里有这个存储接口数量的正好占四个字节,刚好不能超过这个数 然后我们再看return 看变量名proxyClassCache,说明jdk在生成动态代理的时候是做了缓存的,既然是有缓存,第一次生成的的时候肯定不存在啊,那我们就接着去看get的实现,在get方法里我们发现真正创建代理类的方法是这个方法 接着往下走去看apply,我们会发现 apply是一个泛型接口, 我们goto impl正好发现了一个实现类 跟着这个思路走,去看这个实现类里这个方法,我们好像又发现了点什么

    它吧我们的代理类生成了字节码然后存在了byte里了,我们再看看proxyname 哎呀,正好和我们debug出来的一样,后边加了个 long 进行cas自增的一个num.,为了验证是不是递增的我再debug一下把emplee2也看一下 ProxyGenerator.generateProxyClass()这个方法就是 传进去的class信息,然后根据class信息,去实现 这个接口的实现类的字节码,然后通过defineClass0这个方法返回一个class 但是我们点进去 发现是一个native方法,那我们想个办法 看一看这个生成的代理类到底是啥,我们把生成的byte数组写到一个文件里,然后再用反编译工具 反编译一下看看 到底是个什么东西

    public class ProxyUtils { public static void generateClassFile(Class clazz,String proxyName){ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, new Class[]{clazz}); String paths=clazz.getResource(".").getPath(); FileOutputStream out=null; try { out=new FileOutputStream(paths+proxyName+".class"); out.write(proxyClassFile); out.flush(); } catch (IOException e) { e.printStackTrace(); }finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }

    调用

    //动态代理模式 ManToolsFactory aaToolsFactory=new AaFactory(); MarkCompany markCompany=new MarkCompany(); markCompany.setFactory(aaToolsFactory); //张三来了 ManToolsFactory emploee1= (ManToolsFactory) markCompany.getProxyInstance(); emploee1.saleManTools("E"); //张三老婆来了 WomanToolsFactory bbToolsFactory=new BbFactory(); markCompany.setFactory(bbToolsFactory); WomanToolsFactory emploee2= (WomanToolsFactory) markCompany.getProxyInstance(); emploee2.saleWomenTools(1.8f); ProxyUtils.generateClassFile(aaToolsFactory.getClass(),emploee1.getClass().getSimpleName()); ProxyUtils.generateClassFile(bbToolsFactory.getClass(),emploee2.getClass().getSimpleName());

    反编译之后的结果

    // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxy.AaFactory; public final class $Proxy0 extends Proxy implements AaFactory { private static Method m1; private static Method m8; private static Method m3; private static Method m2; private static Method m6; private static Method m5; private static Method m7; private static Method m9; private static Method m0; private static Method m4; 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 void notify() throws { try { super.h.invoke(this, m8, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void saleManTools(String var1) throws { try { super.h.invoke(this, m3, 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 void wait(long var1) throws InterruptedException { try { super.h.invoke(this, m6, new Object[]{var1}); } catch (RuntimeException | InterruptedException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } public final void wait(long var1, int var3) throws InterruptedException { try { super.h.invoke(this, m5, new Object[]{var1, var3}); } catch (RuntimeException | InterruptedException | Error var5) { throw var5; } catch (Throwable var6) { throw new UndeclaredThrowableException(var6); } } public final Class getClass() throws { try { return (Class)super.h.invoke(this, m7, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void notifyAll() throws { try { super.h.invoke(this, m9, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 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 wait() throws InterruptedException { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | InterruptedException | 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")); m8 = Class.forName("proxy.AaFactory").getMethod("notify"); m3 = Class.forName("proxy.AaFactory").getMethod("saleManTools", Class.forName("java.lang.String")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m6 = Class.forName("proxy.AaFactory").getMethod("wait", Long.TYPE); m5 = Class.forName("proxy.AaFactory").getMethod("wait", Long.TYPE, Integer.TYPE); m7 = Class.forName("proxy.AaFactory").getMethod("getClass"); m9 = Class.forName("proxy.AaFactory").getMethod("notifyAll"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m4 = Class.forName("proxy.AaFactory").getMethod("wait"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }

    所有的代理类都继承了 Proxy,看反编译结果 是不是实现了我们的业务接口AaFactory,是不是满足了我们前面所说的不管是代理对象还是真实对象,都得共同实现一个业务接口,接着往下看 我们看到了一个熟悉的方法saleManTools(),里面执行了一个 h.invoke(this, m3, new Object[]{var1}); h是谁呢 这个文件里没有,肯定是再父类里面,我们去找找

    我去,这个h不就是我们在写动态代理传进去的InvocationHandler么,它的invoke方法不就是 this,就是代理对象了,m3是啥?我们继续找m3 看到这里可能,有同学会懵逼,我第一遍也懵来着,总结一下 1、MarkCompany 实现了 InvocationHandler 2丶调用MarkCompany getProxyInstance的时候将业务接口的Class信息传给Proxy.newProxyInstance() 3、newProxyInstance利用反射生成一个 $Proxy+number的一个类 4,newProxyInstance,生成一个代理类的实例 将InvocationHandler 也就是MarkCompany传进去 5、调用这个代理类的实例的 saleManTools方法 ,也就调用了InvocationHandler 也就是MarkCompany 的invoke方法,完成了代理的对象方法的增强

    总结

    上边就是反射和动态代理的相关知识,从源码入手分析了一下Jdk动态代理对象是怎么生成的,写的有什么不对的请大佬们多多赐教!希望大佬们一键三连!

    Processed: 0.010, SQL: 8