理解java反射机制是什么?

因为最近项目中经常有java反射的使用,而其中的IOC、动态代理用到了反射,因此趁这个机会来总结一下关于Java反射的一些知识,复习一下。本篇基于JDK 1.8。

java反射机制是什么

反射原理

Java反射机制(Java Reflection) 是 Java 的特征之一,是Java语言中一种动态(运行时)访问、检测和修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息、调用对象的方法。简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:

  • 在运行时判断任意一个对象所属的类;

  • 在运行时构造任意一个类的对象;

  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);

  • 在运行时调用任意一个对象的方法

重点: 是运行时而不是编译时

多数情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过 new 实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射。反射则是一开始并不知道要初始化的是什么类,无法使用new来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射。

举例:

public class Dog {
    private int id;

    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }

    public static void main(String[] args) throws Exception{
        //一、正射调用过程
        Dog dog = new Dog();
        dog.setId(1);
        System.out.println("这是一个正射调用过程Dog id:" + dog.getId());
        //二、反射调用过程
        Class clz = Class.forName("com.learning.java.Dog");
        Constructor dogConstructor = clz.getConstructor();
        Object dogObj = dogConstructor.newInstance();
        //方法调用
        Method setIdMethod = clz.getMethod("setId", int.class);
        setIdMethod.invoke(dogObj, 2);
        Method getIdMethod = clz.getMethod("getId");
        System.out.println("这是一个反射调用过程Dog id:" + getIdMethod.invoke(dogObj));
    }
}

输出结果:

这是一个正射调用过程Dog id:1
这是一个反射调用过程Dog id:2

Java 语言是一种面向对象的语言,在面向对象的世界里,万事万物皆对象,那我们写的 class 类也是对象,他们都是 java.lang.Class 的对象。我们在写类的时候,并没有显式的写这个对象,我们写的类会编译成一个类,生成一个 class 文件,而编译器就把 java.lang.Class 的这个对象存放在 class 文件的末尾,里面保存了类的元数据信息,这些元数据信息都包括类的所有信息,比如它是类还是接口、集成和实现了哪些类和接口,有什么属性,有什么方法,我们在 new 一个对象的时候,可以 new 很多对象,但是这个类生成的 class 对象只能有一个(在不同的类加载器,可能有多个,这里涉及到虚拟机的知识了)。我们在实例化 Dog 这个的类对象的时候,虚拟机会去检查,在虚拟机里面,这个类有没有被加载过,如果没有,虚拟机会先加载 Dog 对应的这个 class 对象,加载完之后,才会轮到 Dog 实例化本身的对象。

获取类的java.lang.Class实例对象常见的三种方式

获取类的java.lang.Class实例对象,常见的三种方式分别为:

  • 通过 ObjectClass.class 获取,这里的 ObjectClass 指具体类,JVM 会使用 ClassLoader 类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回 java.lang.Class 对象。

  • 通过 Class.forName (“类的全局定名”)获取,全局定名为包名+类名,类会被 JVM 加载到内存中,并且会进行类的静态初始化工作,返回 java.lang.Class 对象。

  • 通过 new 通过 ObjectClass().getClass() 获取,这里的 ObjectClass 指具体类,使用了 new 进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass 方法属于顶级 Object 类中的方法,任何子类对象都可以调用,调用时返回子类的 java.lang.Class 对象。

这几种方式,最终在JVM堆区对应类的 java.lang.Class 对象都属于同一个,也就是内存地址相同,进行双等号比较结果为 true,原因是 JVM 类加载过程中使用的是同一个 ClassLoader 类加载器加载某个类,不论加载多少次,生成到堆区的 java.lang.Class 对象始终只有一个,除非自定义类加载器,破坏 JVM 的双亲委派机制,使得同一个类被不同类加载器加载,JVM 才会把它当做两个不同的 java.lang.Class 对象。

