跳到内容

Java反射机制与动态代理

Canyue
发布日期:
7 分钟阅读

本文仅针对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类提供了两种获取单一方法的方式

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

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

简单演示下:

Class<?> clazz =  Class.forName("entity.Person");
Method sayNameMethod = clazz.getMethod("sayName");
  1. 获取所有方法的Method对象数组

同样也有两种,

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

只能拿到public修饰的方法

 public Method[] getDeclaredMethods() throws SecurityException

注意,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);    //这就是动态执行,往下看
    }
}
@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);
    }

方法有两个参数:

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

举个例子:

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开发框架的核心原理之一

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

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

为什么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();
    }
}
-----------------------各种演唱会前期准备。。。。大明星葛歌正在唱《你太美》各种演唱会收尾工作。。。。-----------------------
各种篮球赛前期准备。。。。
运球
上篮
捡球,收尾。。。。

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

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

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

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

啊,好麻烦

动态代理举例

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

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

我们来举个例子:

这里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

方法接收三个参数:

public class ProxyUtil {
    public static Star createProxy(StarImpl star) {
        //目标类
        ClassLoader classLoader1 = Star.class.getClassLoader();
        //当前类        
        ClassLoader classLoader2 = ProxyUtil.class.getClassLoader();
    }
}

我们来看看返回值,源码注释是这么解释的: 它是一个实现指定接口(我们刚刚传入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);
......

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

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

【保姆级】手把手捋动态代理流程(JDK+Cglib超详细源码分析)_cglib动态代理-CSDN博客

InvocationHandler接口

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

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

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

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

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

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

参数有三个:

返回值:

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

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

异常:

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

参考资料:

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

源码自带的一些注释

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

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

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

Java 动态代理详解-博客园

上一篇
如何保障RabbitMQ的消息可靠性
下一篇
进程调度与死锁