Java反射技术及其应用

2017-01-13 15:07:38来源:csdn作者:qq_35101189人点击

第七城市





学习Java的同学注意了!!!学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:589809992我们一起学Java!



翻阅了很多资料想查看一个比较通俗易懂的关于java反射机制的理解,但是想要从概念中去理解一项技术,可能有点困难,所以先将理论型知识贴出来,后面,慢慢来理解。




反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。—-《百度百科》




注意:要理解java的反射机制,先要了解以下基本概念:运行时,编译时,编译型,解释型,类加载器,动态加载类等相关概念。

一.
Class类的使用
概念理解在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。
获取Class实例的方式不能直接创建Class的实例对象,因为Class类的构造方法是私有的,只有jvm可以去创建。(1) 利用对象调用getClass()方法获取该对象的Class实例;(2) 使用Class类的静态方法forName(),用类的名字获取一个Class实例,源码如下;



1
2
3
4
5
6

@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}


(3)运用.class的方式获取Class实例,对于基本数据类型的封装类,还可以采用TYPE来获取对应的基本数据类型的Class实例



综上所述,其实我们代码中创建的每一个类都是一个对象,只不过她是Class类的实例对象,这个对象我们称为该类的类类型。并且一个类只可能是Class类的一个实例对象,即获取的类类型是相同的



那么,如何去创建Class的实例呢?首先,过程要理解,源文件经过编译(javac.exe)以后,得到一个或多个.class文件。.class文件经过运行(java.exe)这步,就需要进行类的加载(通过JVM的类的加载器),记载到内存中的缓存。每一个放入缓存中的.class文件就是一个Class的实例!下面是创建Class实例的三种方法。



实例如下:





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