下面创建一个实体类,分别在实体类中创建类的静态代码块、动态代码块、有参构造方法、无参构造方法,方便测试几种方式的区别及内存地址是否相同

public class ObjectClass {
    private static final String staticStr = "Hi";
    private static int staticInt = 2024;
    private static Class<?> class1;
    private String id;

    static {
        System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt);
    }

    {
        System.out.println("动态代码块~");
    }
    public ObjectClass() {
        System.out.println("无参构造方法~");
    }

    public ObjectClass(String id) {
        System.out.println("有参构造方法~");
        this.id = id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("1=====================================");
        System.out.println("一、ObjectClass.class方式=========");
        Class<?> class1 = ObjectClass.class;

        System.out.println("2=====================================");

        System.out.println("二、Class.forName方式=========");
        Class class2 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");

        System.out.println("3=====================================");

        System.out.println("三、new ObjectClass().getClass方式=========");
        Class class3 = new ObjectClass().getClass();

        System.out.println("11=====================================");

        System.out.println("一、ObjectClass.class方式=========");
        Class<?> class11 = ObjectClass.class;
        System.out.println("二、Class.forName方式=========");
        Class class12 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");

        System.out.println("22=====================================");

        System.out.println("一、ObjectClass.class方式=========");
        Class<?> class21 = ObjectClass.class;
        System.out.println("三、new ObjectClass().getClass方式=========");
        Class class23 = new ObjectClass().getClass();

        System.out.println("33=====================================");

        System.out.println("二、Class.forName方式=========");
        Class class31 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");
        System.out.println("三、new ObjectClass().getClass方式=========");
        Class class33 = new ObjectClass().getClass();

        System.out.println("44=====================================");

        System.out.println("四、三种方式内存地址比较=========");
        Class<?> class41 = ObjectClass.class;
        Class class42 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");
        Class class43 = new ObjectClass().getClass();
        System.out.println("比较结果=========");
        System.out.println("ObjectClass.class和Class.forName内存地址比较是否相同:" + (class41 == class42));
        System.out.println("ObjectClass.class和new ObjectClass().getClass内存地址比较是否相同:" + (class41 == class43));
        System.out.println("Class.forName和new ObjectClass().getClass内存地址比较是否相同:" + (class42 == class43));
    }
}

输出结果:

静态代码块:staticStr=Hi,staticInt=2024
1=====================================
一、ObjectClass.class方式=========
2=====================================
二、Class.forName方式=========
3=====================================
三、new ObjectClass().getClass方式=========
动态代码块~
无参构造方法~
11=====================================
一、ObjectClass.class方式=========
二、Class.forName方式=========
22=====================================
一、ObjectClass.class方式=========
三、new ObjectClass().getClass方式=========
动态代码块~
无参构造方法~
33=====================================
二、Class.forName方式=========
三、new ObjectClass().getClass方式=========
动态代码块~
无参构造方法~
44=====================================
四、三种方式内存地址比较=========
动态代码块~
无参构造方法~
比较结果=========
ObjectClass.class和Class.forName内存地址比较是否相同:true
ObjectClass.class和new ObjectClass().getClass内存地址比较是否相同:true
Class.forName和new ObjectClass().getClass内存地址比较是否相同:true

获取构造器对象

public class Cat {
    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 Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Cat() {
    }

    //私有方法
    private void privateMethod() {
        System.out.println("我是私有方法");
    }

