反射原理及动态代理模式

幻昼 2020年12月19日 224次浏览

反射原理及动态代理模式

静态代理例子:

public interface Subject {
    abstract String saleTt();
}

public class RealSubject implements Subject {
    @Override
    public String saleTt() {
        return "特殊用品";
    }
}

public class JinProxy implements Subject{

    private RealSubject realSubject;
    
    JinProxy(){
        realSubject = new RealSubject();
    }

    @Override
    public String saleTt() {
        String goods = realSubject.saleTt();
        goods = "包装过的" + goods;
        return goods;
    }

    public static void main(String[] args) {
        System.out.println(new JinProxy().saleTt());
    }
}

动态代理:

// 接口类
public interface ShimanoFactory {
    String saleBrake();
}
public interface SramFactory {
    String saleBrake();
}

// 实现类
public class ShimanoImpl implements ShimanoFactory {
    @Override
    public String saleBrake() {
        return "a shimano Brake";
    }
}
public class SramImpl implements SramFactory{
    @Override
    public String saleBrake() {
        return "a sram Brake";
    }
}

// InvocationHandler 类
public class JinCompany implements InvocationHandler {

    private Object factory;

    public Object getFactory() {
        return factory;
    }

    public void setFactory(Object factory) {
        this.factory = factory;
    }

    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);
        System.out.println(result);
        doSthAfter();
        return result;
    }

    private void doSthAfter() {
        System.out.println("精美包装,快递一条龙");
    }

    private void doSthBefore() {
        System.out.println("根据需求,市场调研");
    }
}


// 使用
public class Run {
    public static void main(String[] args) {
        JinCompany jinCompany = new JinCompany();

        ShimanoFactory shimanoFactory = new ShimanoImpl();
        jinCompany.setFactory(shimanoFactory);

        ShimanoFactory shiFactory = (ShimanoFactory) jinCompany.getProxyInstance();
        shiFactory.saleBrake();

        SramFactory sram = new SramImpl();
        jinCompany.setFactory(sram);

        SramFactory sramFactory = (SramFactory) jinCompany.getProxyInstance();
        sramFactory.saleBrake();

    }
}

反射(Reflect)

反射之中包含了一个「反」字,所以了解反射我们先从「正」开始。

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

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

Java反射机制主要提供了以下功能:

  • 在运行时构造任意一个类的对象
  • 在运行时获取任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法(属性)

Java 是一门面向对象的语言。在面向对象的世界里,万事万物皆对象,既然万事万物皆对象,那么我们的类是不是对象呢?我们写的每一个类都可以看成一个对象,是 java.lang.Class 类的对象。每一个类对应的Class放在哪里呢?当我们写完一个类的Java文件,编译成class文件的时候,编译器都会将这个类的对应的class对象放在class文件的末尾。里面都保存了些什么?大家可以理解保存了类的元数据信息,一个类的元数据信息包括什么?有哪些属性,方法,构造器,实现了哪些接口等等,那么这些信息在Java里都有对应的类来表示。

Class类

Class是一个类,封装了当前对象所对应的类的信息

一个类中有属性,方法,构造器等,比如说有一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class,它应该有类名,属性,方法,构造器等。Class是用来描述类的类。

Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等

对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。

对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例

获取Class对象的三种方式

  1. 通过类名获取 类名.class

  2. 通过对象获取 对象名.getClass()

  3. 通过全类名获取 Class.forName(全类名)

Class类的常用方法

image.png

类加载器、构造器、Method、Field

原始类

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()!");
    }

    //包含一个带参的构造器和一个不带参的构造器
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public Person() {
        super();
    }

    //私有方法
    private void privateMethod() {
        System.out.println("this is private method!");
    }
}

测试ClassLoader

    public static void testClassLoader() throws ClassNotFoundException,
            FileNotFoundException {
        //1. 获取一个系统的类加载器(可以获取,当前这个类PeflectTest就是它加载的)
        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("cn.enjoyedu.refle.more.ReflectionTest")
                .getClassLoader();
        System.out.println(classLoader);


        //5. 测试 JDK 提供的 Object 类由哪个类加载器负责加载(引导类)
        classLoader = Class.forName("java.lang.Object")
                .getClassLoader();
        System.out.println(classLoader);
    }

输出:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null

测试构造方法