public class ReflectTest {public void display(){
System.out.println("Hello World!");
}
public static void main(String[] args){
ReflectTest reflectTest = new ReflectTest();
Class clazz1 = reflectTest.getClass();
Class clazz2 = ReflectTest.class;
Class clazz3 = null;
try {
clazz3 = Class.forName("com.william.test.ReflectTest");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(clazz1==clazz2);
System.out.println(clazz1==clazz3);
System.out.println(clazz2==clazz3);
}
}output:
true
true
true




其实,还有一种创建Class实例的方法,是通过类的加载器,如下:





1
2
3
4
5
6
7
8
9
10
11
12
13
14

@Test
public void LoadClassTest(){
ClassLoader loader = this.getClass().getClassLoader();
Class clazz4 = null;
try {
clazz4 = loader.loadClass("com.william.test.ReflectTest");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(clazz4);
}output:
class com.william.test.ReflectTest


如何根据类类型去创建类的实例呢?



1
2
3
4
5
6
7
8
9
10
11
12
13

//通过类的类型创建该类的实例对象
ReflectTest reflectTest1 = null;
try {
reflectTest1 = (ReflectTest) clazz1.newInstance();//需要类有无参的构造方法
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
reflectTest.display();output:
Hello World!

动态加载类Class.forName("类的全称")表示类的类类型,还代表了动态加载类区分上面所说的编译,运行编译时刻加载类是静态加载类,运行时刻加载类是动态加载类
java获取方法信息



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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

/**
*打印类的信息,包括类的成员函数,成员变量
*/
public class ClassUtil {
/**
* 获取成员函数
* object 该对象所属类的信息
*
* @param object
*/
public static void printMethodMessage(Object object) {
//要获取类的信息,首先要获取类的类类型
Class clazz = object.getClass();//传递的是哪个子类的对象, clazz就是该子类的类类型
//获取类的名称
System.out.println("类的名称是:" + clazz.getName());/**
* Method类,方法对象
* 一个成员方法就是一个Method对象
* getMethods()方法获取的是所有的public的函数,包括父类继承而来的
* getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
*/
Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
//1. 获取注解
Annotation[] ann = methods[i].getAnnotations();
for (Annotation a : ann) {
System.out.println(a);
}//2. 获取权限修饰符
String str = Modifier.toString(methods[i].getModifiers());
System.out.print(str + " ");//3. 得到方法的返回值类型的类类型
Class returnType = methods[i].getReturnType();
System.out.print(returnType.getName() + " ");//4. 得到方法的名称
System.out.print(methods[i].getName() + "(");//5.获取参数类型-->得到的是参数列表的类型的类类型
Class[] paramTypes = methods[i].getParameterTypes();
//解析数组
for (int j = 0; j < paramTypes.length; j++) {
if (j == 1 || j == paramTypes.length - 1) {
System.out.print(paramTypes[j].getName() + " args" + j);
} else {
System.out.print(paramTypes[j].getName() + " args" + j + ",");
}
}
System.out.print(")");//6.获取异常类型
Class[] exps = methods[i].getExceptionTypes();
if (exps.length != 0) {
System.out.print(" throws ");
}
for (int k = 0; k < exps.length; k++) {
System.out.print(exps[k].getName() + " ");
}
System.out.println();
}
}
}class MethodTest {
private String name;
//注解类型在java之注解开发章节讲解过的
@MyAnnotation(wd = WeekDay.MON,id = 4)
public int age;
static String desc = "这是一个人";
public MethodTest() {
}private MethodTest(String name, int age) {
this.name = name;
this.age = age;
}
@Deprecated
public void print(int a, int b) throws Exception {
System.out.println(a + b);
}public void print(String a, String b) {
System.out.println(a.toUpperCase() + "," + b.toLowerCase());
}@Override
public String toString() {
return "MethodTest{" +
"name='" + name + '/'' +
", age=" + age +
'}';
}//调用
MethodTest methodTest = new MethodTest();
ClassUtil.printMethodMessage(methodTest);
output:
类的名称是:com.william.test.MethodTest
public java.lang.String toString()
@java.lang.Deprecated()
public void print(int args0,int args1) throws java.lang.Exception
public void print(java.lang.String args0,java.lang.String args1)


获取方法的信息,主要通过Method类数组来接受getMethods()方法的返回值,然后进行遍历解析。



获取成员变量信息获取成员变量的信息,同获取方法的信息类似,只不过使用Field类数组来接收getFieldss()方法的返回值,然后进行解析。代码如下:





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

/**
* 获取成员变量
*/
public static void printFieldMessage(Object object) {
//要获取类的信息,首先要获取类的类类型
Class clazz = object.getClass();//传递的是哪个子类的对象, clazz就是该子类的类类型
//获取类的名称
System.out.println("类的名称是:" + clazz.getName());/**
* 成员变量也是对象
* java.lang.reflect.Field
* Field类封装了关于成员变量的操作
* getFields()方法获取的是所有的public的成员变量的信息
* getDeclaredFields获取的是该类自己声明的成员变量的信息
*/Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获取每个属性的权限修饰符
int i = field.getModifiers();
String str = Modifier.toString(i);
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(str + " "+ typeName + " " + fieldName);
}}
//调用
String str = "Hello World!";
ClassUtil.printFieldMessage(str);output:
类的名称是:java.lang.String
类的名称是:java.lang.String
private final [C value
private int hash
private static final long serialVersionUID


获取构造函数





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

/**
* 获取对象的构造函数的信息
*
* @param object
*/
public static void printConMessage(Object object) {
//要获取类的信息,首先要获取类的类类型
Class clazz = object.getClass();//传递的是哪个子类的对象, clazz就是该子类的类类型
//获取类的名称
System.out.println("类的名称是:" + clazz.getName());/**
* 构造函数也是对象
* java.lang.Constructor中封装了构造函数的信息
* getConstructors获取所有的public的构造函数
* getDeclaredConstructors得到所有的构造函数
*/
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.print(constructor.getName() + "(");
//获取构造函数的参数列表--->得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class clazz1 : paramTypes) {
System.out.print(clazz1.getName() + ",");
}
System.out.println(")");
}}//调用
String str = "Hello World!";
ClassUtil.printConMessage(str);output:
类的名称是:java.lang.String
java.lang.String([B,int,int,)
java.lang.String([B,java.nio.charset.Charset,)
java.lang.String([B,java.lang.String,)
java.lang.String([B,int,int,java.nio.charset.Charset,)
java.lang.String([B,int,int,java.lang.String,)
java.lang.String(java.lang.StringBuilder,)
java.lang.String(java.lang.StringBuffer,)
二.
方法的反射
方法的反射1) 如何获取某个方法方法的名称和方法的参数里列表才能唯一决定某个方法2) 方法反射的操作method.invoke(对象,参数里列表)示例代码:



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

public class MethodReflect {
public static void main(String[] args){
//要获取print(int,int)方法 1. 要获取一个方法就是获取类的信息,获取类的信息首先要获取类的类类型
MethodTest methodTest = new MethodTest();
Class clazz = methodTest.getClass();try {
//Methodmethod = clazz.getMethod("print",new Class[]{int.class,int.class});
Method method = clazz.getMethod("print",int.class,int.class);//以前的方法的方法调用
//methodTest.print(10,20);//方法的反射操作
//方法如果没有返回值返回null,有返回值返回具体的返回值
//method.invoke(methodTest,new Object[]{10,20});
Object o = method.invoke(methodTest,10,20);
System.out.println("============================");
//获取方法print(String,String)
Method method1 = clazz.getMethod("print",String.class,String.class);
//用方法进行反射操作
o = method1.invoke(methodTest,"hello","WORLD!");
} catch (Exception e) {
e.printStackTrace();
}
}
}class MethodTest {
private String name;
public int age;
static String desc = "这是一个人";
public MethodTest() {
}private MethodTest(String name, int age) {
this.name = name;
this.age = age;
}public void print(int a, int b) {
System.out.println(a + b);
}public void print(String a, String b) {
System.out.println(a.toUpperCase() + "," + b.toLowerCase());
}@Override
public String toString() {
return "MethodTest{" +
"name='" + name + '/'' +
", age=" + age +
'}';
}
}output:
30
============================
HELLO,world!
三.
成员变量的反射


实例如下:





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 clazz1;
try {
clazz1 = Class.forName("com.william.test.MethodTest");
Object object = clazz1.newInstance();
MethodTest methodTest1 = (MethodTest) object;
//调用private属性
Field field1 = clazz1.getDeclaredField("name");
field1.setAccessible(true);
field1.set(methodTest1,"william");
System.out.println(methodTest1.toString());
//调用public的属性
Field field2 = clazz1.getField("age");
field2.set(methodTest1,9);
System.out.println(methodTest1.toString());
//调用static属性
Field field3 = clazz1.getDeclaredField("desc");
System.out.println(field3.get(MethodTest.desc));
//System.out.println(field3.get(null));
} catch (Exception e) {
e.printStackTrace();
}
output:
MethodTest{name='william', age=0}
MethodTest{name='william', age=9}
这是一个人


四.
构造函数的反射


实例如下:





1
2
3
4
5
6
7
8
9
10
11
12
13

//构造函数的反射
Class clazz3 = MethodTest.class;
try {
Constructor constructor = clazz3.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
MethodTest methodTest2 = (MethodTest) constructor.newInstance("will",99);
System.out.println(methodTest2.toString());
} catch (Exception e) {
e.printStackTrace();
}output:
MethodTest{name='will', age=99}


五.
认识泛型的本质


先来看一个实例:





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

public class FanXingTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
list1.add("hello");
//list1.add(20);编译错误
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.println(c1 == c2);
//反射的操作都是编译之后的操作try {
Method m = c2.getMethod("add",Object.class);
m.invoke(list1,20);//绕过编译操作就绕过了泛型
System.out.println(list1.size());
System.out.println(list1);
} catch (Exception e) {
e.printStackTrace();
}}
}
output:
true
2
[hello, 20]




通过返回结果,我们可以看到在编译之后集合的泛型是去泛型化的,java中集合类型的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了,所以我们通过方法的反射来操作,可以绕过编译。

六.
反射应用之动态代理


动态代理是指客户通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。实例代码:





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

//动态代理的使用
interface Subject {
void action();
}//被代理类
class RealSubject implements Subject {@Override
public void action() {
System.out.println("我是被代理类,记得要执行我奥,么么~~~");
}
}class MyInvocationHandler implements InvocationHandler {Object object;//实现了接口的被代理类的对象的声明//①给被代理的对象实例化 ②返回一个代理类的对象
public Object blind(Object object) {
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}//当通过代理类的对象发起对被重写的方法的调用时,都会转化为对如下的invoke方法的调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method方法的返回值是returnVal
Object returnVal = method.invoke(object,args);
return returnVal;
}
}
public class ProxyTest {
public static void main(String[] args){
//1. 创建被代理类对象
RealSubject realSubject = new RealSubject();
//2. 创建一个实现了InvocationHandler接口的类的对象
MyInvocationHandler handler = new MyInvocationHandler();
//3. 调用blind()方法,冬天的返回一个同样实现了real所在类实现的接口Subject的代理类的对象。
Object object = handler.blind(realSubject);
Subject subject = (Subject) object;//此时sub就是代理类的对象subject.action();//转到对InvacationHandler接口的实现类的invoke()方法的调用
}
}
output:
我是被代理类,记得要执行我奥,么么~~~




