背景

反射在Java中十分重要,是Java区别于其余编程语言的一大个性。Java中的AOP切面、动静代理等看起来像黑魔法一样的技术,就离不开反射、字节码等。这些技术能在不侵入原有代码的状况下,做一些加强的非功能性需要。多提一句,千万不要把业务逻辑放在AOP切面、动静代理里,否则前人相对会骂。

  • AOP切面:在办法执行前后减少逻辑,可决定办法如何执行、甚至不执行。
  • 动静代理:在运行时生成指标类的代理类,可加强指标类的性能。

本文总结一下反射的原理和理论用法。后续有空再介绍AOP切面、动静代理。

什么是反射?

看一下官网的原文定义:

Reflection is a feature in the Java programming language. It allows an executing Java program to examine or "introspect" upon itself, and manipulate internal properties of the program. For example, it's possible for a Java class to obtain the names of all its members and display them.

翻译过去就是:反射是指一个运行中的Java程序能够查看、批改本人外部的属性,也可称之为自省。反射是Java有别于其它编程语言的一大个性。
从reflection的字面意思看,就是倒影、反射,就好比你照镜子,通过倒影就能晓得本人长什么样,理一理头发就能扭转发型。

反射的原理

一句话详情就是:JVM会动静加载Class,一个Class实例蕴含了该类的所有残缺信息,如:包名、类名、各个字段、各个办法、父类、实现的接口等。
因而,如果获取了某个类或对象的Class实例,就能够通过它获取到对应类的所有信息。

动静加载是指,JVM在第一次读取到一种Class类型时,才将其加载进内存,而不是一启动就加载所有类的信息。
每加载一品种,JVM就为其创立一个Class类型的实例,并关联起来。也即一个类的不同对象实例,背地对应的是同一个Class实例。

具体介绍可参考:https://www.liaoxuefeng.com/w...

怎么应用反射?

须要纯熟应用反射中常见的几个类:Class、Field、Method、Constructor。其它还有参数Parameter类等。能够这么了解,但凡Java对象中呈现的货色都能在java.reflect包中找到对应的类。

Class

通过3种形式获取:

  • 类.class:类的class动态变量
  • 对象.getClass()
  • Class.forName("类的全路径名")

Field

  • Class实例.getField(name):依据字段名获取某个public的Field(包含父类)
  • Class实例.getDeclaredField(name):依据字段名获取该类申明的某个Field(不包含父类)。罕用
  • Field[] getFields():获取所有的public字段,包含父类的字段。不罕用,因为依照Java标准,个别都会定义private字段,而后通过public的getter、setter办法来获取字段值
  • Field[] getDeclaredFields():获取该类申明所有的字段,不包含父类的字段。罕用。

Method

  • Class实例.getMethod(name, Class...):获取某个public的Method(包含父类)
  • Class实例.getDeclaredMethod(name, Class...):获取该类的某个Method(不包含父类)
  • Method[] getMethods():获取所有public的Method(包含父类)。罕用
  • Method[] getDeclaredMethods():获取以后类的所有Method(不包含父类)

AnnotatedElement

Class、Field、Method都是AnnotatedElement的子类,有这些罕用办法:

  • getAnnotation(Class):依据Class获取对应的注解
  • isAnnotationPresent(Class):判断是否被某个注解润饰,等效于getAnnotation(annotationClass) != null,是一种简便的写法
  • getAnnotations():获取所有润饰的注解

反射用法举例

1、反射获取类的所有字段,不便后续运行时读写

如果一个类没有实现toString()办法,或者某些private字段没有提供getter办法,那么想要遍历所有字段,就只能通过反射来实现了。获取到所有反射字段后,想遍历或者批改就很容易了。

  • 通过Field和对象,获取对应字段的值:field.get(object)
  • 通过Field和对象,批改对应字段的值:

    • 先设置为可拜访,这样即便是private字段,也能批改:field.setAccessible(true);
    • 反射批改字段值:field.set(Object, Object)

反射获取类的所有字段,分成2局部:

  • 一是该类的父类的字段
  • 二是该类援用的其它类的字段

getDeclaredFields办法只能获取到该类申明的字段,如果该类还继承了父类(可能有多层继承关系),那怎么获取所有这些继承而来的字段呢?所有类默认都是Object的子类,或者说Object是根上的父类。利用这一个性,能够通过getSuperclass一直往上找父类,获取父类的字段,而后直至父类为Object。同时也需理解,Object类自身没有任何字段。
于是,能够这么实现:

    // 获取类的所有字段,包含父类    private List<Field> getAllFields(Class<?> clazz) {        List<Field> result = new ArrayList<>();        Class<?> cls = clazz;        while (cls != null) { // 也能够写成 while (cls != null && cls != Object.class)            result.addAll(Arrays.asList(cls.getDeclaredFields()));            cls = cls.getSuperclass();        }        return result;    }