    public static void testConstructor() throws Exception {
        String className = "cn.learning.java.reflect.classtest.test.Cat";
        Class<Cat> clazz = (Class<Cat>) Class.forName(className);
        System.out.println("获取全部Constructor对象-----");
        Constructor<Cat>[] constructors = (Constructor<Cat>[]) clazz.getConstructors();
        for (Constructor<Cat> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("获取某一个Constructor对象 需要参数列表----");
        Constructor<Cat> constructor = clazz.getConstructor(String.class, int.class);
        System.out.println(constructor);

        System.out.println("调用Constructor的newInstance方法创建对象----");
        Cat cat1 = constructor.newInstance("小名", 18);
        System.out.println(cat1.getName());
    }

    public static void main(String[] args) throws Exception {
        testConstructor();
    }
}

执行结果:

获取全部Constructor对象-----
public cn.learning.java.reflect.classtest.test.Cat()
public cn.learning.java.reflect.classtest.test.Cat(java.lang.String,int)
获取某一个Constructor对象 需要参数列表----
public cn.learning.java.reflect.classtest.test.Cat(java.lang.String,int)
调用Constructor的newInstance方法创建对象----
小名

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

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

public static void testMethod() throws Exception {
        String className = "cn.learning.java.reflect.classtest.test.Cat";
        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);

    }

运行结果:

获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法
main()
getName()
setName()
testConstructor()
testMethod()
getAge()
setAge()
wait()
wait()
wait()
equals()
toString()
hashCode()
getClass()
notify()
notifyAll()
=====================================
获取所有方法,包括私有方法、所有声明的方法,且获取当前类方法
main()
getName()
setName()
privateMethod()
testConstructor()
testMethod()
getAge()
setAge()
=====================================
获取指定方法,和获取构造器的差不多,需要方法名称 和参数列表 无参则不写
public void cn.learning.java.reflect.classtest.test.Cat.setName(java.lang.String)
public void cn.learning.java.reflect.classtest.test.Cat.setAge(int)
=====================================
执行我们获取的方法
this is setAge
=====================================
执行私有方法======
我是私有方法

反射获取调用类可以通过 Class.forName(),反射获取类实例要通过 newInstance(),相当于 new 一个新对象,反射获取方法要通过 getMethod(),获取到类方法之后使用 invoke() 对类方法进行调用。如果是类方法为私有方法的话,则需要通过 setAccessible(true) 来修改方法的访问限制。

通过反射访问成员变量

public static void testFiled() throws Exception {
        String className = "cn.learning.java.reflect.classtest.test.Cat";
        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("获取指定字段的值");
        Cat cat = new Cat("铭儿", 18);
        //第一个参数 这个方法所在类的实例
        Object object = field.get(cat);
        System.out.println(field.getName() + "=" + object);
        System.out.println("=====================================");
        System.out.println("设置指定对象的值");
        field.set(cat, "名儿猫猫");
        System.out.println(field.getName() + "=" + cat.getName());
        //访问私有字段
        field = clazz.getDeclaredField("age");
        field.setAccessible(true);
        field.get(cat);
        field.set(cat, 20);
        System.out.println(field.getName() + "=" + cat.getAge());
    }

执行结果:

获取共有和私有的所有字段,但不能获取父类字段
name
age
=====================================
获取指定字段
name
=====================================
获取指定字段的值
name=铭儿
=====================================
设置指定对象的值
name=名儿猫猫
age=20

动态代理模式

代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。

代理模式UML类图

图片

静态代理

静态代理:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,目的是通过引入代理对象方式,来间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性,可以对对象的原有的业务进行增强。

  • 优点:可以在不修改目标对象的前提下扩展目标对象的功能。

  • 缺点:冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。违反开闭原则:扩展能力差,可维护性差。一旦接口增加方法,目标对象与代理对象都要进行修改。

  • 静态代理属于代理模式的一种代理方式,需要代理对象和目标对象实现相同的接口。

  • 静态代理的代理类是由程序员编写源码,编译后即可获取到代理类的 class 字节码文件,也就是在程序运行前就已经得到实际的代理类 class 字节码文件了。

举例:保存用户功能的静态代理实现

  • 接口类: IUserDao

public interface IUserDao {
    public void save();
}
  • 目标对象:UserDao

public class UserDao implements IUserDao{

    @Override
    public void save() {
        System.out.println("保存数据");
    }
}
  • 静态代理对象:UserDapProxy 需要实现IUserDao接口!