说明,了解动态代理和静态代理的区别,所谓的静态代理,其代理类和目标对象的类在编译期间就确定下来,不利于程序的扩展。即,每一个代理类只能为一个接口服务,也就是说程序开发中会产生很多代理类。

七.
动态代理与AOP


由:ctProxy转换为:xyProxy



实例:





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

interface Human {
void info();void fly();
}class SuperMan implements Human {@Override
public void info() {
System.out.println("我是超人!");
}@Override
public void fly() {
System.out.println("I believe I can fly!");
}
}class HumanUtil {
public void method1() {
System.out.println("=============方法一============");
}public void method2() {
System.out.println("=============方法二============");
}
}class MyInvocationHandler implements InvocationHandler {Object object;//被代理类对象的声明public void setObject(Object object) {
this.object = object;
}@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil h = new HumanUtil();h.method1();Object returnVal = method.invoke(object, args);h.method2();return returnVal;
}
}//动态的创建一个代理类的对象
class MyProxy {
public static Object getProxyInstance(Object object) {
MyInvocationHandler handler = new MyInvocationHandler();
handler.setObject(object);return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), handler);
}
}public class TestAOP {
public static void main(String[] args){
SuperMan man = new SuperMan();//创建一个被代理类的对象
Human human = (Human) MyProxy.getProxyInstance(man);//返回一个代理类的对象
human.info();//通过代理类的对象嗲用重写的抽象的方法
System.out.println();
human.fly();
}}output:
=============方法一============
我是超人!
=============方法二=========================方法一============
I believe I can fly!
=============方法二============