public void testConstructor() throws Exception{
        String className = "cn.enjoyedu.refle.more.Person";
        Class<Person> clazz = (Class<Person>) Class.forName(className);

        System.out.println("1. 获取全部Constructor对象-----");
        Constructor<Person>[] constructors
                = (Constructor<Person>[]) clazz.getConstructors();
        for(Constructor<Person> constructor: constructors){
            System.out.println(constructor);
        }


        System.out.println("2. 获取某一个Constructor 对象,需要参数列表----");
        Constructor<Person> constructor
                = clazz.getConstructor(String.class, int.class);
        System.out.println(constructor);

        //2. 调用构造器的 newInstance() 方法创建对象
        System.out.println("3. 调用构造器的 newInstance() 方法创建对象-----");
        Person obj = constructor.newInstance("Jin", 18);
        System.out.println(obj.getName());
    }
1. 获取全部Constructor对象-----
   public cn.enjoyedu.refle.more.Person(java.lang.String,int)
   public cn.enjoyedu.refle.more.Person()
2. 获取某一个Constructor 对象,需要参数列表----
   public cn.enjoyedu.refle.more.Person(java.lang.String,int)
3. 调用构造器的 newInstance() 方法创建对象-----
   Jin

测试Field

    public void testField() throws Exception{
        String className = "cn.enjoyedu.refle.more.Person";
        Class clazz = Class.forName(className);

        System.out.println("获取公用和私有的所有字段,但不能获取父类字段");
        Field[] fields = clazz.getDeclaredFields();
        for(Field field: fields){
            System.out.print(" "+ field.getName());
        }
        System.out.println();
        System.out.println("---------------------------");


        System.out.println("获取指定字段");
        Field field = clazz.getDeclaredField("name");
        System.out.println(field.getName());

        Person person = new Person("ABC",12);
        System.out.println("获取指定字段的值");
        Object val = field.get(person);
        System.out.println(field.getName()+"="+val);

        System.out.println("设置指定对象指定字段的值");
        field.set(person,"DEF");
        System.out.println(field.getName()+"="+person.getName());

        System.out.println("字段是私有的,不管是读值还是写值," +
                "都必须先调用setAccessible(true)方法");
        //     比如Person类中,字段name字段是非私有的,age是私有的
        field = clazz.getDeclaredField("age");
        field.setAccessible(true);
        System.out.println(field.get(person));
    }
获取公用和私有的所有字段,但不能获取父类字段

name age
---------------------------

获取指定字段
name
获取指定字段的值
name=ABC
设置指定对象指定字段的值
name=DEF
字段是私有的,不管是读值还是写值,都必须先调用setAccessible(true)方法
12

Process finished with exit code 0

测试Method

    public void testMethod() throws Exception{
        Class clazz = Class.forName("cn.enjoyedu.refle.more.Person");

        System.out.println("获取clazz对应类中的所有方法," +
                "不能获取private方法,且获取从父类继承来的所有方法");
        Method[] methods = clazz.getMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");

        System.out.println("获取所有方法,包括私有方法," +
                "所有声明的方法,都可以获取到,且只获取当前类的方法");
        methods = clazz.getDeclaredMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");

        System.out.println("获取指定的方法," +
                "需要参数名称和参数列表,无参则不需要写");
        //  方法public void setName(String name) {  }
        Method method = clazz.getDeclaredMethod("setName", String.class);
        System.out.println(method);
        System.out.println("---");

        //  方法public void setAge(int age) {  }
        /* 这样写是获取不到的,如果方法的参数类型是int型
        如果方法用于反射,那么要么int类型写成Integer: public void setAge(Integer age) {  }
        要么获取方法的参数写成int.class*/
        method = clazz.getDeclaredMethod("setAge", int.class);
        System.out.println(method);
        System.out.println("---------------------------");


        System.out.println("执行方法,第一个参数表示执行哪个对象的方法" +
                ",剩下的参数是执行方法时需要传入的参数");
        Object obje = clazz.newInstance();
        method.invoke(obje,18);

        /*私有方法的执行,必须在调用invoke之前加上一句method.setAccessible(true);*/
        method = clazz.getDeclaredMethod("privateMethod");
        System.out.println(method);
        System.out.println("---------------------------");
        System.out.println("执行私有方法");
        method.setAccessible(true);
        method.invoke(obje);
    }
获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法

 getName() setName() getAge() setAge() wait() wait() wait() equals() toString() hashCode() getClass() notify() notifyAll()
---------------------------

获取所有方法,包括私有方法,所有声明的方法,都可以获取到,且只获取当前类的方法

 getName() setName() privateMethod() getAge() setAge()
---------------------------

获取指定的方法,需要参数名称和参数列表,无参则不需要写

public void cn.enjoyedu.refle.more.Person.setName(java.lang.String)
---

public void cn.enjoyedu.refle.more.Person.setAge(int)
---------------------------

