重学Android基础系列篇(三):架构动态编程技术原理

前言

本系列文章主要是汇总了一下大佬们的技术文章,属于Android基础部分,作为一名合格的安卓开发工程师,咱们肯定要熟练掌握java和android,本期就来说说这些~

[非商业用途,如有侵权,请告知我,我会删除]

DD一下: Android进阶开发各类文档/资料,也可关注公众号<Android苦做舟>获取。

1.Android高级开发工程师必备基础技能
2.Android性能优化核心知识笔记
3.Android+音视频进阶开发面试题冲刺合集
4.Android 音视频开发入门到实战学习手册
5.Android Framework精编内核解析
6.Flutter实战进阶技术手册
7.近百个Android录播视频+音视频视频dome
.......

架构动态编程技术原理

动态编程核心架构:动态代理

1.前言

代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。

其实代理模式的核心思想就是这么简单,在java中,代理又分静态代理和动态代理2种,其中动态代理根据不同实现又区分基于接口的的动态代理和基于子类的动态代理。

其中静态代理由于比较简单,面试中也没啥问的,在代理模式一块,问的最多就是动态代理,而且动态代理也是spring aop的核心思想,spring其他很多功能也是通过动态代理来实现的,比如拦截器,事务控制等。

熟练掌握动态代理技术,能让你业务代码更加精简而优雅。如果你需要写一些中间件的话,那动态代理技术更是必不可少的技能包。

那此篇文章就带大家一窥动态代理的所有细节吧。

2.静态代理

在说动态代理前,还是先说说静态代理。

所谓静态代理,就是通过声明一个明确的代理类来访问源对象。

我们有2个接口,Person和Animal。每个接口各有2个实现类,UML如下图:

每个实现类中代码都差不多一致,用Student来举例(其他类和这个几乎一模一样)

public class Student implements Person{
    private String name;

    public Student() {
   }

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void wakeup() {
        System.out.println(StrUtil.format("学生[{}]早晨醒来啦",name));
    }

    @Override
    public void sleep() {
        System.out.println(StrUtil.format("学生[{}]晚上睡觉啦",name));
    }
}

假设我们现在要做一件事,就是在所有的实现类调用wakeup()前增加一行输出早安~,调用sleep()前增加一行输出晚安~。那我们只需要编写2个代理类PersonProxyAnimalProxy

PersonProxy:

public class PersonProxy implements Person {

    private Person person;

    public PersonProxy(Person person) {
        this.person = person;
    }

    @Override
    public void wakeup() {
        System.out.println("早安~");
        person.wakeup();
    }

    @Override
    public void sleep() {
        System.out.println("晚安~");
        person.sleep();
    }
}

AnimalProxy:

public class AnimalProxy implements Animal {

    private Animal animal;

    public AnimalProxy(Animal animal) {
        this.animal = animal;
    }

    @Override
    public void wakeup() {
        System.out.println("早安~");
        animal.wakeup();
    }

    @Override
    public void sleep() {
        System.out.println("晚安~");
        animal.sleep();
    }
}

最终执行代码为:

public static void main(String[] args) {
    Person student = new Student("张三");
    PersonProxy studentProxy = new PersonProxy(student);
    studentProxy.wakeup();
    studentProxy.sleep();
​
    Person doctor = new Doctor("王教授");
    PersonProxy doctorProxy = new PersonProxy(doctor);
    doctorProxy.wakeup();
    doctorProxy.sleep();
​
    Animal dog = new Dog("旺旺");
    AnimalProxy dogProxy = new AnimalProxy(dog);
    dogProxy.wakeup();
    dogProxy.sleep();
​
    Animal cat = new Cat("咪咪");
    AnimalProxy catProxy = new AnimalProxy(cat);
    catProxy.wakeup();
    catProxy.sleep();
}

输出:

早安~
学生[张三]早晨醒来啦
晚安~
学生[张三]晚上睡觉啦
早安~
医生[王教授]早晨醒来啦
晚安~
医生[王教授]晚上睡觉啦
早安~~
小狗[旺旺]早晨醒来啦
晚安~~
小狗[旺旺]晚上睡觉啦
早安~~
小猫[咪咪]早晨醒来啦
晚安~~
小猫[咪咪]晚上睡觉啦

结论:

静态代理的代码相信已经不用多说了,代码非常简单易懂。这里用了2个代理类,分别代理了PersonAnimal接口。

这种模式虽然好理解,但是缺点也很明显:

  • 会存在大量的冗余的代理类,这里演示了2个接口,如果有10个接口,就必须定义10个代理类。
  • 不易维护,一旦接口更改,代理类和目标类都需要更改。

3.JDK动态代理

动态代理,通俗点说就是:无需声明式的创建java代理类,而是在运行过程中生成"虚拟"的代理类,被ClassLoader加载。从而避免了静态代理那样需要声明大量的代理类。

JDK从1.3版本就开始支持动态代理类的创建。主要核心类只有2个:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

还是前面那个例子,用JDK动态代理类去实现的代码如下:

创建一个JdkProxy类,用于统一代理:

public class JdkProxy implements InvocationHandler {
​
    private Object bean;
​
    public JdkProxy(Object bean) {
        this.bean = bean;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("wakeup")){
            System.out.println("早安~~~");
        }else if(methodName.equals("sleep")){
            System.out.println("晚安~~~");
        }
​
        return method.invoke(bean, args);
    }
}

执行代码:

public static void main(String[] args) {
    JdkProxy proxy = new JdkProxy(new Student("张三"));
    Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
    student.wakeup();
    student.sleep();
​
    proxy = new JdkProxy(new Doctor("王教授"));
    Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
    doctor.wakeup();
    doctor.sleep();
​
    proxy = new JdkProxy(new Dog("旺旺"));
    Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
    dog.wakeup();
    dog.sleep();
​
    proxy = new JdkProxy(new Cat("咪咪"));
    Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
    cat.wakeup();
    cat.sleep();
}

讲解:

可以看到,相对于静态代理类来说,无论有多少接口,这里只需要一个代理类。核心代码也很简单。唯一需要注意的点有以下2点:

  • JDK动态代理是需要声明接口的,创建一个动态代理类必须得给这个”虚拟“的类一个接口。可以看到,这时候经动态代理类创造之后的每个bean已经不是原来那个对象了。

  • 为什么这里JdkProxy还需要构造传入原有的bean呢?因为处理完附加的功能外,需要执行原有bean的方法,以完成代理的职责。

    这里JdkProxy最核心的方法就是

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

    其中proxy为代理过之后的对象(并不是原对象),method为被代理的方法,args为方法的参数。

    如果你不传原有的bean,直接用method.invoke(proxy, args)的话,那么就会陷入一个死循环。

可以代理什么

JDK的动态代理是也平时大家使用的最多的一种代理方式。也叫做接口代理。前几天有一个小伙伴在群里问我,动态代理是否一次可以代理一个类,多个类可不可以。

JDK动态代理说白了只是根据接口”凭空“来生成类,至于具体的执行,都被代理到了InvocationHandler 的实现类里。上述例子我是需要继续执行原有bean的逻辑,才将原有的bean构造进来。只要你需要,你可以构造进任何对象到这个代理实现类。也就是说,你可以传入多个对象,或者说你什么类都不代理。只是为某一个接口”凭空“的生成多个代理实例,这多个代理实例最终都会进入InvocationHandler的实现类来执行某一个段共同的代码。

所以,在以往的项目中的一个实际场景就是,我有多个以yaml定义的规则文件,通过对yaml文件的扫描,来为每个yaml规则文件生成一个动态代理类。而实现这个,我只需要事先定义一个接口,和定义InvocationHandler的实现类就可以了,同时把yaml解析过的对象传入。最终这些动态代理类都会进入invoke方法来执行某个共同的逻辑。

4.Cglib动态代理

Spring在5.X之前默认的动态代理实现一直是jdk动态代理。但是从5.X开始,spring就开始默认使用Cglib来作为动态代理实现。并且springboot从2.X开始也转向了Cglib动态代理实现。