public class UserDaoProxy implements IUserDao{

    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    
    @Override
    public void save() {
        System.out.println("开启事务");//扩展了额外功能
        target.save();
        System.out.println("提交事务");
    }
}
  • 测试类:TestProxy

import org.junit.Test;

public class StaticUserProxy {
    @Test
    public void testStaticProxy(){
        //目标对象
        IUserDao target = new UserDao();
        //代理对象
        UserDaoProxy proxy = new UserDaoProxy(target);
        proxy.save();
    }
}
  • 输出结果

开启事务
保存数据
提交事务

动态代理

动态代理也属于代理模式的一种代理方式,不过只需要目标对象实现接口,代理对象不需要实现接口。动态代理的代理类编译后是没有 class 字节码文件的,而是在运行时利用 Java 反射机制动态的生成代理类的 class 字节码文件。动态代理被广为人知的使用场景是 Spring 中的面向切面编程(AOP)。例如,依赖注入 @Autowired 和事务注解 @Transactional 等,都是利用动态代理实现的。动态代理还可以封装一些 RPC 调用,也可以通过代理实现一个全局拦截器等。

动态代理是指代理关系在运行时确定的代理模式。需要注意,JDK 动态代理并不等价于动态代理,前者只是动态代理的实现之一,其它实现方案还有:CGLIB 动态代理、Javassist 动态代理和 ASM 动态代理等。因为代理类在编译前不存在,代理关系到运行时才能确定,因此称为动态代理。

JDK 原生动态代理

JDK 原生动态代理,主要利用了 JDK API 的 java.lang.reflect.Proxy 和 java.lang.relfect.InnvocationHandler 这两个类来实现。通过 java.lang.reflect.Proxy 代理类的 newProxyInstance方法,传递3个参数,分别是:

  • 目标对象的加载器,通过 MyClass.getClass().getClassLoader 方式获取。

  • 目标对象的实现接口类型,通过 Object.getClass().getInterfaces() 方式获取。

  • InnvocationHandler 事件处理器,通过 new 实例化对象并重写 invoke 方法方式获取。

代码实现:


interface Animal {
    void eat();
}

class Dog implements Animal {
    @Override
    public void eat() {
    System.out.println("The  dog  is  eating");
    }
}
//  JDK  代理类  
class AnimalProxy implements InvocationHandler {
    private Object target;  //  代理对象

    public Object getInstance(Object target) {
        this.target = target;
        //  取得代理对象  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    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;
    }
    public static void main(String[] args) {
        //  JDK  动态代理调用  
        AnimalProxy proxy = new AnimalProxy();
        Animal dogProxy = (Animal) proxy.getInstance(new Dog());
        dogProxy.eat();
    }
}

注意: JDK Proxy 只能代理实现接口的类(即使是 extends 继承类是不可以代理的)。

cglib动态代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。要是用 cglib 实现要添加对 cglib 的引用,如果是 maven 项目的话,直接添加以下代码:

<dependency>  
     <groupId>cglib</groupId>  
     <artifactId>cglib</artifactId>  
     <version>3.2.12</version>  
</dependency>

cglib 的具体实现,请参考以下代码:


class Panda {
    public void eat() {
        System.out.println("The  panda  is  eating");
    }
}

class CglibProxy implements MethodInterceptor {
    private Object target;  //  代理对象  

    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        //  设置父类为实例类  
        enhancer.setSuperclass(this.target.getClass());
        //  回调方法  
        enhancer.setCallback(this);
        //  创建代理对象  
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用前");
        Object result = methodProxy.invokeSuper(o, objects);  //  执行方法调用  
        System.out.println("调用后");
        return result;
    }
}
class Test{
    public static void main(String[] args) {
        //  cglib  动态代理调用  
        CglibProxy proxy = new CglibProxy();
        Panda panda = (Panda) proxy.getInstance(new Panda());
        panda.eat();
    }
}

执行结果为:

调用前 
The panda is eating 
调用后 

cglib 的调用通过实现 MethodInterceptor 接口的 intercept 方法,调用 invokeSuper 进行动态代理的。它可以直接对普通类进行动态代理,并不需要像 JDK 代理那样,需要通过接口来完成,值得一提的是 Spring 的动态代理也是通过 cglib 实现的。

注意: cglib 底层是通过子类继承被代理对象的方式实现动态代理的,因此代理类不能是最终类(final),否则就会报错 java.lang.IllegalArgumentException: Cannot subclass final class xxx。

Proxy如何生成的代理类

proxy主要api

Proxy 描述
getProxyClass(ClassLoader, Class…) : Class 获取实现目标接口的代理类 Class 对象
newProxyInstance(ClassLoader,Class<?>[],InvocationHandler) : Object 获取实现目标接口的代理对象
isProxyClass(Class<?>) : boolean 判断一个 Class 对象是否属于代理类
getInvocationHandler(Object) : InvocationHandler 获取代理对象内部的 InvocationHandler

核心源码 Proxy.java

//1、获取代理类 Class 对象
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces){
    final Class<?>[] intfs = interfaces.clone();
    ...
    1.1 获得代理类 Class 对象
    return getProxyClass0(loader, intfs);
}

//2、实例化代理类对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
    ...
    final Class<?>[] intfs = interfaces.clone();
    //2.1 获得代理类 Class对象
    Class<?> cl = getProxyClass0(loader, intfs);
    ...
    //2.2 获得代理类构造器 (接收一个 InvocationHandler 参数)
    // private static final Class<?>[] constructorParams = { InvocationHandler.class };
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    ...
    //2.3 反射创建实例
    return newInstance(cons, ih);
}

可以看到,实例化代理对象也需要先通过 getProxyClass0(…) 获取代理类 Class 对象,而newProxyInstance(…) 随后会获取参数为 InvocationHandler 的构造函数实例化一个代理类对象。

我们先看下代理类 Class 对象是如何获取的:

Proxy.java

//-> 1.1、2.1 获得代理类 Class对象
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
    ...
    //从缓存中获取代理类,如果缓存未命中,则通过ProxyClassFactory生成代理类
    return proxyClassCache.get(loader, interfaces);
}

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{

    //3.1 代理类命名前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    //3.2 代理类命名后缀,从 0 递增(原子 Long)
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        //3.3 参数校验
        for (Class<?> intf : interfaces) {
            // 验证参数 interfaces 和 ClassLoder 中加载的是同一个类
            // 验证参数 interfaces 是接口类型
            // 验证参数 interfaces 中没有重复项
            // 否则抛出 IllegalArgumentException
        }
        // 验证所有non-public接口来自同一个包

        //3.4(一般地)代理类包名
        String proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";

        //3.5 代理类的全限定名
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        //3.6 生成字节码数据
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

        //3.7 从字节码生成 Class 对象
        return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); 
    }
}

//-> 3.6 生成字节码数据
public static byte[] generateProxyClass(final String var0, Class[] var1) {
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);
    ...
    final byte[] var3 = var2.generateClassFile();
    return var3;
}

ProxyGenerator.java

private byte[] generateClassFile() {
    //3.6.1 只代理Object的hashCode、equals和toString
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);

    //3.6.2 代理接口的每个方法
    ...
    for(var1 = 0; var1 < this.interfaces.length; ++var1) {
        ...
    }
    
    //3.6.3 添加带有 InvocationHandler 参数的构造器
    this.methods.add(this.generateConstructor());
    var7 = this.proxyMethods.values().iterator();
    while(var7.hasNext()) {
        ...
        //3.6.4 在每个代理的方法中调用InvocationHandler#invoke()
    }

    //3.6.5 输出字节流
    ByteArrayOutputStream var9 = new ByteArrayOutputStream();
    DataOutputStream var10 = new DataOutputStream(var9);
    ...
    return var9.toByteArray();
}

