残月的技术日志

残月的技术日志

Java反射机制与动态代理

2024-07-18

本文仅针对Java1.8,新版本我没深入研究

反射机制

Java的反射机制,指的是可以动态的获取运行中的Java程序员的类、对象的信息,并可以动态的调用对象的方法的一种机制。属于Java动态性之一。

在程序运行过程中,允许获取任意一个类本身以及其所有的成员属性与成员方法,并对任意对象都能调用其任意方法。

反射: 能够通过一个已经创建的对象,追溯到这个对象的“出身”

动态语言: 程序在执行过程中,可以改变其结构。例如新增、删除方法等。

反射的概念与过程

我们都知道,在Java中,一切皆为类

在Java程序运行前,JVM会使用ClassLoader加载字节码文件到内存中,此时会为类创建Class对象

故在Java程序运行的过程中,我们开发者所定义的类,都有一个与之对应的java.lang.Class类的对象,对象中封装了类的结构信息

Class对象由JVM进行管理,开发者无需主动声明

这里要捋清楚实例化对象与Class对象的区别

实例化对象:

这更偏向于现实中的实例,有类创建,每个实例的信息相互独立

通常需要开发者显式的创建,每个实例有自己的内存空间

Class对象:

不需要开发者显式的创建,JVM会在类被加载时自动创建Class对象

里面封装的是这个类的结构信息

一个Class对象在一个JVM中只存在一个,且被所有的实例化对象共享

而Class对象,就是反射的核心,接下来的操作都依赖它

如何拿到Class类的对象

获取Class对象的方法有三种:

  1. 提供调用某个实例化对象的getClass()方法获得实例化对象来源类的Class对象

Person xiaoHua = new Person("小花", 22);
Class clazz = xiaoHua.getClass();
  1. 调用类的class属性获取其在运行时的Class对象

Class<Person> clazz = Person.class;
  1. 通过Class类的forName方法,基于全限定名获取类在运行时的Class对象

相对而言,这种方法更加安全性能也更好

我们先看看Class类中部分源码,如下:

/**
 * .......
 * @param name       fully qualified name of the desired class
 * @param initialize if {@code true} the class will be initialized.
 *                   See Section 12.4 of <em>The Java Language Specification</em>.
 * @param loader     class loader from which the class must be loaded
 * @return           class object representing the desired class
 * .......
**/
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException{
        Class<?> caller = null;
        //这里其实会调用SecurityManager进行权限检查
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            caller = Reflection.getCallerClass();
            if (sun.misc.VM.isSystemDomainLoader(loader)) {
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(name, initialize, loader, caller);
}

/** Called after security check for system loader access checks have been made. */
    private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller)
        throws ClassNotFoundException;


/**
     * Returns the {@code Class} object associated with the class or
     * interface with the given string name.  Invoking this method is
     * equivalent to:
     *
     * <blockquote>
     *  {@code Class.forName(className, true, currentLoader)}
     * </blockquote>
     * ......
**/
@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

可以发现,Class提供了两种实现

  1. 默认, 我们可以只需传入全限定名(包名+类名)

  2. 高级,我们可以再额外传入initialize(是否初始化)和loader(用于加载类的ClassLoader对象)

当initialize为true时,当类被加载时,会初始化类中定义的静态成员和方法

若反射操作无需使用静态成员/方法,可考虑选择false ,好像能提升一点点性能,具体影响待研究

简单举个例子:

import java.lang.reflect.InvocationTargetException;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> clazz = Class.forName("entity.Person");
        //拿到我(Main类)的类加载器,也就是,谁调用的我,就由谁加载Person
        ClassLoader classLoader = Main.class.getClassLoader();
        Class<?> clazz2 = Class.forName("entity.Person",false,classLoader);
    }
}

小试牛刀

让我们基于Class对象,看看能不能拿到类的信息

有个Person类,如下