是什么导致了spring体系整体转投Cglib呢,jdk动态代理又有什么缺点呢?

那么我们现在就要来说下Cglib的动态代理。

Cglib是一个开源项目,它的底层是字节码处理框架ASM,Cglib提供了比jdk更为强大的动态代理。主要相比jdk动态代理的优势有:

  • jdk动态代理只能基于接口,代理生成的对象只能赋值给接口变量,而Cglib就不存在这个问题,Cglib是通过生成子类来实现的,代理对象既可以赋值给实现类,又可以赋值给接口。
  • Cglib速度比jdk动态代理更快,性能更好。

那何谓通过子类来实现呢?

还是前面那个例子,我们要实现相同的效果。代码如下

创建CglibProxy类,用于统一代理:

public class CglibProxy implements MethodInterceptor {
​
    private Enhancer enhancer = new Enhancer();
​
    private Object bean;
​
    public CglibProxy(Object bean) {
        this.bean = bean;
    }
​
    public Object getProxy(){
        //设置需要创建子类的类
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        if (methodName.equals("wakeup")){
            System.out.println("早安~~~");
        }else if(methodName.equals("sleep")){
            System.out.println("晚安~~~");
        }
​
        //调用原bean的方法
        return method.invoke(bean,args);
    }
}

执行代码:

public static void main(String[] args) {
    CglibProxy proxy = new CglibProxy(new Student("张三"));
    Student student = (Student) proxy.getProxy();
    student.wakeup();
    student.sleep();
​
    proxy = new CglibProxy(new Doctor("王教授"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();
​
    proxy = new CglibProxy(new Dog("旺旺"));
    Dog dog = (Dog) proxy.getProxy();
    dog.wakeup();
    dog.sleep();
​
    proxy = new CglibProxy(new Cat("咪咪"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}

讲解:

在这里用Cglib作为代理,其思路和jdk动态代理差不多。也需要把原始bean构造传入。原因上面有说,这里不多赘述。

关键的代码在这里

//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();

可以看到,Cglib"凭空"的创造了一个原bean的子类,并把Callback指向了this,也就是当前对象,也就是这个proxy对象。从而会调用intercept方法。而在intercept方法里,进行了附加功能的执行,最后还是调用了原始bean的相应方法。

在debug这个生成的代理对象时,我们也能看到,Cglib是凭空生成了原始bean的子类:

5.javassist动态代理

Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

在日常使用中,javassit通常被用来动态修改字节码。它也能用来实现动态代理的功能。

话不多说,还是一样的例子,我用javassist动态代理来实现一遍

创建JavassitProxy,用作统一代理:

public class JavassitProxy {
​
    private Object bean;
​
    public JavassitProxy(Object bean) {
        this.bean = bean;
    }
​
    public Object getProxy() throws IllegalAccessException, InstantiationException {
        ProxyFactory f = new ProxyFactory();
        f.setSuperclass(bean.getClass());
        f.setFilter(m -> ListUtil.toList("wakeup","sleep").contains(m.getName()));
​
        Class c = f.createClass();
        MethodHandler mi = (self, method, proceed, args) -> {
            String methodName = method.getName();
            if (methodName.equals("wakeup")){
                System.out.println("早安~~~");
            }else if(methodName.equals("sleep")){
                System.out.println("晚安~~~");
            }
            return method.invoke(bean, args);
        };
        Object proxy = c.newInstance();
        ((Proxy)proxy).setHandler(mi);
        return proxy;
    }
}

执行代码:

public static void main(String[] args) throws Exception{
    JavassitProxy proxy = new JavassitProxy(new Student("张三"));
    Student student = (Student) proxy.getProxy();
    student.wakeup();
    student.sleep();
​
    proxy = new JavassitProxy(new Doctor("王教授"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();
​
    proxy = new JavassitProxy(new Dog("旺旺"));
    Dog dog = (Dog) proxy.getProxy();
    dog.wakeup();
    dog.sleep();
​
    proxy = new JavassitProxy(new Cat("咪咪"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}

讲解:

熟悉的配方,熟悉的味道,大致思路也是类似的。同样把原始bean构造传入。可以看到,javassist也是用”凭空“生成子类的方式类来解决,代码的最后也是调用了原始bean的目标方法完成代理。

javaassit比较有特点的是,可以对所需要代理的方法用filter来设定,里面可以像Criteria构造器那样进行构造。其他的代码,如果你仔细看了之前的代码演示,应该能很轻易看懂了。

6.ByteBuddy动态代理

ByteBuddy,字节码伙计,一听就很牛逼有不。

ByteBuddy也是一个大名鼎鼎的开源库,和Cglib一样,也是基于ASM实现。还有一个名气更大的库叫Mockito,相信不少人用过这玩意写过测试用例,其核心就是基于ByteBuddy来实现的,可以动态生成mock类,非常方便。另外ByteBuddy另外一个大的应用就是java agent,其主要作用就是在class被加载之前对其拦截,插入自己的代码。

ByteBuddy非常强大,是一个神器。可以应用在很多场景。但是这里,只介绍用ByteBuddy来做动态代理,关于其他使用方式,可能要专门写一篇来讲述,这里先给自己挖个坑。

来,还是熟悉的例子,熟悉的配方。用ByteBuddy我们再来实现一遍前面的例子

创建ByteBuddyProxy,做统一代理:

public class ByteBuddyProxy {
​
    private Object bean;
​
    public ByteBuddyProxy(Object bean) {
        this.bean = bean;
    }
​
    public Object getProxy() throws Exception{
        Object object = new ByteBuddy().subclass(bean.getClass())
                .method(ElementMatchers.namedOneOf("wakeup","sleep"))
                .intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
                .make()
                .load(ByteBuddyProxy.class.getClassLoader())
                .getLoaded()
                .newInstance();
        return object;
    }
​
    public class AopInvocationHandler implements InvocationHandler {
​
        private Object bean;
​
        public AopInvocationHandler(Object bean) {
            this.bean = bean;
        }
​
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (methodName.equals("wakeup")){
                System.out.println("早安~~~");
            }else if(methodName.equals("sleep")){
                System.out.println("晚安~~~");
            }
            return method.invoke(bean, args);
        }
    }
}

执行代码:

public static void main(String[] args) throws Exception{
    ByteBuddyProxy proxy = new ByteBuddyProxy(new Student("张三"));
    Student student = (Student) proxy.getProxy();
    student.wakeup();
    student.sleep();
​
    proxy = new ByteBuddyProxy(new Doctor("王教授"));
    Doctor doctor = (Doctor) proxy.getProxy();
    doctor.wakeup();
    doctor.sleep();
​
    proxy = new ByteBuddyProxy(new Dog("旺旺"));
    Dog dog = (Dog) proxy.getProxy();
    dog.wakeup();
    dog.sleep();
​
    proxy = new ByteBuddyProxy(new Cat("咪咪"));
    Cat cat = (Cat) proxy.getProxy();
    cat.wakeup();
    cat.sleep();
}

讲解:

思路和之前还是一样,通过仔细观察代码,ByteBuddy也是采用了创造子类的方式来实现动态代理。

7.各种动态代理的性能如何

前面介绍了4种动态代理对于同一例子的实现。对于代理的模式可以分为2种:

  • JDK动态代理采用接口代理的模式,代理对象只能赋值给接口,允许多个接口
  • Cglib,Javassist,ByteBuddy这些都是采用了子类代理的模式,代理对象既可以赋值给接口,又可以复制给具体实现类

Spring5.X,Springboot2.X只有都采用了Cglib作为动态代理的实现,那是不是cglib性能是最好的呢?

我这里做了一个简单而粗暴的实验,直接把上述4段执行代码进行单线程同步循环多遍,用耗时来确定他们4个的性能。应该能看出些端倪。

JDK PROXY循环10000遍所耗时:0.714970125秒
Cglib循环10000遍所耗时:0.434937833秒
Javassist循环10000遍所耗时:1.294194708秒
ByteBuddy循环10000遍所耗时:9.731999042秒

执行的结果如上

从执行结果来看,的确是cglib效果最好。至于为什么ByteBuddy执行那么慢,不一定是ByteBuddy性能差,也有可能是我测试代码写的有问题,没有找到正确的方式。所以这只能作为一个大致的参考。

看来Spring选择Cglib还是有道理的。

动态代理技术对于一个经常写开源或是中间件的人来说,是一个神器。这种特性提供了一种新的解决方式。从而使得代码更加优雅而简单。动态代理对于理解spring的核心思想也有着莫大的帮助,希望对动态代理技术感兴趣的同学能试着去跑一遍示例中的代码,来加强理解。

2、动态编程核心:反射

1.Java反射机制概述

Reflection(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性以及方法

Class c = Class.forName("java.lang.String");                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整类的类的结构信息。我们可以通过对这个对象看到的类的结构。这个对象就像一面镜子,通过这个镜子看到类的结构,所以,我们形象地称之为:反射

  • 正常方式:引入需要的 “ 包类 ” 名称 ——> 通过 new 实例化 ——> 取得实例化对象
  • 反射方式:实例化对象 ——> getClass() 方法 ——> 得到完整的 “包类” 名称

2.Java 反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

3.Java 反射优点和缺点

优点: 可以实现动态创建对象和编译,体现出很大的灵活性

缺点: 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们什么要求,这类操作总是慢于直接执行相同的操作

4.反射相关的主要 API

  • java.lang.Class 代表一个类
  • java.lang.reflect.Method 代表类的方法
  • java.lang.reflect.Field 代表类的成员变量
  • java.lang.reflect.Constructor 代表类的构造器

5.理解Class类并获取Class实例

先定义一个实体类

image-20220708190051431

主程序

public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类Class对象                                                                                                           
Class<?> cl = Class.forName("com.sww.Refelection.User");
System.out.println(cl);
}    
输出:class com.sww.Refelection.User

一个类在内存中只有一个 Class 对象,一个类被加载之后,类的整个结构都会被封装在 Class 对象中

在 Object 类中定义了以下方法,此方法将被所有子类继承

public final native Class<?> getClass();     

上面的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称

对于每个类而言,JRE 都为其保留了一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class / interface / enum / annotation / primitive type/void / [])

  • Class 本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在 JVM 中只会有一个 Class 实例
  • 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 通过 Class 可以完整地得到一个类中的所有被加载的结构
  • Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类、唯有先获得响应的 Class 对象

Class 类的常用方法

方法名 功能说明
static Class forName(String name) 返回指定类名 name 的 Class 对象
Object newInstance() 调用缺省构造函数,返回 Class 对象的一个实例
getName() 返回此 Class 对象所表示的实体(类、接口、数组类、或 void)的名称
Class getSuperClass() 返回当前 Class 对象的父类的 Class 对象
Class[] getInterfaces() 获取当前 Class 对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Constructor[] getConstructors() 返回一个包含某些 Constructor 对象的数组
Method getMethod(String name, Class… T) 返回一个 Method 对象,此对象的形参类型为 paramType
Field[] getDeclaredFields() 返回 Field 对象的一个数组

6.获取 Class 类的实例

public static void main(String[] args) throws Exception {
// 方式一:forName 获得
Class aClass = Class.forName("com.java.demo.reflect.User");
System.out.println(aClass);
// 方式二:通过对象获得
Class aClass1 = new User().getClass();
System.out.println(aClass1);
// 方式三:通过类名.class 获得
Class<User> aClass2 = User.class;
System.out.println(aClass2);
}      

7.所有类型的 Class 对象

ublic static void main(String[] args) {
Class objectClass = Object.class;            // 类
Class comparableClass = Comparable.class;    // 接口
Class aClass = String[].class;               // 一维数组
Class aClass1 = int[][].class;               // 二维数组
Class overrideClass = Override.class;        // 注解
Class elementTypeClass = ElementType.class;  // 枚举
Class integerClass = Integer.class;          // 基本数据类型
Class voidClass = void.class;                // void
Class classClass = Class.class;              // Class
System.out.println(objectClass);
System.out.println(comparableClass);
System.out.println(aClass);
System.out.println(aClass1); 
System.out.println(overrideClass);
System.out.println(elementTypeClass);
System.out.println(integerClass); 
System.out.println(voidClass); 
System.out.println(classClass);                                                                                                     
}                                                                                                                               

8.Java 内存分析

9.类的加载与ClassLoader

  • 加载:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象
  • 链接:将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程
  • 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题
  • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 初始化:
  • 执行类构造器< clint>() 方法的过程,类构造器< clint>() 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
  • 虚拟机会保证一个类的< clint>() 方法在多线程环境中被正确加锁和同步
public static void main(String[] args) {
/**
* 1、加载到内存,会产生一个类对应的 Class 对象
* 2、链接,链接结束后 m = 0
* 3、初始化         
<clint>() {   
System.out.println("A 类静态代码块初始化");
m = 300;     
m = 100;       
}        
m = 100;    
*/  
A a = new A();
System.out.println(A.m);
}         
}     
class A {     
static {
System.out.println("A 类静态代码块初始化"); 
m = 300;
}  
static int m = 100;  
public A() {
System.out.println("A 类的无参构造函数初始化");
}                                                                                           
------------------------------------------------------------------------                                                         
A 类静态代码块初始化                                                                                                                 
A 类的无参构造函数初始化
100

10.获取运行时类的完整结构 & 动态创建对象执行方法

Field、Method、Constructor、SuperClass、Interface、Annotation

  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的 Field
  • 注解

11.性能对比分析

setAccessible

  • Method 和 Field、Constructor 对象都有 setAccessible() 方法
  • setAccessible 作用是启动和禁用访问安全检查的开关
  • 参数值为 true 则提示反射的对象在使用时应该取消 Java 语言访问检查
  • 提高反射的效率,如果代码中必须用反射,而该语句需要频繁的被调用,那么请设置为 true
  • 使得原本无法访问的私有成员也可以访问
  • 参数值为 false 则指示反射的对象应该实施 Java 语言访问检查
// 普通方式调用
public static void test01() {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式执行 1 亿次:" + (endTime - startTime) + "ms");
}  
// 反射方式调用    
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c = user.getClass();
Method getName = c.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式执行 1 亿次:" + (endTime - startTime) + "ms");
 }
// 反射方式调用,关闭检测 
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 
User user = new User();
Class c = user.getClass();
Method getName = c.getDeclaredMethod("getName", null);
getName.setAccessible(true); 
long startTime = System.currentTimeMillis(); 
for (int i = 0; i < 100000000; i++) { 
getName.invoke(user, null);
}       
long endTime = System.currentTimeMillis();
System.out.println("关闭检测方式执行 1 亿次:" + (endTime - startTime) + "ms");
}      
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test01();       
test02(); 
test03();        
}            
---------------------------------------------------------------------
普通方式执行 1 亿次:4ms 
反射方式执行 1 亿次:119ms 
关闭检测方式执行 1 亿次:87ms   
折叠 

12.通过反射去操作泛型

  • Java 采用泛型擦除的机制来引入泛型,Java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除
  • 为了通过反射操作这些类型,Java 新增了 ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 几种类型来代表不能被归一到 Class 类中的类型但是又和原始类型齐名的类型
  • ParameterizedType:表示一种参数化类型,比如 Collection< String>
  • GenericArrayType:表示一种元素类型时参数化类型或者类型变量的数组类型
  • TypeVariable :是各种类型变量的公共父接口
  • WildcardType :代表一种通配符类型表达式
public void test01(Map<String, User> map, List<User> list) {
    System.out.println("test01");
}
​
public Map<String, User> test02() {
    System.out.println("test02");
    return null;
}
​
// 通过反射获取泛型
public static void main(String[] args) throws NoSuchMethodException {
    Method test01 = Test01.class.getMethod("test01", Map.class, List.class);
    // 获取通用参数类型
    Type[] genericParameterTypes = test01.getGenericParameterTypes();
    // 迭代遍历
    for (Type genericParameterType : genericParameterTypes) {
        System.out.println("# " + genericParameterType);
        if (genericParameterType instanceof ParameterizedType) {
            // 获取实际参数类型
            Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
​
    Method test02 = Test01.class.getMethod("test02", null);
    Type genericReturnType = test02.getGenericReturnType();
    if (genericReturnType instanceof ParameterizedType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        for (Type actualTypeArgument : actualTypeArguments) {
            System.out.println(actualTypeArgument);
        }
    }
}

注意: invoke方法如果提供了错误的参数,会抛出一个异常,所以要提供一个异常处理器。建议在有必要的时候才使用invoke方法,有如下原因:

  • 1、invoke方法的参数和返回值必须是Object类型,意味着必须进行多次类型转换
  • 2、通过反射调用方法比直接调用方法要明显慢一些

13.反射类方法

getConstructor(Class<?>… parameterTypes)

获取类的特定 public 构造方法。参数为方法参数对应 Class 的对象

public class ReflectDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Constructor<CaseMethod> constructor = CaseMethod.class.getConstructor(int.class, String.class);
        System.out.println(constructor);
    }
}
​
class CaseMethod{
​
    private int id;
    private String name;
    private int cap;
​
    public CaseMethod(int id,String name){
        this.id = id;
        this.name = name;
    }
​
    public CaseMethod(int cap){
​
    }
}

getConstructors()

获取类的所有 public 构造方法

public static void main(String[] args) {
        Constructor<?>[] constructors = CaseMethod.class.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
    }
}
​
class CaseMethod{
​
    private int id;
    private String name;
    private int cap;
​
    public CaseMethod(int id,String name){
        this.id = id;
        this.name = name;
    }
​
    public CaseMethod(int cap){
​
    }

getDeclaredConstructor(Class<?>… parameterTypes)

    public static void main(String[] args) throws NoSuchMethodException {
        // 注意:由于private修饰的CaseMethod构造函数没有参数,所以getDeclaredConstructor()可以为空
        // 默认的getDeclaredConstructor(Class<?>... parameterTypes)方法是要传参数类型的
        Constructor<CaseMethod> constructor = CaseMethod.class.getDeclaredConstructor();
        Constructor<CaseMethod> declaredConstructor = CaseMethod.class.getDeclaredConstructor(int.class, String.class);
        System.out.println(constructor);
        System.out.println(declaredConstructor);
    }
}
​
class CaseMethod{
​
    private int id;
    private String name;
    private int cap;
​
    private CaseMethod(){
​
    }
​
    public CaseMethod(int id,String name){
        this.id = id;
        this.name = name;
    }

getDeclaredConstructors()

    public static void main(String[] args) throws NoSuchMethodException {
        Constructor<?>[] constructors = CaseMethod.class.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
    }
}
​
class CaseMethod{
​
    private int id;
    private String name;
    private int cap;
​
    private CaseMethod(){
​
    }
​
    public CaseMethod(int id,String name){
        this.id = id;
        this.name = name;
    }

14.使用newInstance创建实例

public class ReflectDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<CaseMethod> constructor = CaseMethod.class.getConstructor(String.class);
        CaseMethod caseMethod = constructor.newInstance("Lisa");
        System.out.println(caseMethod);
    }
}
​
class CaseMethod{
​
    private String name;
​
    public CaseMethod(String name){
        this.name = name;
    }
​
    @Override
    public String toString() {
        return "CaseMethod{" +
                "name='" + name + ''' +
                '}';
    }
}

总结

Java获得Class对象的引用的方法中,Class.forName() 方法会自动初始化Class对象,而 .class 方法不会, .class 的初始化被延迟到静态方法或非常数静态域的首次引用。

猜你喜欢

转载自blog.csdn.net/m0_64420071/article/details/127615393
今日推荐