以上代码已经非常简化了,主要关注核心流程:JDK 动态代理生成的代理类命名为 com.sun.proxy从开始的数字(例如:从开始的数字(例如:Proxy0),这个类继承自 java.lang.reflect.Proxy。其内部还有一个参数为 InvocationHandler 的构造器,对于代理接口的方法调用都会分发到 InvocationHandler#invoke()。 可以看到,ProxyGenerator#generateProxyClass() 其实是一个静态 public 方法,所以我们直接调用,并将代理类 Class 的字节流写入磁盘文件,使用 IntelliJ IDEA 的反编译功能查看源代码。

输出Animal字节码:

...
byte[] classFile = ProxyGenerator.generateProxyClass("$proxy0", new Class[]{Animal.class});
// 直接写入项目路径下,方便使用IntelliJ IDEA的反编译功能
String path = "/Users/*/src/proxy/Animal.class";
try(FileOutputStream fos = new FileOutputStream(path))
{
    fos.write(classFile);
    fos.flush();
    System.out.println("success");
} catch(Exception e)
{
    e.printStackTrace();
    System.out.println("fail");
}
...

反编译结果:

public final class $proxy0 extends Proxy implements Animal {
    //反射的元数据Method存储起来,避免重复创建
    private static Method m1;
    private static Method m2;
    private static Method m3;
    ...
    private static Method m0;

    public $proxy0(InvocationHandler var1) throws ... {
        super(var1);
    }

    /**
     * Object#hashCode()
     * Object#equals(Object)
     * Object#toString()
     */

    // 实现了Animal接口
    public final String eat() throws ... {
        try {
            //转发到Invocation#invoke()
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            //Object#hashCode()
            //Object#equals(Object)
            //Object#toString()
            ...
            m3 = Class.forName("Animal").getMethod("eat");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

查看反编译后的代码,总结一下

  1. AnimalProxy 实现了 InvocationHandler。

  2. 调用 AnimalProxy getProxyInstance 的时候将业务接口的 Class 信息传给 Proxy.newProxyInstance()。

  3. newProxyInstance 利用反射生成一个 $Proxy+number 的一个类。

  4. newProxyInstance,生成一个代理类的实例 将 InvocationHandler,也就是 AnimalProxy 传进去。

  5. 调用这个代理类的实例的 eat 方法 ,也就调用了 InvocationHandler,也就是 AnimalProxy 的 invoke 方法,完成了代理的对象方法的增强。

原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/78243.html

Like (0)
guozi的头像guozi
Previous 2024年5月29日
Next 2024年5月29日

相关推荐

  • cdn龙头企业,cdn企业排名

    作为一名SEO工程师,我深知在当今数字化时代,CDN(内容分发网络)在网站运营中扮演着至关重要的角色。本文将探讨CDN行业的龙头企业,并重点介绍其中一家领先的CDN服务提供商——速…

    2024年5月11日
    0
  • 什么是语义分析引擎WAF

    语义分析引擎WAF 当下,Web 应用防火墙大多采用规则匹配方式来识别和阻断攻击流量,但由于 Web 攻击成本低、方式复杂多样、高危漏洞不定期爆发等原因,管理者们在安全运维工作中不…

    2024年5月16日
    0
  • ping命令的一些常用选项及其示例

    ping命令的一些常用选项及其示例,这些知识对于进行网络故障排查和日常网络管理至关重要: 基础用法 不带任何参数:默认情况下,ping会尝试连续发送四个ICMP回显请求到指定的目标…

    2024年5月17日
    0
  • 国内cdn加速类型有哪些?

    国内 CDN 加速类型主要包括以下几种:   元素加速:仅对站点的元素进行 CDN 加速。指的是页面上所包含的 JPG、Gif、CSS 等静态内容元素,则通过就近 CDN…

    CDN资讯 2024年5月16日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注