package entity;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name,int age) {
        this.age = age;
        this.name = name;
    }

    public void sayName(){
        System.out.println("我叫"+name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

我们看看通过反射,能拿到什么信息

import entity.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        //通过new关键字实例化对象
        Person xiaoMing = new Person("小明", 22);
        //通过反射,得到他的"出生"
        Class<? extends Person> clazz = xiaoMing.getClass();
        System.out.println("类名 : " + clazz.getName());
        System.out.println("类的所有方法:");
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        System.out.println("类的所有成员:");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field.getName());
        }
        System.out.println("类的所有构造方法:");
        Constructor[] declaredMethods = clazz.getDeclaredConstructors();
        for (Constructor declaredMethod : declaredMethods) {
            System.out.println(declaredMethod.getName());
        }
    }
}
类名 : entity.Person
类的所有方法:
setAge
getAge
getName
setName
类的所有成员:
name
age
类的所有构造方法:
entity.Person
entity.Person

利用好反射机制,你可以实现以下功能

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

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

  • 在运行时判断任意一个类所具有的成员变量和方法

  • 在运行时获取范型信息

  • 在运行时调用任意一个对象的成员变量和方法

  • 在运行时处理注解

  • 实现动态代理

反射机制在很多场景下都非常有用,比如在框架开发中,利用好反射机制能辅助开发者更加通用和灵活的编写代码。

反射API

Java提供了许多用于在运行时动态的生成类、接口或对象信息的API,常用的API如下:

API

描述

Class类

获取类的属性、方法信息

Field类

表类的成员变量,可获取成员的属性

Method类

表类的方法,可获取方法的信息以及用于执行方法

Constructor类

表类的构造方法

其实在前面的小实验就已经简单演示了通过API的调用,获取类xiaoMing对象的类的信息

通过Class对象实例化类

既然我们可以通过Class对象得到类的信息,那我们也可以利用他对类进行实例化

  1. 通过Class对象的newInstance()方法创建实例

本方法要求类中定义有无参构造方法

try {
    Person person = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {一个是调用那个实例化对象的方法
    throw new RuntimeException(e);
}
  1. 通过Class对象拿到类的Constructor对象,再通过Constructor对象的newInstance()方法创建实例

本方法可以指定使用哪一个构造函数

try {
    //这里要传入构造方法的参数类型
    Constructor<? extends Person> constructor = clazz.getConstructor(String.class, int.class);
    Person xiaoZhang = constructor.newInstance("小张", 21);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
    throw new RuntimeException(e);
}

通过Method对象动态执行方法

Method对象封装了一个类或接口中某个方法的信息

Method对象是java.lang.reflect.Method 的实现

我们可以通过以下两种方式,获取一个Class对象对于类中的方法

  1. 获取单一方法的Method对象

Class类提供了两种获取单一方法的方式

  • getDeclaredMethod()方法主要是可以通过方法名与参数,获取当前类的特定方法的Method对象

包括公共、保护、默认(包)、私有方法

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
  • getMethod()方法与前者类似,唯一不同的是,可以获取到继承自来自父类的方法

public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

简单演示下:

Class<?> clazz =  Class.forName("entity.Person");
Method sayNameMethod = clazz.getMethod("sayName");

  1. 获取所有方法的Method对象数组

同样也有两种,

  • getDeclaredMethods()方法,可以获取当前类的所有public方法,并返回Method对象组成的数组

只能得到当前类的方法,继承自父类的拿不到

只能拿到public修饰的方法

 public Method[] getDeclaredMethods() throws SecurityException
  • getMethods()与前者类似,但不仅仅可以拿到公共的方法

注意,getMethods()可以拿到公共、保护、默认(包)、私有方法

依旧拿不到继承至父类的方法

Returns an array containing Method objects reflecting all the declared methods of the class or interface represented by this Class object, including public, protected, default (package) access, and private methods, but excluding inherited methods.

public Method[] getDeclaredMethods() throws SecurityException

简单演示下怎么用:

for (Method declaredMethod : clazz.getDeclaredMethods()) {
    if (declaredMethod.getParameterTypes().length == 0) {
        declaredMethod.invoke(liHua);    //这就是动态执行,往下看
    }
}
  • 我们可以调用Method对象的incoke()方法,实现动态执行

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

方法有两个参数:

  • obj:调用方法的对象,如果基础方法是静态的,则可以传个null

  • args: 参数列表,按顺序传入,若方法无参,可以传null或直接不传参

方法的返回值也就是在动态执行后,被调用的方法返回的值

举个例子:

import entity.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> clazz =  Class.forName("entity.Person");
        Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
        Person liHua = (Person) constructor.newInstance("李华", 1);
        try {
            Method sayNameMethod = clazz.getMethod("sayName");
            //两个参数,第一个是由谁去调用方法,另一个是不定长的参数列表,这里sayName无参我们就留空
            sayNameMethod.invoke(liHua);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}
我叫李华

再举一个静态方法:

package utils;
import java.util.UUID;
public class UserUtil {
    /**
     * 获得一个随机生成的UUID
     * @return UUID
     */
    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class clazz = Class.forName("utils.UserUtil");
        Method getUUID = clazz.getDeclaredMethod("getUUID");
        String uuid = (String) getUUID.invoke(null);    //这里直接传null就行
        System.out.println(uuid);
    }
}
53494680ffaa4fe7bfb91dd39ca493f3

