- 认识反射
反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。
Java中,反射允许在编译期间不知道接口的名称,字段、方法的情况下在运行时检查类、接口、字段和方法。它还允许的实例化新对象和调用的方法。
- 反射在java中的应用
主要包含两部分:(1)根据类名获取对应类的Class对象;(2)通过Class对象提供的方法API,可获取类的全部信息。
首先引入测试类Student.java
/**
* 学生类
* @author mjs
* @version 1.0.0
* @filename Student.java
* @time 2017-3-15 下午7:51:44
* @copyright(C) 2017 **********有限公司
*/
package com.bat.test;
public class Student {
private int id;
private String name;
public Student() {
// TODO Auto-generated constructor stub
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1、根据类名获取对应类的Class对象
以下三种:
//1、直接通过类型获取
Class sClass1= Student.class;
//2、通过已经实例化的对象获取
Class sClass2= student.getClass; //注:MyObject需要初始化
//3、Class类中的静态方法
String sClassUrl= "Student"; //注:此处包含包名称(测试类和测试Test为同一目录)
Class sClass3 = Class.forName(sClassUrl);
2、通过Class对象提供的方法API,可获取类的全部信息
获取类名
Class sClass = Student.class;
String className = sClass.getName(); //对象所表示的实体名称
String classSimpleName = sClass.getSimpleName();//获取底层类的简称
System.out.println("className:" + className);
System.out.println("simpleName:" + classSimpleName);
/**
* 输出:
* com.bat.test.Student
* simpleName:Student
*/
获取修饰符
Class sClass = Student.class;
int nu = sClass.getModifiers(); //返回此类或接口以整数编码的 Java 语言修饰符
System.out.println(nu);
System.out.println(Modifier.isPublic(nu)); //使用Modifier类判断类修饰符
/**
* 输出:
* 1
* true
* false
*/
获取方法
Class sClass = Student.class;
Method[] methods = sClass.getMethods();
System.out.println("方法个数" + methods.length); //不包含构造方法
for(Method method:methods){
//打印方法名称和参数个数,getMethods获取包括超类/超接口的方法
System.out.println("方法名称:" + method.getName() + " 参数个数:" + method.getParameterCount());
System.out.println("------------------------------------");
}
/**
* 输出:
* 方法个数13
* 方法名称:getName 参数个数:0
* ------------------------------------
* 方法名称:getId 参数个数:0
* ------------------------------------
* 方法名称:setName 参数个数:1
* ------------------------------------
* 方法名称:setId 参数个数:1
* ------------------------------------
* 方法名称:wait 参数个数:0
* ------------------------------------
* 方法名称:wait 参数个数:2
* ------------------------------------
* 方法名称:wait 参数个数:1
* ------------------------------------
* 方法名称:equals 参数个数:1
* ------------------------------------
* 方法名称:toString 参数个数:0
* ------------------------------------
* 方法名称:hashCode 参数个数:0
* ------------------------------------
* 方法名称:getClass 参数个数:0
* ------------------------------------
* 方法名称:notify 参数个数:0
* ------------------------------------
* 方法名称:notifyAll 参数个数:0
* ------------------------------------
*/
获取成员变量
/**
* 在Student类中新增 public int age; age理论是private的,只需要对外提供set/get方法,此处只为测试使用
*/
Class sClass = Student.class;
//只能获取公共字段(public)
Field[] fields = sClass.getFields();
for(Field field : fields){
System.out.println("变量名称:" + field.getName() + " 变量类型 :" + field.getGenericType());
System.out.println("----------------------------");
}
System.out.println("---------------我是分割线--------------");
//获取所有成员变量字段(public、protected等)
Field[] fields1 = sClass.getDeclaredFields();
for(Field filed : fields1){
System.out.println("变量名称:" + filed.getName() + " 变量类型 :" + filed.getGenericType());
System.out.println("----------------------------");
}
/**
* 输出:
* 变量名称:age 变量类型 :int
* ----------------------------
* ---------------我是分割线--------------
* 变量名称:id 变量类型 :int
* ----------------------------
* 变量名称:name 变量类型 :class java.lang.String
* ----------------------------
* 变量名称:age 变量类型 :int
* ----------------------------
*/
java.lang.reflect.Class类提供了许多方法,具体参照对应的API。
- 动态代理
先介绍spring AOP技术。(此处摘抄)AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,已获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然而它又以巧夺天工的妙手将这些剖开的切面复原,不留痕迹。
而动态代理是实现AOP技术的一类。下面我们了解java两个重要的相关类或接口:
(1)java.lang.reflect.Proxy 该类提供用于创建动态代理类的实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
//用于获取指定代理对象所关联的调用处理器 static InvocationHandler getInvocationHandler(Object proxy) //用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) //用于判断指定类对象是否是一个动态代理类 static boolean isProxyClass(Class cl) // 用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
(2)java.lang.reflect.InvocationHandler 该接口自定义了一个invoke方法,用于集中处理在动态代理类对象上的方法调用,通常通过实现该接口重写invoke方法,以实现对委托类的代理访问。
实现动态代理的步骤:
-
- 实现 InvocationHandler接口创建自己的调用处理器;
- 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
下面引入一个实例:
Subject.java
/** * 主题接口 * @author mjs * @version 1.0.0 * @filename Subject.java * @time 2017-3-20 上午11:09:48 * @copyright(C) 2017 **********有限公司 */ package proxy; public interface Subject { void doSomething(); }
RealSubject.java
/** * 主题实现类 * @author mjs * @version 1.0.0 * @filename ReadSubject.java * @time 2017-3-20 上午11:11:00 * @copyright(C) 2017 **********有限公司 */ package proxy; public class RealSubject implements Subject { @Override public void doSomething() { System.out.println("call doing something!"); } }
ProxyHandler.java
/** * 主题代理类,需要实现 * @author mjs * @version 1.0.0 * @filename SubjectHandler.java * @time 2017-3-20 上午11:17:31 * @copyright(C) 2017 **********有限公司 */ package proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ProxyHandler implements InvocationHandler{ private Object proxied; public ProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before execution"); method.invoke(proxied, args); System.out.println("After implementation"); return null; } }
测试如下所示:
/** * 测试类 * @author mjs * @version 1.0.0 * @filename Test.java * @time 2017-3-20 上午11:12:09 * @copyright(C) 2017 **********有限公司 */ package proxy; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class TestBuildProxy { public static void main(String[] args){ try { Subject subject = new RealSubject(); /** * ProxyHandler实现InvocationHandler接口,并能实现方法调用从代理类到委托的分派转发 * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用 */ InvocationHandler handler = new ProxyHandler(subject); System.out.println("动态代理对象创建方法一:"); /** * 获取关联于指定类加载器和一组接口的动态代理类的类对象 */ Class clazz = Proxy.getProxyClass(Subject.class.getClassLoader(), new Class[]{Subject.class}); /** * 通过反射从生成的类对象获得构造函数对象 */ Constructor constructor; /** * 通过反射从生成的类对象获得构造函数对象 */ constructor = clazz.getConstructor(new Class[]{InvocationHandler.class}); /** * 通过构造函数对象创建动态代理类实例 */ Subject s = (Subject) constructor.newInstance(new Object[] {handler}); System.out.println("方法一创建的RealSubject接口代理:" + s.getClass()); System.out.println("---------------------------我是分割线-------------------------------"); System.out.println("动态代理对象创建方法二:"); Subject ss = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[] { Subject.class }, handler); System.out.println("方法二创建的RealSubject接口代理:" + ss.getClass()); System.out.println("---------------------------我是分割线-------------------------------"); s.doSomething(); ss.doSomething(); } catch (Exception e) { e.printStackTrace(); } } } /** * 输出: * * 动态代理对象创建方法一: * 方法一创建的RealSubject接口代理:class $Proxy0 * ---------------------------我是分割线------------------------------- * 动态代理对象创建方法二: * 方法二创建的RealSubject接口代理:class $Proxy0 * ---------------------------我是分割线------------------------------- * false * Before execution * call doing something! * After implementation * Before execution * call doing something! * After implementation * */
注:考虑以上两种生成代理类方式是否一致?其实后者是前者的简化版,详细请看JDK源码。
通过观察,动态生成的代理类本身的一些特点(网上摘录):1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:对于代理类,Proxy 类是它的父类。
附:java.lang.reflect.Proxy类两个重要方法:
Subject subject = new RealSubject(); InvocationHandler handler = new ProxyHandler(subject); Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[] { Subject.class }, handler); System.out.println("获取Subject代理对象关联的调用处理器:" + Proxy.getInvocationHandler(proxy)); System.out.println("判断类对象proxy是否是一个动态代理类标识:" + Proxy.isProxyClass(proxy.getClass())); /** * 输出: * 获取Subject代理对象关联的调用处理器:proxy.ProxyHandler@482923 * 判断类对象proxy是否是一个动态代理类标识:true */
- 总结
通过学习反射以及java动态代理实现,进一步理解。在这方面还需要看源码以及api,不断进步,才有无限可能。后续将会学习设计模式中的一种重要设计模式-代理模式,本篇随笔只为设计模式做铺垫。