执行方法,第一个参数表示执行哪个对象的方法,剩下的参数是执行方法时需要传入的参数
this is setAge()!

private void cn.enjoyedu.refle.more.Person.privateMethod()
---------------------------

执行私有方法
this is private method!

测试Method

    public void testMethod() throws Exception{
        Class clazz = Class.forName("cn.enjoyedu.refle.more.Person");

        System.out.println("获取clazz对应类中的所有方法," +
                "不能获取private方法,且获取从父类继承来的所有方法");
        Method[] methods = clazz.getMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");

        System.out.println("获取所有方法,包括私有方法," +
                "所有声明的方法,都可以获取到,且只获取当前类的方法");
        methods = clazz.getDeclaredMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");

        System.out.println("获取指定的方法," +
                "需要参数名称和参数列表,无参则不需要写");
        //  方法public void setName(String name) {  }
        Method method = clazz.getDeclaredMethod("setName", String.class);
        System.out.println(method);
        System.out.println("---");

        //  方法public void setAge(int age) {  }
        /* 这样写是获取不到的,如果方法的参数类型是int型
        如果方法用于反射,那么要么int类型写成Integer: public void setAge(Integer age) {  }
        要么获取方法的参数写成int.class*/
        method = clazz.getDeclaredMethod("setAge", int.class);
        System.out.println(method);
        System.out.println("---------------------------");


        System.out.println("执行方法,第一个参数表示执行哪个对象的方法" +
                ",剩下的参数是执行方法时需要传入的参数");
        Object obje = clazz.newInstance();
        method.invoke(obje,18);

        /*私有方法的执行,必须在调用invoke之前加上一句method.setAccessible(true);*/
        method = clazz.getDeclaredMethod("privateMethod");
        System.out.println(method);
        System.out.println("---------------------------");
        System.out.println("执行私有方法");
        method.setAccessible(true);
        method.invoke(obje);
    }
获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法
 getName() setName() getAge() setAge() wait() wait() wait() equals() toString() hashCode() getClass() notify() notifyAll()
---------------------------
获取所有方法,包括私有方法,所有声明的方法,都可以获取到,且只获取当前类的方法
 getName() setName() privateMethod() getAge() setAge()
---------------------------
获取指定的方法,需要参数名称和参数列表,无参则不需要写
public void cn.enjoyedu.refle.more.Person.setName(java.lang.String)
---
public void cn.enjoyedu.refle.more.Person.setAge(int)
---------------------------
执行方法,第一个参数表示执行哪个对象的方法,剩下的参数是执行方法时需要传入的参数
this is setAge()!
private void cn.enjoyedu.refle.more.Person.privateMethod()
---------------------------
执行私有方法
this is private method!

动态代理工作流程

image.png

而最终生成代理类的class对象是defineClass0方法,但是这个方法是个native方法,所以我们不去也无法深究它,但是通过这个方法的参数我们可以明显看到它接收了上面所生成的byte数组。

而我们通过ProxyUtils,这个自己写的工具类,将这个byte数组写入文件,我们并反编译,我们将会看到

image.png

同时我们还会看到其中实现了业务接口的方法

image.png

image.png

而h则来自派生类Proxy中

image.png

这个h的实例来自哪里?就是我们在创建代理类的实例时传入的。

image.png

使用了动态代理的 Retrofit

Retrofit简单的说就是一个网络请求的适配器,它将一个基本的Java接口通过动态代理的方式翻译成一个HTTP请求,并通过OkHttp去发送请求。此外它还具有强大的可扩展性,支持各种格式转换以及RxJava。我们基于Retrofit2解析。

先定义一个名为X的java接口,当然里面有各种注解。

@FormUrlEncoded注解表示from表单,另外还有@Multipart等注解。@POST表示post请求,此外还可以使用@GET请求

image.png

然后如何使用的呢?

首先将域名传入构造一个Retrofit,然后通过retrofit中的create方法传入一个Java接口并得到一个x(当然x这个对象是经过处理了的)调用getPersonalListInfo(12)然后返回一个Call,最后这个Call调用了enqueue方法去异步请求http,这就是一个基本的Retrofit的网络请求。Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client。

image.png

我们只定义了一个接口X,并没有实现这个接口,那么它是如何工作的呢?我们看看create方法的实现。

create()方法是个泛型方法,调用它时会返回一个范型T的对象,我们这里类型是X接口,在内部实现上,很明显了使用了动态代理返回了一个X的代理类。当调用X内部方法的时候,会调用invoke方法。invoke方法内则通过内部一系列的封装最后返回一个Call对象。

image.png