利用反射机制时要注意

  • 反射会带来而外的开销,若不需要动态的创建一个对象,不建议使用反射

  • 反射会忽略权限检查,可能会破坏封装性

动态代理

动态代理,是目前几乎所有Java开发框架的核心原理之一

动态代理是一种在运行时创建代理对象并拦截方法调用的机制。它允许你在不修改目标对象代码的情况下,通过代理对象来增强或控制对目标对象的访问

在代理设计模式中,有三个角色,我们来看这张来自《大话设计模式》的图:

  • Subject(抽象主题): 通常是一个接口,这里定义的是对外访问的公共方法,RealSubject和Proxy都要实现这个接口

  • RealSubject(真实主题/委托者):被代理的对象,实现真正的业务逻辑

  • Proxy(代理者):代理委托者方法的对象

为什么RealSubject与Proxy都要实现Subject

因为,作为代理,你的职责就是要能替代委托者去相应第三方的请求,让第三方就像和委托者本人交互一样。并调用委托者实现真正的业务逻辑,在调用委托者方法前后,通常代理会而外添加自己的逻辑

也就是说,代理实现所有委托者需要被代理的方法

而这些方法(预期第三方的行为),会被定义在Subject(抽象主题)中,这也就是代理设计模式的一个目标,也就是所谓透明性

代理者与委托者之间的耦合度低

对委托者(也就是真实主题)中业务逻辑的修改,代理也只需要实现新的要被代理的接口就行,无需考虑真实的业务逻辑的变更。

好像有种叫CGLIB,待研究,本文暂不考虑。

静态代理与动态代理

代理的实现可分为静态和动态代理两种,其实现目的其实是一样的

这里我参考黑马程序员-磊哥的案例,进行一些修改

葛歌(Gege,委托者)是一位国际明星,他的特长有三个,分别是唱歌、跳舞、打篮球

大明星最近很苦恼,有一堆人找他去演出,葛歌本人并不擅长安排活动等等其他事宜,只想专注于他的爱好

所以他就请了一位经纪人(starProxy,代理),第三方先找代理沟通,代理安排好后,找葛歌演出

静态代理举例

静态代理是指在编译时就已经确定代理类和真实主题类的关系,代理类在运行前就实现已经被编译好

代理类必须手动实现与真实主题相同的接口,并创建真实主题类的实例。代理类的方法调用会转发给真实主题类的实例。

这里主要是为了讲动态和静态的区别,不感兴趣的可以跳

来举个例子:

首先,定义一个抽象主题

package staticProxy;

//定义预计第三方访问的方法
public interface Star {
    String sing(String singName);
    String dance(String danceName);
    String playBasketball();
}

定义我们的大明星实现类

package staticProxy;

public class StarImpl implements Star {
    private String name;

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

    @Override
    public String sing(String singName) {
        System.out.printf("大明星%s正在唱《%s》\n", this.name,singName);
        return "怎么样,唱的不错吧!!";
    }

    @Override
    public String dance(String danceName) {
        System.out.printf("大明星%s正在跳《%s》\n", this.name,danceName);
        return "怎么样,跳的不错吧!!";
    }

