反射基础

2020/1/1

# 类的加载机制

# 两次运行的Java程序为什么不能共享数据?

当我们调用Java命令运行某个Java程序时,该命令将会启动一条Java虚拟机进程,不管该Java程序有多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。

同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。

两次运行Java程序处于两个不同的JVM进程中,两个JVM之间并不会共享数据。

# 类加载机制

当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。

# 类加载

类加载指的是将类的class文件读入内存,并为之创建一一个 java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立-一个java.lang.Class对象

类是某一类对象的抽象,是概念层次的东西。

# 类的连接

当类被加载之后,系统为之生成一-个对应的Class对象,接着将会进入连接阶段,连接阶段将会负责把类的二进制数据合并到JRE中。类连接又可分为如下三个阶段: 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一-致。 准备:类准备阶段则负责为类的静态属性分配内存,并设置默认初始值。. 解析:将类的二进制数据中的符号引用替换成直接引用。

# 类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态属性进行初始化。

在Java类中对静态属性指定初始值有两种方式:

(1)声明静态属性时指定初始值;

(2)使用静态初始化块为静态属性指定初始值。

# 类的初始化时机(什么时候会进行类的初始化)?

当Java程序首次通过下面6种方式来使用某个类或接口时,系统就会初始化该类或接口: ➢创建类的实例。为某个类创建实例的方式包括使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。 ➢调用某个类的静态方法。访问某个类或接 口的静态属性,或为该静态属性赋值。使用反射方式来强制创建某个类或接口对应的ja.lang.Class对象。例如代码:Class.forName("Person"),如果系统还未初始化Person类,则这行代码将会导致该Person类被初始化,并返回Person对应的java.lang.Class对象。 ➢初始化某个类的子类, 当初始化某个类的子类时,该子类的所有父类都会被初始化。直接使用 java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化该主类。

除此之外,下面有几种情形需要特别指出: 对于一个final型的静态属性,如果该属性可以在编译时就得到属性值,则可认为该属性可被当成编译时常量。当程序使用编译时常量时,系统会认为这是对该类的被动使用,所以不会导致该类的初始化。

# 使用final修饰符修饰的成员变量,在类调用时,是否初始化此类?

当某个静态属性使用final修饰,而且它的值可以在编译时得到,那么程序其他地方使用该静态属性时,实际上并不会使用该静态属性,而是相当于使用常量。

例如:

static final String str = "Java学习";
1

str的值在编译器就确定是"Java学习",所以使用该静态属性,不会初始化此类。

反之,如果final类型的静态属性的值不能在编译时得到,必须等到运行时才可以确定该属性的值**,如果通过该类来访问该静态属性,则可以认为是主动访问使用该类,将会导致该类被初始化。

例如:

static final String str = "现在的系统时间是:" + System.currentTimeMillis();
1

str的值在编译期无法确定,因为要在运行时获取当前系统时间,所以使用该静态属性,会初始化此类。

注意:

当使用ClassLoader类loadClass()方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。当使用ClassforName()静态方法才会导致强制初始化该类

# 图解类加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。

image-20200618172433032

# 类加载器

类装载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class 实例。

当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构: ➢Bootstrap Classl oader:根类(引导或原始)加载器。它负责加载Java的核心类。根类加载器非常特殊,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。 ➢Extension ClassL oader:扩展类加载器。它负贵加载JRE的扩展月录(JAVA_ HOME/jre/lib/ext或者山java.ext.dirs 系统属性指定的几录)中JAR的类包。通过这种方式,我们就可以为Java护展核心类以外的新功能,只要我们把自己开发的类打包成JAR文件,然后放入JAVA_ HOME/jre/lib/ext 路径即可。 ➢System ClassLoader:系统类加载器。它负责在JVM启动时,加载来自命令java中的Classpath选项或java.class path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。

程序可以通过ClassLoader静 态方法getSystemClassLoader()获取 该类加载器。如果没有特别指定,则用户自定义的类加载器都以该类加载器作为它的父加载器。

# 思考:一个类被载入JVM中,同一个类就不会被再次载入了。怎么样才算“同一个类”?

在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。

# 类加载器的加载机制

