Reflection(反射)被视为动态语言的关键,反射机制允许程序在执行期取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
正常方式:获取类的信息——>使用new实例化——>取得实例化对象
反射方式:实例化对象——>getClass()方法——>得到类的信息
- 动态语言:运行时结构可变的语言。如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。主要动态语言:Object-C、C#、JavaScript、PHP、Python。
- 静态语言:运行时结构不可变的语言。如Java、C、C++
Java可以称之为“准动态语言”。即Java可以使用反射得到一定的动态性。
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
在实际开发中,建议使用new实例化对象来获取相应的信息。仅在涉及到动态化时使用反射。例如:服务器先跑起来,用户才能连接,当用户访问某项功能如/login,服务器收到url后动态的创建对象,事先并不知道要创建什么类型的对象。
13.1 Class类
Object类中定义了getClass()
,Class类是反射的源头。只要元素类型与维度一样,就是同一个Class。一个Class对象对应唯一一个加载到JVM中的相应.class文件,通过Class可以完整地得到一个类中的所有被加载的结构。
获取Class类实例:
Class clazz = Class.forName(“java.lang.String”) 根据指定类名获取,该方式用的最多。最好传入完整包名
Class clazz = String.class 根据类的class属性获取,该方式安全性最高
Class clazz = person.getClass() 根据类实例getClass()获取
Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例
getName() 返回此Class对象所表示的实体名称
Class getSuperClass() 返回当前Class对象的父类的Class对象
Class [] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
Field[] getDeclaredFields() 返回Field属性对象数组
Method getMethod(String name,Class … paramTypes)返回指定名称和参数类型的方法对象
13.2 类的加载与ClassLoader
当程序使用某个类时,如果该类还未被加载到内存中,会通过如下三个步骤来对该类进行初始化。
- 加载:将.class文件加载到内存中,然后生成一个代表这个类的Class对象。所有访问和使用类数据只能通过这个Class对象。这个过程需要类加载器参与。
- 链接:将类的二进制代码合并到JVM的运行状态之中的过程,正式为类变量(static)分配内存并设置类变量默认初始值,虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)。
- 初始化:执行类构造器
<clinit>()
方法,类构造器方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的,用于构造类的信息。当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
ClassLoader classloader = ClassLoader.getSystemClassLoader();//获取一个系统类加载器
classloader = classloader.getParent();//获取系统类加载器的父类加载器,即扩展类加载器
// 引导类不可继续获得
classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();//获取指定类的类加载器
// 类加载器的一个主要使用情形就是获取类路径下的指定文件的输入流,常用于加载配置文件等
InputStream in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties");
// 默认搜索路径为src,而文件输入流的默认路径为moudle。通常更建议将配置文件置于src下,因为放在moudle下的部分文件在配置到服务器中可能会丢失
13.3 通过反射对运行时类进行操作
通过Class对象创建类的对象:
- 调用Class对象的newInstance 方法,该方法仅可调用类的无参构造器,且构造器的权限不可为private
- 通过Class对象的getDeclaredConstructor(Class … parameterTypes)取得该类指定形参类型的构造器,获取构造器后再使用newInstanc方法初始化类属性
在下述所指的get…方法仅可调用该类所拥有的相应public结构,getDeclared…方法可以调用该类所用于的全部访问权限结构。getModifiers返回的整型可由Modifier.toString转化为字符串。
1.实现的全部接口
public Class<?>[] getInterfaces()
2.所继承的父类
public Class<? Super T> getSuperclass() // 返回此 Class 所表示的实体的父类的Class。
3.全部的构造器
public Constructor<T>[] getConstructors()
public Constructor<T>[] getDeclaredConstructors()
/* Constructor类中:*/
public int getModifiers(); // 取得修饰符
public String getName(); // 取得方法名称
public Class<?>[] getParameterTypes(); // 取得参数的类型
4.全部的方法
public Method[] getDeclaredMethods()
public Method[] getMethods()
/* Method类中:*/
public Class<?> getReturnType()取得全部的返回值
public Class<?>[] getParameterTypes()取得全部的参数
public int getModifiers()取得修饰符
public Class<?>[] getExceptionTypes()取得异常信息
5.全部的Field
public Field[] getFields()
public Field[] getDeclaredFields()
/* Field方法中:*/
public int getModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。
6. Annotation相关:任何使用了注解的结构都可以调用该方法,但只有RUNTIME类型的注解才能够被获取
getAnnotation(Class<T> annotationClass)
getDeclaredAnnotations()
7.泛型相关
Type getGenericSuperclass() // 获取父类泛型类型
ParameterizedType // 泛型类型
getActualTypeArguments() // 获取实际的泛型类型参数数组
8.类所在的包
Package getPackage()
调用指定方法:
- 通过Class类的getMethod(String name,Class…parameterTypes)方法取得指定重载Method对象
- 使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
调用指定属性:Object invoke(Object obj, Object … args)中的第一个参数为调用者。
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null,也就是让当前所属方法运行时类调用
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke 方法前,显式调用方法对象的setAccessible true 方法
通过Field类提供的set 和get()方法就可以完成设置和取得属性内容的操作。 ```java public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field
在Field中: public Object get(Object obj) 取得指定对象obj上此Field的属性内容 public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
```java
Class clazz = Class.forName("Person"); // 获取指定Class对象
Constructor con = clazz.getConstructor(String.class,Integer.class); //调用指定参数结构的构造器,生成Constructor的实例
Person p = (Person) con.newInstance("Peter",20); // 通过构造器实例创建对应类的对象,并初始化类属性
Field field = clazz.getField("name"); // 获取该类指定属性
field.set(p, "Peter"); // 修改该对象的获取属性
System.out.println(field.get(p)); // 获取该对象的获取属性
13.4 代理模式Proxy
创建一个代理类执行原对象一些相应操作。如果已有方法在使用时需要进行改进,此时有两种办法:
- 修改该方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
- 采用一个代理类调用原有的方法,且对产生的结果进行控制。(代理模式)
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
// 代码示例
// 接口类
public interface Sourceable {
public void method();
}
// Source类
public class Source implements Sourceable {
public void method() {
System.out.println("the original method!");
}
}
// Proxy类
public class Proxy implements Sourceable {
private Sourceable source; // 用代理类对象进行实例化
public Proxy(Sourceable source){
this.source = source;
}
public void method() {
System.out.println("before proxy!");
source.method();
System.out.println("after proxy!");
}
}
// 测试类
public class ProxyTest {
public static void main(String[] args) {
Source source = new Source(); // 创建被代理类对象
Proxy proxy = new Proxy(source);
proxy.method();
}
}
13.5 反射的应用:动态代理
上述的代理模式是静态代理,不利于程序的扩展。每一个代理类只为一个接口服务,这必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。将接口声明的所有方法都转移到一个集中方法中处理。主要使用场景是调试、远程方法调用。
Proxy :专门完成代理的操作类,是所有动态代理类的父类。其提供用于创建动态代理类和动态代理对象的静态方法:
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 创建一个动态代理类所对应的Class对象
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 直接创建一个动态代理对象
// Class<?>[] interfaces:代理类需要实现的全部接口
// InvocationHandler:InvocationHandler接口实现类
// 动态代理举例
interface Human{ // 声明接口来让其他类继承
String getBelief();
void eat(String food);
}
// 被代理类,实现两个接口方法
class SuperMan implements Human{
public String getBelief(){
return "I believe I can fly!";
}
public void eat(String food){
System.out.println("我喜欢吃" + food);
}
}
// 动态代理类
class ProxyFactory{
// 调用该方法获取代理类对象,传入参数为被代理类的对象。因为要代理任意类I型那,因此返回Object
public static Object getProxyInstance(Object obj){
// 创建实现InvocationHandler接口的类对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj); // 将被代理类对象传入handler用于实现反射invoke方法
return Proxy.newProxyInstance(obj.getClass().getClassLoder(),obj.getClass().getInterfaces(),handler); // 因为要实现动态性,因此传给该方法的参数均由静态方法传入的Object对象提供
}
}
//
class MyInvocationHandler implements InvocationHandler{
private Object obj; // 绑定的动态被代理类对象
public void bind(Object obj){
this.obj = obj; // 用于初始化obj属性的方法
}
// 当通过代理类的对象,调用方法a时,将自动调用该方法。该方法用于扩充与调用被代理类指定参数args方法
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
// 代理类实现的功能.......1
Object returnValue = method.invoke(obj,args); // 被代理类对象要调用的方法。该方法的返回值就作为当前类中invoke方法的返回值,动态性体现
// 代理类实现的功能.......2
return returnValue;
}
}
// 测试类
public class ProxyTest{
public static void main(String[] args){
SuperMan superman = new SuperMan();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superman); // 获取代理类对象
// 当通过代理类调用相应方法时将调用被代理类的同名方法
System.out.println(proxyInstance.getBelief());
proxyInstance.eat("四川麻辣烫");
}
}
/*输出结果
* I believe I can fly!
* 我喜欢吃四川麻辣烫
*/
上述代码反映了面向切面编程方法原理,如下入所示,橙色部分是固定的,而灰色部分是动态可变的。