    @Override
    public String playBasketball() {
        System.out.println("运球");
        System.out.println("上篮");
        return "怎么样,我打的不错吧!!";
    }
}

在静态代理中,要针对每个真实主题类定义一个代理类,这里我们就一个

同样,也是实现抽象主题

package staticProxy;

public class StarProxy implements Star{
    //这是被代理的对象
    private StarImpl target;

    public StarProxy(StarImpl target) {
        this.target = target;
    }

    @Override
    public String sing(String singName) {
        System.out.println("-----------------------");
        System.out.println("各种演唱会前期准备。。。。");
        //请大明星唱
        String result = target.sing(singName);
        System.out.println("各种演唱会收尾工作。。。。");
        //转达大明星的返回值
        return result;
    }

    @Override
    public String dance(String danceName) {
        System.out.println("-----------------------");
        System.out.println("各种舞会前期准备。。。。");
        String result = target.dance(danceName);
        System.out.println("各种舞会收尾工作。。。。");
        return result;
    }

    @Override
    public String playBasketball() {
        System.out.println("-----------------------");
        System.out.println("各种篮球赛前期准备。。。。");
        String result = target.playBasketball();
        System.out.println("捡球,收尾。。。。");
        return result;
    }
}

来个第三方调用

package staticProxy;

public class Main {
    public static void main(String[] args) {
        StarImpl gege = new StarImpl("葛歌");
        StarProxy proxy = new StarProxy(gege);
        //这里,第三方和代理交互,就像个本人一样
        proxy.sing("你太美");
        proxy.playBasketball();
    }
}

-----------------------
各种演唱会前期准备。。。。
大明星葛歌正在唱《你太美》
各种演唱会收尾工作。。。。
-----------------------
各种篮球赛前期准备。。。。
运球
上篮
捡球,收尾。。。。

貌似,代理实现的很成功?吗?

要是,我的业务中,要代理的不仅仅是一个类呢!

那就要为每个委托类都创建一个代理类

而且,当需代理的接口要新增、删除、修改(例如参数)时,就需要同时修改三个地方

啊,好麻烦

动态代理举例

动态代理与本文前面的反射机制密切相关,在运行时根据需要创建代理类和真实主题类的关系。它不要求代理类和真实主题类都实现相同的接口

创建动态代理需要使用以下两个类/接口:

  • java.lang.reflect.Proxy :在这用于创建代理对象

  • java.lang.reflect.InvocationHandler : 我们要为代理创建一个处理器类,也就行代理要干什么,类要实现InvocationHandler 接口,每个代理的都对应一个InvocationHandler对象,当代理的方法被调用时,会调用InvocationHandler对象的invoke()方法

我们来举个例子:

这里Star接口与StarImpl类不变

我们实现一个StarHandle类,实现java.lang.reflect.InvocationHandler

package dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


//一个处理类,描述代理要做什么
public class StarHandle implements InvocationHandler {
    //这是被代理的对象
    private StarImpl target;

    public StarHandle(StarImpl target) {
        this.target = target;
    }