JVM的类加载机制主要有如下三种机制:

  1. 全盘负责:所谓全盘负责,就是说当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
  2. 父类委托:所谓父类委托则是先让parent (父)类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
  3. 缓存机制:缓存机制将会保证所有被加载过的Class 都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在该Class对象时,系统才会重读取该类对应的二进制数据,并将其转换成Class对象,并存入cache。这就是为什么我们修改了Class 后,程序必须重新启动JVM,程序所作的修改才会生效的原因。

# 思考:为什么我们修改了Class 后,程序必须重新启动JVM,程序所作的修改才会生效?

缓存机制将会保证所有被加载过的Class 都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在该Class对象时,系统才会重读取该类对应的二进制数据,并将其转换成Class对象,并存入cache。

#

# 类加载器的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

# 自定义类加载器

JVM中除根加载器之外的所有炎加载器都是ClassLoader 子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。

如何实现,暂略。

# 反射(Reflect)

# 什么是反射?如何理解反射?

反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。

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

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

# 图示正常方式与反射方式的区别

image-20200618201509002

# 如何看待动态语言和静态语言?

# 动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

# 静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。

Java的动态性让编程的时候更加灵活!

# 反射的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时查看一个类的信息(成员变量、方法、注解、实现的接口、构造器、泛型、修饰符、继承关系、内部类等)
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理

# 理解Class类并获取Class的实例

# Class类简介

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在JVM 中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class 实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

# 获取Class的方式

  1. 调用某个类的class属性来获取该类对应的Class对象。例如Person.class将会返回Person类对应的Class对象
  2. 使用Class类forName()静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。
  3. 调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所以所有Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。
  4. 使用类的加载器:ClassLoader

# 推荐使用第一种方式(直接调用类的class属性)获取Class对象

优势如下:

  1. 代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。
  2. 程序性能更高,因为这种方式无须调用方法,所以性能更好。

但如果我们只有一个字符串, 例如“java.lang. String”,如果需要获取该字符串对应的Class对象,则只能使用第二种方 式了,使用Class的forName方法获取Class对象时,该方法可能抛出一个ClasNotFoundException异常

# 代码测试获取Class对象

MyAnnotation注解Person接口Producer类为通用的,后续还会使用

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "Annotation测试";

}
1
2
3
4
5
6
7
/**
 * 测试反射的接口
 */
public interface Person {
    //吃饭
    void eat(String food);
}
1
2
3
4
5
6
7
/**
 * 功能描述:测试反射的JavaBean
 * 生产商类
 *
 * @author RenShiWei
 * Date: 2020/6/19 9:16
 **/
@MyAnnotation("反射获取类注解信息")
public class Producer implements Person {

    @MyAnnotation("生产商姓名")
    private String name;

    int age;

    public int id;
    
    //生产的商品map
    public Map<String,String> productMap=new HashMap<>();

    public Producer () {
    }