AOP代理的方法:AOPMethod





相关概念


什么是编译?答:将原程序翻译成计算机语言,就是二进制代码,在java中是将.java文件也就是源程序翻译成.class的字节码什么是编译时?答:将原程序翻译成计算机语言的过程中,将.java翻译为.class文件的过程什么是运行时?答:就是在启动这个程序的时候,在java中是,类加载器加载.class文件,并交给jvm处理什么是编译型语言?答:将原程序一次性全部转换为二进制代码,然后执行程序什么是解释型语言?答:转换一句,执行一句,java是既编译又解释的语言编译型语言和解释型语言的区别:答:编译型语言效率高,依赖于编译器,但是跨平台差,解释型的效率低,依赖于解释器,但跨平台强什么是类加载器?答:类加载器就是JVM中的类装载器,作用就是将编译好的.class字节码运到检查器进行安全检查的,检查通过后开始解释执行什么是运行时动态加载类?答:反射就是可以将一个程序(类)在运行的时候获得该程序(类)的信息的机制,也就是获得在编译期不可能获得的类的信息,因为这些信息是保存在Class对象中的,而这个Class对象是在程序运行时动态加载的

它就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成对象。类在运行的时候,可以得到该类的信息,并且
可以动态的修改这些信息,自己能看到自己,跟照镜子一样,class对象是在运行的时候产生的,通过class对象操作类的信息是在运行时进行的,当运行 程序的时候,类加载器会加载真正需要的类,什么是真正需要的呢?就是该类真正起作用,如:有该类的对象实例,或该类调用了静态方法属性等。















学习Java的同学注意了!!!学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:589809992我们一起学Java!


第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台