反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。Java反射机制是在进行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法。本实例介绍反射类的方法以及如何获得和调用其他类的属性、构造方法和方法信息。
技术要点:
运用反射的技术要点如下:
反射机制最重要的内容-检查类的结构;
反射机制提供以下功能:在运行时判断任意一个对象所属的类Class;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量Field,方法Method和构造器Constructor;
对于Class类中的getFields,getMethods和getConstructors方法将分别返回类提供的public域,方法和构造器数组,其中包括超类的公有成员。关键词是公有的,Class类的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但是不包括超类的成员。
package core;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Customer {// 用户类
private Long id; // 用户编号
private String name;// 用户名称
private int age; // 年龄
public Customer() {
}// 默认构造方法
public Customer(String name, int age) {// 带参构造方法
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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 void showInfo(String salary){
System.out.println("薪水: "+ salary+" RMB");
}
}
public class TextReflect {
public Object getProperty(Object obj) throws Exception {// 取得参数对象中的属性
Class<?> classType = obj.getClass();// 获取对象的类型
System.out.println("class:" + classType.getName());
// 调用的这两个方法要仔细查阅资料
Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});//通过默认构造方法创建一新对象
Field fields[] = classType.getDeclaredFields();//获取对象的所有属性
for (int i = 0; i < fields.length; i++) {//循环输出该对象的每一个属性
Field field=fields[i];
String fieldName=field.getName();
String firstLetter=fieldName.substring(0,1).toUpperCase();//首先把每个属性列的首字母变为大写
//get方法
String getMethodName="get"+firstLetter+fieldName.substring(1);//获得属性对应的getXxx()方法get+大写首字母+首字母之后的字符串拼接起来
Method getMethod=classType.getMethod(getMethodName, new Class[] {});//获取和属性对应的getXxx()方法
Object value=getMethod.invoke(obj, new Object[] {});//调用原对象的getXxx()方法得到该属性列所对应的值
System.out.println(fieldName+":"+ value);
//set方法
String setMethodName="set"+firstLetter+fieldName.substring(1);//获得属性对应的setXxx()方法set+大写首字母+首字母之后的字符串拼接起来
Method setMethod=classType.getMethod(setMethodName, new Class[] {field.getType()});//获取和属性对应的setXxx()方法
setMethod.invoke(objectCopy, new Object[] {value});//调用拷贝对象的setXxx()方法
//System.out.println(""+objectCopy.toString());
}
return objectCopy;
}
public Object getPrivatePropertyValue(Object obj,String propertyName) throws Exception{//获取参数对象的属性值
Class cls=obj.getClass();//获取对象的类型
Field field=cls.getDeclaredField(propertyName);//获取对象的指定属性
field.setAccessible(true);//属性允许访问
Object retvalue=field.get(obj);//获取obj对象的属性field所对应的值
return retvalue;
}
public Object invokeMethod(Object owner,String methodName,Object[] args) throws Exception{//执行某对象的方法
Class<?> cls=owner.getClass();//获取对象的类型
Class[] argclass=new Class[args.length];//对象参数
for (int i = 0; i < argclass.length; i++) {
argclass[i]=args[i].getClass(); //获取参数的类型,即形参
}
Method method=cls.getMethod(methodName, argclass);//获取对象方法 argclass为形参
return method.invoke(owner, args);//args为实参
}
public void invokeMethod() throws Exception{//调用类的方法
Class<?> classType=TextReflect.class;
Object invokeTester=classType.newInstance();
Method addMethod=classType.getMethod("add", new Class[]{int.class,int.class});//调用TextReflect对象的add()方法,形参
Object result=addMethod.invoke(invokeTester, new Object[]{new Integer(100),new Integer(200)});//获取方法结果,实参
System.out.println("result : "+result);
Method echoMethod=classType.getMethod("echo", new Class[]{String.class});
Object echo=echoMethod.invoke(invokeTester, "Hello");
System.out.println("echo : "+echo);
}
public int add(int num1,int num2){
return num1+num2;
}
public String echo(String info){
return info;
}
public static void main(String[] args) throws Exception {
TextReflect tr = new TextReflect();// 实例化对象
Customer customer = new Customer("lingda", 20); // 对象初始化
customer.setId(new Long(1));
System.out.println("1.取得参数对象中的全部属性");
Customer cu = (Customer) tr.getProperty(customer);
System.out.println("用户信息:编号--"+cu.getId()+",名称--"+cu.getName()+",年龄--"+cu.getAge());
System.out.println("2.获取参数对象的属性值:");
String userName=(String) tr.getPrivatePropertyValue(customer, "name");
System.out.println("用户名称:"+ userName);
System.out.println("3.执行某对象的方法");
tr.invokeMethod(customer, "showInfo", new Object[]{"2000"});
System.out.println("4.调用本类的方法");
tr.invokeMethod();
}
}
源程序解读:
(1)getProperty()方法首先获得对象的类型及名称,其中"?"表示可获得任意类型的对象。再通过默认构造器创建一个新的对象。通过getDeclaredFields()方法获得传入对象的所有属性,循环遍历属性中,可以根据属性的名字的名字获得相应属性的getXxx和setXxx方法。最后调用对象中的getXxx方法,接收该方法的返回值。将获得的返回值传入setXxx方法中。
(2)getPrivatePropertyValue()方法根据传入的对象获得对象类型,根据传入的属性获得对象的指定属性。当访问的属性的访问修饰符为private时,需要设置setAccessible的值为true。这样便获得属性对应的值。
(3)在含有参数的invokeMethod()方法中,根据传入的对象、对象方法以及对象方法需要的参数获得相应的对象类型、带参数的方法。根据invoke()方法获得方法执行后返回的结果。
(4)在没有参数的invokeMethod()方法中创建TextReflect对象类型。根据getMethod()方法调用此对象的add()并设置方法的参数是整型,调用此对象的echo()方法并设置方法的参数是对象。根据invoke()方法获得方法返回的结果。
在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE),说明了这个注解该如何使用。
@Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。
@Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。
附注:你使用了注解,但是无法捕获到注解的相关信息,可能原因在于你使用的注解的生存周期没有设置正确,要在你的注解类前面设置如下两行代码
动态加载
===================
Java允许你在运行期动态加载。
类加载器
所有Java应用中的类都是被java.lang.ClassLoader类的一系列子类加载的。因此要想动态加载类的话也必须使用java.lang.ClassLoader的子类。
一个类一旦被加载时,这个类引用的所有类也同时会被加载。类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。
类加载体系
在Java中类加载是一个有序的体系。当你新创建一个标准的Java类加载器时你必须提供它的父加载器。当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。
类加载
类加载器加载类的顺序如下:
1、检查这个类是否已经被加载。
2、如果没有被加载,则首先调用父加载器加载。
3、如果父加载器不能加载这个类,则尝试加载这个类。
当你实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。在后面的段落会进行讲解。
动态类加载
动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的loadClass()方法。下面是个例子:
动态代理
===================
利用Java反射机制你可以在运行期动态的创建接口的实现。java.lang.reflect.Proxy类就可以实现这一功能。动态的代理的用途十分广泛,比如数据库连接和事物管理(transaction management)还有单元测试时用到的动态mock对象以及AOP中的方法拦截功能等等都使用到了动态代理。
常见用例
动态代理常被应用到以下几种情况中
数据库连接以及事物管理
单元测试中的动态Mock对象
自定义工厂与依赖注入(DI)容器之间的适配器
类似AOP的方法拦截器
1.数据库连接以及事物管理
Spring框架中有一个事物代理可以让你提交/回滚一个事物。它的具体原理在 Advanced Connection and Transaction Demarcation and Propagation一文中有详细描述,所以在这里我就简短的描述一下,方法调用序列如下:
Spring框架可以拦截指定bean的方法调用,你只需提供这个bean继承的接口。Spring使用动态代理来包装bean。所有对bean中方法的调用都会被代理拦截。代理可以判断在调用实际方法之前是否需要调用其他方法或者调用其他对象的方法,还可以在bean的方法调用完毕之后再调用其他的代理方法。
@Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。
@Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。
附注:你使用了注解,但是无法捕获到注解的相关信息,可能原因在于你使用的注解的生存周期没有设置正确,要在你的注解类前面设置如下两行代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
===================动态加载
===================
Java允许你在运行期动态加载。
类加载器
所有Java应用中的类都是被java.lang.ClassLoader类的一系列子类加载的。因此要想动态加载类的话也必须使用java.lang.ClassLoader的子类。
一个类一旦被加载时,这个类引用的所有类也同时会被加载。类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。
类加载体系
在Java中类加载是一个有序的体系。当你新创建一个标准的Java类加载器时你必须提供它的父加载器。当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。
类加载
类加载器加载类的顺序如下:
1、检查这个类是否已经被加载。
2、如果没有被加载,则首先调用父加载器加载。
3、如果父加载器不能加载这个类,则尝试加载这个类。
当你实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。在后面的段落会进行讲解。
动态类加载
动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的loadClass()方法。下面是个例子:
public class MainClass {
public static void main(String[] args){
ClassLoader classLoader = MainClass.class.getClassLoader();
try {
Class aClass = classLoader.loadClass("com.jenkov.MyClass");
System.out.println("aClass.getName() = " + aClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
===================动态代理
===================
利用Java反射机制你可以在运行期动态的创建接口的实现。java.lang.reflect.Proxy类就可以实现这一功能。动态的代理的用途十分广泛,比如数据库连接和事物管理(transaction management)还有单元测试时用到的动态mock对象以及AOP中的方法拦截功能等等都使用到了动态代理。
常见用例
动态代理常被应用到以下几种情况中
数据库连接以及事物管理
单元测试中的动态Mock对象
自定义工厂与依赖注入(DI)容器之间的适配器
类似AOP的方法拦截器
1.数据库连接以及事物管理
Spring框架中有一个事物代理可以让你提交/回滚一个事物。它的具体原理在 Advanced Connection and Transaction Demarcation and Propagation一文中有详细描述,所以在这里我就简短的描述一下,方法调用序列如下:
web controller --> proxy.execute(...);
proxy --> connection.setAutoCommit(false);
proxy --> realAction.execute();
realAction does database work
proxy --> connection.commit();
2.类似AOP的方法拦截器Spring框架可以拦截指定bean的方法调用,你只需提供这个bean继承的接口。Spring使用动态代理来包装bean。所有对bean中方法的调用都会被代理拦截。代理可以判断在调用实际方法之前是否需要调用其他方法或者调用其他对象的方法,还可以在bean的方法调用完毕之后再调用其他的代理方法。