    /**
     * 这是一个回调
     * @param proxy 当前的代理对象,当成一个Object传入
     * @param method 当前调用的方法的Method对象
     * @param args 方法参数
     * @return 委托者方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sing")) {
            System.out.println("-----------------------");
            System.out.println("各种演唱会前期准备。。。。");
            Object result = method.invoke(target,args);
            System.out.println("各种演唱会收尾工作。。。。");
            //转达大明星的返回值
            return result;
        }
        if (method.getName().equals("dance")) {
            System.out.println("-----------------------");
            System.out.println("各种舞会前期准备。。。。");
            Object result = method.invoke(target,args);
            System.out.println("各种舞会收尾工作。。。。");
            return result;
        }
        if (method.getName().equals("playBasketball")) {
            System.out.println("-----------------------");
            System.out.println("各种篮球赛前期准备。。。。");
            Object result = method.invoke(target,args);
            System.out.println("捡球,收尾。。。。");
            return result;
        }

        return method.invoke(target,args);
    }
}

为方便创建代理,我们编写一个工具类

package dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyUtil {
    //静态方法,由于创建代理,返回一个Star的实现类
    public static Star createProxy(StarImpl star) {
        InvocationHandler handler = new StarHandle(star);
        return (Star) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),     //通常都是自己的类加载器
                star.getClass().getInterfaces(),      //抽象主题
                handler                               //处理器实例
        );
    }
}

跑起来看看:

package dynamicProxy;


public class Main {
    public static void main(String[] args) {
        StarImpl gege = new StarImpl("葛歌");
        Star starProxy = ProxyUtil.createProxy(gege);
        String result = starProxy.playBasketball();
        System.out.println(result);
    }
}
-----------------------
各种篮球赛前期准备。。。。
运球
上篮
捡球,收尾。。。。
怎么样,我打的不错吧!!

Proxy.newProxyInstance()方法

java.lang.reflect.Proxy,他是Java反射包下一个动态代理相关类

newProxyInstance()是Proxy类下的一个静态方法,会创建并返回一个用于指定接口的代理类实例,该代理类会将方法调用转发给指定的调用处理程序。

我们开看看他的源码:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
    throws IllegalArgumentException

方法接收三个参数:

  • ClassLoader loader :指定代理类的类加载器,也就是一个ClassLoader对象,通常我们会使用当前类或是目标类的加载器。

    • 我们来举个例子

public class ProxyUtil {
    public static Star createProxy(StarImpl star) {
        //目标类
        ClassLoader classLoader1 = Star.class.getClassLoader();
        //当前类        
        ClassLoader classLoader2 = ProxyUtil.class.getClassLoader();
    }
}
  • Class<?>[] interfaces:代理类需要实现的接口数组,代理类会实现这些接口中的所有的方法

  • InvocationHandler h:当代理被调用是,要传给哪一个InvocationHandler对象

我们来看看返回值,源码注释是这么解释的: 它是一个实现指定接口(我们刚刚传入interfaces的数组)的代理类的代理实例,并指定了该代理类的调用处理程序

a proxy instance with the specified invocation handler of a proxy class that is defined by the specified class loader and that implements the specified interfaces

返类型比较特殊,是个Object类型,这也就是代表我们要手动将它强转成interfaces中指定的接口类型

其实我们Debug一下就可以看到,其实这里的返回值就是代理类的实例

h就是我们自己实现的StarHandle的实例

而这里的cons,是由我们传入的类加载器按照interfaces 参数生成的一个一个新的代理类 的构造方法

源码部分如下

......
final Class<?>[] intfs = interfaces.clone();
......
Class<?> cl = getProxyClass0(loader, intfs);
......
final Constructor<?> cons = cl.getConstructor(constructorParams);
......

所以这就是我们要把它转为代理说实现的接口的类型原因

具体流程可以看这篇文章,写的很详细

https://blog.csdn.net/aqin1012/article/details/129179722

InvocationHandler接口

InvocationHandler接口是Java反射包中的一个重要部分,是代理类实例所调用的处理器类必须要实现的接口。

接口中就定义类一个方法,签名如下:

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

上一小节讲过,invoke()方法是一个回调,当一个代理类对象中一个方法被调用时,其实会被转发到这个接口上

所以,我们要在这定义代理如何处理方法调用

参数和返回我在这再写一遍,就不要翻回去看了

参数有三个:

  • proxy :就是被调用的代理对象本身

  • method :这里应该表示的是委托类的被调用的方法的Method对象

  • args参数数组,或者如果方法不带参数,则为null

返回值:

我们需要在invoke()方法中,调用Method对象的invoke方法,也就是要动态执行委托类中的方法

执行后,若被代理的方法有返回值,可以在invoke()方法中返回

异常:

如果invoke()方法抛出异常,这个异常将被视为代理对象方法调用时发生的异常

参考资料:

《Offer来了-Java面试题核心知识点精讲》 中国工信出版社

源码自带的一些注释

思否-深入理解Java中的反射机制和使用原理

【黑马磊哥】Java动态代理深入剖析,真正搞懂Java核心设计模式:代理设计模式-哔哩哔哩

java动态代理实现与原理详细分析-博客园

Java 动态代理详解-博客园

  • 0