把某个类波及的所有字段的反射信息(包含援用的其余类),都缓存至本地。
之所以要把反射信息缓存至本地,是因为反射是一个耗时操作。如果在初始化阶段就缓存起来,在后续要用到时性能更快。
应用广度优先遍历算法获取类援用的所有反射字段。以下是带正文的代码。

/** * 获取类的所有反射字段示例(蕴含援用的类) */public class GetAllFields {    // 构建反射字段的缓存:key是全门路类名,value是该类的所有字段    public static Map<String, List<Field>> buildReflectCache(Class<?> clazz) {        Map<String, List<Field>> result = new HashMap<>();        List<Field> topLevelFields = getAllFields(clazz);        result.put(clazz.getName(), topLevelFields);        // 广度优先遍历类的字段,通过队列来实现        Queue<Field> queue = new LinkedList<>(topLevelFields);        while (!queue.isEmpty()) {            Field field = queue.poll();            // 如果是汇合或Map类型的字段,须要提取出泛型            if (Collection.class.isAssignableFrom(field.getType())                    || Map.class.isAssignableFrom(field.getType())) {                Type genericType = field.getGenericType();                if (genericType instanceof ParameterizedType) {                    ParameterizedType parameterizedType = (ParameterizedType) genericType;                    for (Type type : parameterizedType.getActualTypeArguments()) {                        Class<?> actualClass = (Class<?>) type;                        if (!isBasicClass(actualClass)) {                            List<Field> subFields = getAllFields(actualClass);                            result.putIfAbsent(actualClass.getName(), subFields);                            queue.addAll(subFields);                        }                    }                }            } else if (!isBasicClass(field.getType())) { // 只解决自定义类型,不解决Java根本类型                List<Field> subFields = getAllFields(field.getType());                result.putIfAbsent(field.getType().getName(), subFields);                queue.addAll(subFields);            }        }        return result;    }    // 获取类的所有字段,包含父类    private static List<Field> getAllFields(Class<?> clazz) {        List<Field> result = new ArrayList<>();        Class<?> cls = clazz;        while (cls != null) {            result.addAll(Arrays.asList(cls.getDeclaredFields()));            cls = cls.getSuperclass();        }        return result;    }    // 是否Java根本类型:通过加载类的class loader是否为null得悉,null为Java根本类型,否则为自定义类型    private static boolean isBasicClass(Class<?> clazz) {        return clazz != null && clazz.getClassLoader() == null;    }    public static void main(String[] args) {        List<Field> allFields = getAllFields(SomeClass.class);        System.out.println(allFields);        Map<String, List<Field>> reflectCache = buildReflectCache(SomeClass.class);        System.out.println(reflectCache);    }}

2、联合反射和Spring,初始化时找到对应bean的对应办法

须要联合Spring来获取bean:Map<String, Object> applicationContext.getBeansWithAnnotation(Class),key是bean名称,value是bean Class实例。

假如有2个注解,@PrintInfoClass润饰在类上,@PrintInfoMethod润饰在办法上,代表要打印办法的签名。

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.Type})@Documentedpublic @interface PrintInfoClass {}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})@Documentedpublic @interface PrintInfoMethod {}
@Componentpublic class PrintInfo implements ApplicationContextAware {    @PostConstruct    public void init() {        // 通过Spring框架,不便地获取带PrintInfoClass注解的bean        Map<String, Object> beansWithAnnotationMap = applicationContext.getBeansWithAnnotation((PrintInfoClass.class);        for (Map.Entry<String, Object> entry : beansWithAnnotationMap.entrySet()) {            Object bean = entry.getValue();            for (Method method : bean.getMethods()) {                // 只获取带PrintInfoMethod注解的办法                if (method.isAnnotationPresent(PrintInfoMethod.class) {                    // 打印办法签名                    StringBuilder paramString = new StringBuilder();                    Class<?>[] paramClassList = method.getParameterTypes();                    for (int i = 0; i < paramClassList.length; ++i) {                        Class<?> paramClass = paramClassList[i];                        paramString.append(paramClass.getSimpleName());                        if (i != paramClassList.length - 1) {                            paramString.append(",");                        }                    }                    System.out.print(Modifier.toString(method.getModifiers()) + " " + method.getReturnType().getSimpleName()                      + " " + method.getName() + "(" + paramString.toString() + ")\n");                }            }        }    }    @Autowired    private ApplicationContext applicationContext;    public void setApplicationContext(ApplicationContext applicationContext) {        this.applicationContext = applicationContext;    }}