    private Producer ( String name, int age, int id ) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    //get和set方法
    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;
    }

    public int getId () {
        return id;
    }

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

    //生产产品
    @MyAnnotation
    public String produce ( String product ) {
        return "正在生产产品:" + product;
    }

    //查看产品
    void showProducts () {
        System.out.println("正在查看产品");
    }

    //实现接口的方法
    @Override
    public void eat ( String food ) {
        System.out.println("正在吃:" + food);
    }

    //内部类
    class Inner{}
    
    @Override
    public String toString () {
        return "Producer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

测试获取Class对象

public static void main ( String[] args ) {
        System.out.println("**************测试获取Class的对象******************");
        System.out.println("----方式一:调用类的class属性获取----");
        Class<Producer> clazz1 = Producer.class;
        System.out.println(clazz1);

        System.out.println("---方式二:通过Class类的静态方法forName()获取----");
        Class<?> clazz2=null;
        try {
            clazz2 = Class.forName("reflectiontest.Producer");
            System.out.println(clazz2);
        } catch (ClassNotFoundException e) {
            System.out.println("没有通过全限类名找到此类,不能使用反射获取Class对象");
            e.printStackTrace();
        }

        System.out.println("---方式三:调用对象的getClass()方法(此方法为java.lang.Object的方法)获取---");
        Producer producer=new Producer();
        Class<? extends Producer> clazz3 = producer.getClass();
        System.out.println(clazz3);

        System.out.println("---方式四:通过类加载器ClassLoader获取---");
        ClassLoader classLoader=ReflectionTest.class.getClassLoader();
        Class<?> clazz4=null;
        try {
            clazz4 = classLoader.loadClass("reflectiontest.Producer");
            System.out.println(clazz4);
        } catch (ClassNotFoundException e) {
            System.out.println("没有通过全限类名找到此类,不能使用反射获取Class对象");
            e.printStackTrace();
        }
        System.out.println("对比四种方式获取的Class对象是否是同一个");
        System.out.println(clazz1==clazz2);
        System.out.println(clazz1==clazz3);
        System.out.println(clazz1==clazz4);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

结果:

**************测试获取Class的对象******************
----方式一:调用类的class属性获取----
class reflectiontest.Producer
---方式二:通过Class类的静态方法forName()获取----
class reflectiontest.Producer
---方式三:调用对象的getClass()方法(此方法为java.lang.Object的方法)获取---
class reflectiontest.Producer
---方式四:通过类加载器ClassLoader获取---
class reflectiontest.Producer
对比四种方式获取的Class对象是否是同一个
true
true
true
1
2
3
4
5
6
7
8
9
10
11
12
13

# 如何从Class中获取类的信息?

# 获取构造器的4种方式

构造器 描述
Connstructor getConstructor(Class<?>... parameterTypes) 返回此Class对象所表示的类的指定的public构造器
Constructor<?>[] getConstructors() 返回此Class对象所表示的类的所有public 构造器。
Constructor getDeclaredConstructor(Class<?>... parameterTypes) 返回此Class对象所表示的类的指定构造器,与构造器的访问级别无关
Constructor<?>[] getDeclaredConstructors() 返回此Class对象所表示的类的所有构造器,与构造器的访问级别无关

# 获取方法的4种方式

方法 描述
Method getMethod(String name, Class<?>... parameterTypes) 返回此Class对象所表示的类的指定public方法
Method[] getMethods() 返回此Class对象所表示的类的所有public方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回此Class对象所表示的类的指定方法,与方法的访问级别无关
Method[] getDeclaredMethods() 返回此Class对象所表示的类的全部方法,与方法的访问级别无关

# 获取属性的4种方式

方法 描述
Field getField(String name) 返回此Class对象所表示的类的指定的public属性( Field)
Field[] getFields() 返回此Class对象所表示的类的所有public属性(Field)
Field getDeclaredField(String name) 返回此Class对象所表示的类的指定属性( Field) ,与属性的访问级别无关。
Field] getDeclaredFields() 返回此Class对象所表示的类的全部属性(Field) ,与属性的访间级别无关

# 获取注解的3种方式

方法 描述
A getAnnotation(Class annotationClass) 试图获取该Class对象所表示类上.指定类型的注释;如果该类型的注释不存在则返回null
Annotation[] getAnnotations() 返回此元素上存 在的所有注释
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注释

# 获取内部类:

方法 描述
Class<?>[ getDeclaredClasses() 返回该Class对象所对应类里包含的全部内部类

# 获取外部类

方法 描述
Class<?> getDeclaringClass() 返回该Class对象所对应类所在的外部类

# 获取继承的父类后者所实现的接口

方法 描述
Class<?>[] getInterfaces() 返回该Class对象对应类所实现的全部接口
int getModifiers() 返回此类或接口的所有修饰符
修饰符由public. protected、private、final、static、abstract等对应的常量组成
返回的整数应使用Modifier工具类的方法来解码,才可以获取真实的修饰符。
Package getPackage() 获取此类的包
String getName() 以字符串形式返回此Class 对象所表示的类的名称
String getSimpleName() 以字符串形式返回此Class 对象所表示的类的简称
Class<? super T> getSuperclass() 返回该Class 所表示的类的超类对应的Class对象

# 判断该类是否为接口、枚举、注释类型等

方法 描述
boolean isAnnotation() 返回此Class 对象是否表示一个注释类型( 由@interface定义)
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断此Class对象,上是否使用了Annotation注释修饰
boolean isAnonymousClass() 返回此Class 对象是否是一个匿 名类
boolean isArray() 返回此Class 对象是否表示一个数组类
boolean isEnum() 返回此Class 对象是否表示一个枚举(由enum关键字定义)
boolean isInterface() 返回此Class 对象是否表示一一个接口( 使用interface定义)
boolean isInstance(Object obj) 判断obj是否是此Class对象的实例,该方法可以完全代替instanceof操作符

# 代码测试获取类的信息

MyAnnotation注解Person接口Producer类还采用上文的。

public static void main ( String[] args ) throws Exception {
        Class<Producer> clazz = Producer.class;
        System.out.println("************测试获取类的信息****************");
        System.out.println("---测试获取Class对象的全部构造器---");
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        for (Constructor<?> c : constructors) {
            System.out.println(c);
        }

        System.out.println("---测试获取Class对象的全部为public的构造器---");
        Constructor<?>[] publicConstructors = clazz.getConstructors();
        for (Constructor<?> c : publicConstructors) {
            System.out.println(c);
        }

        System.out.println("---测试获取全部方法---");
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            System.out.println(m);
        }

        System.out.println("---测试获取指定的方法---");
        Method method = clazz.getMethod("produce", String.class);
        System.out.println(method);

        System.out.println("---测试获取类的全部注解---");
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation a : annotations) {
            System.out.println(a);
        }

        System.out.println("---测试获取元素的注解---\n" + Arrays.toString(clazz.getAnnotationsByType(MyAnnotation.class)));

        System.out.println("---测试获取全部的内部类---");
        Class<?>[] declaringClass = clazz.getDeclaredClasses();
        for (Class<?> inner : declaringClass) {
            System.out.println(inner);
        }

        System.out.println("---测试使用Class的forName的方法获取内部类---");
        System.out.println(Class.forName("reflectiontest.Producer$Inner"));

        //还可以获取很多信息,暂略.....
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

结果:

************测试获取类的信息****************
---测试获取Class对象的全部构造器---
public reflectiontest.Producer()
private reflectiontest.Producer(java.lang.String,int,int)
---测试获取Class对象的全部为public的构造器---
public reflectiontest.Producer()
---测试获取全部方法---
public java.lang.String reflectiontest.Producer.getName()
public int reflectiontest.Producer.getId()
public void reflectiontest.Producer.setName(java.lang.String)
public void reflectiontest.Producer.setId(int)
public void reflectiontest.Producer.setAge(int)
public java.lang.String reflectiontest.Producer.produce(java.lang.String)
void reflectiontest.Producer.showProducts()
public int reflectiontest.Producer.getAge()
public void reflectiontest.Producer.eat(java.lang.String)
---测试获取指定的方法---
public java.lang.String reflectiontest.Producer.produce(java.lang.String)
---测试获取类的全部注解---
@reflectiontest.MyAnnotation(value=反射获取类注解信息)
---测试获取元素的注解---
[@reflectiontest.MyAnnotation(value=反射获取类注解信息)]
---测试获取全部的内部类---
class reflectiontest.Producer$Inner
---测试使用Class的forName的方法获取内部类---
class reflectiontest.Producer$Inner
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 反射创建并操作运行时类的对象

Class对象可以获得该类里的成分包括方法(由Method对象表示)、构造器(由Constructor 对象表示)、Field (由Field 对象表示),这三个类都定义在java.lang.reflect 包下,并实现了java.lang.reflect.Member接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建对象,能通过Field对象直接访问并修改对象的属性值。

# 创建对象

  1. 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newlnstance()方法时实际上是利用默认构造器来创建该类的实例。
  2. 先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创 建该Class对象对应类的实例。通过这种方式可以选择使用某个类的指定构造器来创建实例。

通过第一种方式来创建对象是比较常见的情形(更加通用)。因为在很多JavaEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序就需要根据该字符串来创建对应的实例,就必须使用反射。

第二种方式:

1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过Constructor实例化对象。

# 调用方法

当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法一这两个方法的返回值是Method对象数组,或者Method对象。每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用对应方法。

之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。

说明:

  1. Object 对应原方法的返回值,若原方法无返回值,此时返回null
  2. 若原方法若为静态方法,此时形参Object obj可为null
  3. 若原方法形参列表为空,则Object[] args为null
  4. 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

# 访问属性值

通过Class对象的getFields()或getField(方法可以获取该类所包括的全部Field(属性)或指定Field。通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。

  • public Field getField(String name)返回此Class对象表示的类或接口的指定的public的Field。
  • public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。

Field提供了如下两组方法来访问属性:

  • getXxx(Object obj): 获取obj对象该Field的属性值。此处的Xxx对应8个基本类型,如果该属性的类型是引用类型则取消get后面的Xxx。
  • setXxx(Object obj,Xxx val):将obj对象的该Field设置成val值。此处的Xxx对应8个基本类型,如果该属性的类型是引用类型则取消set后面的Xxx。使用这两个方法可以随意地访问指定对象的所有属性,包括private访问控制的属性。

# 关于setAccessible方法的使用

  • Method和Field、Constructor对象都有setAccessible()方法。
  • setAccessible启动和禁用访问安全检查的开关。
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查

# 代码示例

MyAnnotation注解Person接口Producer类还采用上文的。

public static void main ( String[] args ) throws Exception {
        System.out.println("*****测试创建运行时类的对象,并通过此对象调用方法和访问属性*****");
        System.out.println("---测试创建运行时类的对象---");
        //获取Class对象
        Class<Producer> clazz = Producer.class;
        //获取构造器,这里先使用默认的空参构造器(也可以使用指定的构造器)
        Constructor<Producer> constructor = clazz.getConstructor();
        //通过调用构造器的newInstance方法创建对象
        Producer producer = constructor.newInstance();
        System.out.println(producer);
        //可以通过这个对象操作数据
        System.out.println("------使用这个对象操作类的属性和方法------");
        producer.setAge(22);
        producer.showProducts();
        System.out.println(producer);

        System.out.println("---测试通过反射调用方法---");
        Method method = clazz.getDeclaredMethod("showProducts");
        method.setAccessible(true);
        //传入的是创建的运行时类的对象
        method.invoke(producer);

        System.out.println("---测试通过反射访问属性---");
        Field field = clazz.getDeclaredField("name");
        field.setAccessible(true);
        field.set(producer,"我叫反射");
        System.out.println(field.get(producer));
        System.out.println(producer);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

结果:

*****测试创建运行时类的对象,并通过此对象调用方法和访问属性*****
---测试创建运行时类的对象---
Producer{name='null', age=0, id=0}
------使用这个对象操作类的属性和方法------
正在查看产品
Producer{name='null', age=22, id=0}
---测试通过反射调用方法---
正在查看产品
---测试通过反射访问属性---
Producer{name='我叫反射', age=22, id=0}
我叫反射
1
2
3
4
5
6
7
8
9
10
11

# 操作数组

在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array来动态地创建数组,操作数组元素等。

Array提供了如下几类方法:

  • static Object newlnstance(Class<?> componentT ype, int... length):创建- -个 具有指定的元素类型、指定维度的新数组。
  • static xxx getXxx(Object array, int index):返回array数组中第index个元素。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变为get(Object array, int index)
  • static void setXxx(Object array, int index, xXXx val):将array数组中第index元素的值设为val。其中xxx是各种基本数据类型,如果数组元素是引用类型,则该方法变成set(Object array,int index, Object val)

# 代码示例

public static void main ( String[] args ) {
        System.out.println("*******测试反射操作数组********");
        System.out.println("---操作一维数组---");
        //创建数组
        Object arr = Array.newInstance(String.class, 10);
        //赋值
        Array.set(arr, 2, "测试反射");
        Array.set(arr, 4, "测试反射操作数组");
        //获取指定元素的值
        System.out.println(Array.get(arr, 2));
        System.out.println(Array.get(arr, 4));
        System.out.println(Array.get(arr, 6));

        //三维数组其实是数组元素为二维数组的特殊一维数组
        System.out.println("---操作三维数组---");
        Object arr3 = Array.newInstance(String.class, 3, 4, 10);
        //
        System.out.println("------获取index为2的数组元素,应该是一个二维数组------");
        //获取arr3index为2的数组元素,应该是一个二维数组
        Object arrObj = Array.get(arr3, 2);
        //赋值
        Array.set(arrObj,2,new String[]{"反射测试1","反射测试2"});
        //获取arrObj数组的第三个元素,应该是一维数组
        Object anArr = Array.get(arrObj, 3);
        Array.set(anArr,8,"测试一维反射");
        String[][][] cast= (String[][][]) arr3;
        System.out.println(cast[2][3][8]);
        System.out.println(cast[2][2][0]);
        System.out.println(cast[2][2][1]);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

结果:

*******测试反射操作数组********
---操作一维数组---
测试反射
测试反射操作数组
null
---操作三维数组---
------获取index为2的数组元素,应该是一个二维数组------
测试一维反射
反射测试1
反射测试2
1
2
3
4
5
6
7
8
9
10

# 反射和泛型

在反射中使用泛型,可以避免使用反射的对象需要强制转换类型,从而引起异常。

# 对比泛型使用前后的差别

public static void main ( String[] args ) throws Exception {
        System.out.println("*******测试反射获取泛型*******");
        System.out.println("---不使用泛型的情况---");
        Person producer = (Producer) getInstance(Producer.class);

        System.out.println("---使用泛型的情况---");
        Person producer2 = getInstance2(Producer.class);

        //false,因为一个是Object类型,一个是Producer类型
        System.out.println(producer == producer2);
    }

    /**
     * 不使用泛型的情况
     */
    public static Object getInstance ( Class clazz ) throws Exception {
        return clazz.getConstructor().newInstance();
    }

    /**
     * 使用泛型的情况
     */
    public static <T> T getInstance2 ( Class<T> clazz ) throws Exception {
        return clazz.getConstructor().newInstance();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

结果:

*******测试反射获取泛型*******
---不使用泛型的情况---
---使用泛型的情况---
false
1
2
3
4

# 使用反射获取泛型信息

通过指定类对应的Class 对象,程序可以获得该类里包括的所有Ficld, 不管该Field 使用private修饰,还是使用public修饰。获得了Field对象后,就可以很容易地获得该Field的数据类型,即使用如下代码即可获得指定Field的类型:

//获取Field对象f的类型
Class<?> a = f.getType() ;
1
2

但通过这种方式只对普通类型的Field 有效。但如果该Field 的类型是有泛型限制的类型,如Map<String , Integer>类型,则不能准确得到该Field的泛型参数。

为了获得指定Field的泛型类型,应先使用如下方法来获取指定Field的泛型类型:

//获得Field实例f的泛型类型
Type gType = f.getGenericType() ;
1
2

然后将Type对象强制类型转换为ParameterizedType对象,ParameterizedType 代表被参数化的类型,也就是增加了泛型限制的类型。ParameterizedType 类提供了两个方法: .

  • getRawType(): 返回被泛型限制的类型。
  • getActualTypeArguments(): 返回泛型参数类型。

# 代码测试

producer类中添加代码:

//生产的商品map
public Map<String,String> productMap=new HashMap<>();
1
2

测试:

public static void main ( String[] args ) throws Exception {
		System.out.println("***使用反射获取泛型信息****");
        Class<Producer> clazz = Producer.class;
        Field f = clazz.getDeclaredField("productMap");
        Class<?> a = f.getType();
        System.out.println("获取参数类型" + a);
        Type genericType = f.getGenericType();
        System.out.println("获取泛型参数类型"+genericType);
        if (genericType instanceof ParameterizedType) {
            //将Type强转为ParameterizedType
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            //获取原始类型
            System.out.println("原始类型:" + parameterizedType.getRawType());
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for(Type t:actualTypeArguments){
                System.out.print(t+" ");
            }
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

结果:

*******测试反射获取泛型*******
---不使用泛型的情况---
---使用泛型的情况---
false
***使用反射获取泛型信息****
获取参数类型interface java.util.Map
获取泛型参数类型java.util.Map<java.lang.String, java.lang.String>
原始类型:interface java.util.Map
class java.lang.String class java.lang.String
1
2
3
4
5
6
7
8
9

# 读取配置文件利用反射创建并操作运行时类的对象

新建配置文件Producer.properties

className=reflectiontest.Producer
methodName=showProducts
name=小明
1
2
3
public static void main ( String[] args ) throws Exception {
        //加载配置文件
        Properties properties = new Properties();
        InputStream is = ReflectionTest6.class.getClassLoader().getResourceAsStream("Producer.properties");
        assert is != null;
        //InputStreamReader将字节流转为字符流(解决乱码问题)
        InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
        properties.load(isr);

        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        String argsType = properties.getProperty("argsType");
        String name = properties.getProperty("name");

        //创建运行时类的对象
        Class<?> clazz = Class.forName(className);
        Producer producer = (Producer) clazz.newInstance();

        Method method = clazz.getDeclaredMethod(methodName);
        method.invoke(producer);
        Field field = clazz.getDeclaredField("name");
        field.setAccessible(true);
        field.set(producer, name);
        System.out.println(producer);

        isr.close();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

结果:

正在查看产品
Producer{name='小明', age=0, id=0}
1
2

# 反射实现一个servlet处理多个接口请求

还记得在刚开始学习JavaEE的时候,在使用Servlet时,一个Servlet只能处理一个请求,写在doGet或是doPost的方法中。这样会造成如果接口API多的话,会产生大量的Servlet文件,造成类爆炸,而且项目结构并不清晰。

解决办法是可以采用反射实现一个servlet处理多个接口请求。

# 代码

/**
 * 测试使用反射,一个servlet处理多个接口请求
 *
 * @author rsw
 */
@WebServlet(name = "LoginServlet", value = "*.login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doPost ( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        doGet(request, response);
    }

    @Override
    protected void doGet ( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
        System.out.println("servlet调用....");

        //获取servletPath: /add.do 或 /query.do 等
        String servletPath = request.getServletPath();
        //去除/和.do : add 或 query
        String methodName = servletPath.substring(1);
        methodName = methodName.substring(0, methodName.length() - 6);

        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        try {
            //利用反射获取methondNmae对应的方法
            Method method = getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
            //利用反射调用对应的方法
            method.invoke(this, request, response);
        } catch (Exception e) {
            e.printStackTrace();
            //response.sendRedirect("error.jsp");    //给用户提示
        }
    }

    private void login ( HttpServletRequest request, HttpServletResponse response ) throws IOException, SQLException {
        PrintWriter out = response.getWriter();
        System.out.println("正在尝试登录...");
        //输出到页面
        out.println("正在尝试登录...");
    }

    private void exitLogin ( HttpServletRequest request, HttpServletResponse response ) throws IOException, SQLException {
        //获取请求参数
        PrintWriter out = response.getWriter();
        System.out.println("正在尝试退出...");
        //输出到页面
        out.println("正在尝试退出...");
    }


}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 解析

  1. 注解@WebServlet(name = "LoginServlet", value = "*.login")的value为*.login;截取接口的访问路径如果携带.login,那么则会进入到LoginServlet
  2. 字符串截取:*.login中的*代表的是在当前servlet的方法,通过匹配方法的方式,来实现使用接口API调用servlet中的指定的方法。
 //去除/和.do : add 或 query
String methodName = servletPath.substring(1);
methodName = methodName.substring(0, methodName.length() - 6);
1
2
3

.login为6个字符,所以减去6。 3. 使用反射调用指定的方法

//利用反射获取methondNmae对应的方法
Method method = getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
//利用反射调用对应的方法
method.invoke(this, request, response);
1
2
3
4

HttpServletRequest.classHttpServletResponse.class为调用方法的参数类型,一般都需要request和response,不管使用不,这里默认都使用。 4. loginexitLogin为两个测试的方法

注意:这里value*.login只是测试使用,login可以是任意字符,但必须要根据字符个数修改methodName.substring(0, methodName.length() - 6)

# 使用示例

配置tomcat略......

在这里插入图片描述 控制台输出:

servlet调用....
正在尝试登录...
1
2

在这里插入图片描述 控制台输出:

servlet调用....
正在尝试退出...
1
2

# 总结

使用反射实现一个servlet处理多个接口请求,如果想要添加一个接口的API就不必再新增一个servlet文件了,直接添加方法就可以了。