前言
Java的类在被类加载器加载完成后会在JVM堆中的方法区(jdk 8 改名为元空间)中生成一个对应类的class 对象,这个class对象中几乎包含了这个类的所有信息。我们通过反射获取到这个类,来对类进行分析或者实例化等操作。
Class 类中包含的信息
class类中包含的信息很多,这里列出部分。主要有字段、方法、构造器方法、注释、包名、类加载器、类实现的接口等。详细部分可以查看API手册
下面我们定义一个Person类尝试获取这个类的class对象并对其进行操作
@SimpleAnnotation
public class Person {
private String name;
private String sex;
private int id;
public Person(){}
@SimpleAnnotation
private void show(Integer id){
System.out.println("执行show()方法");
}
private Person(String name, String sex, int id) {
this.name = name;
this.sex = sex;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", id=" + id +
'}';
}
}
获取Person类的Class 对象
获取一个类的对象有四种方法,下面通过代码展示
/**
* 获取类的三种方式
* Class<?> person = Class.forName("entity.Person");
* Class<? extends Person> person = people.getClass();
* Class<Person> person = Person.class;
* 第四种方式:通过类加载器实现
*/
操作Person类的Class对象
通过类对应的Class实例对应的对象我们可以得到很多类相关的信息。这里有一处很有意思,在获取类的构造器方法时,传入Intger.class失败 ,int.class成功。换句话说int.class 和Integer.class对应了不同类对象。
public class ClazzInstance {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, InstantiationException {
Person people = new Person();
Class<Person> person = Person.class;
// 获取Filed
Field[] declaredFields = person.getDeclaredFields();//获取类中声明的所有字段
Field[] fields = person.getFields();//获取公有字段
Field field = person.getDeclaredField("name");//获取指定名称的字段,参数为String
/*System.out.println(field);
System.out.println(Arrays.toString(declaredFields));
System.out.println(Arrays.toString(fields));*/
// 获取构造器方法
Constructor<?>[] declaredConstructors = person.getDeclaredConstructors();// 获取类中所有的构造器方法
Constructor<?>[] constructors = person.getConstructors();
// 获取指定的构造器方法,参数为构造器方法参数的class对象
Constructor<Person> declaredConstructor = person.getDeclaredConstructor(String.class,String.class,int.class);
/*System.out.println(Arrays.toString(declaredConstructors));
System.out.println(Arrays.toString(constructors));
System.out.println(declaredConstructor);*/
// 获取Method
Method[] declaredMethods = person.getDeclaredMethods();
Method[] methods = person.getMethods();
// 相较构造器方法多了一个方法名(谁让构造器方法名固定呢)
Method showMethod = person.getDeclaredMethod("show", Integer.class);
/*System.out.println(Arrays.toString(declaredMethods));
System.out.println(Arrays.toString(methods));
System.out.println(showMethod);*/
// 获取类上的注解
Annotation[] annotations = person.getAnnotations();//返回此元素上存在的注释
Annotation[] declaredAnnotations = person.getDeclaredAnnotations();//返回直接出现在此元素上的注释
SimpleAnnotation declaredAnnotation = person.getDeclaredAnnotation(SimpleAnnotation.class);
System.out.println(Arrays.toString(declaredAnnotations));
System.out.println(declaredAnnotation);
// 获取方法上的注解
Method show = person.getDeclaredMethod("show", Integer.class);
show.getDeclaredAnnotations();
System.out.println(Arrays.toString(show.getDeclaredAnnotations()));
// 获取类所属包名和模块名
/*System.out.println(person.getPackageName());
System.out.println(person.getModule());*/
/*修改类中的字段、执行方法、利用构造器方法返回构建实例*/
field.setAccessible(true);
// 修改的实体对象,修改的名称
field.set(people,"zhangsan");
// System.out.println(people);
// 执行方法
showMethod.setAccessible(true);
// 在具有指定参数的指定对象上调用此方法对象表示的基础方法 obj 和 此方法的参数列表
showMethod.invoke(people,1);
declaredConstructor.setAccessible(true);
Person firstPerson = declaredConstructor.newInstance("lisi", "男", 101);
System.out.println(firstPerson);
}
}
针对代码中涉及到的Filed、Method、Constructor的时,我们在操作这些类的时候需要判断是否是可接近的(方法是否私有的),通过setAceesible(true)设置可接近。下面我们来看一下这三个类中的部分方法。
(1)Field
public Object get(Object obj) 取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容
public int getModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。
(2)Mehtod
Object invoke(Object obj, Object … args)执行目标方法
public Class<?> getReturnType()取得全部的返回值 public Class<?>[] getParameterTypes()取得全部的参数
public int getModifiers()取得修饰符
public Class<?>[] getExceptionTypes()取得异常信息
(3)Constructor
public T newInstance(Object … initargs)。返回创建的实例
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?>[] getParameterTypes();
反射的应用
这里我们试着利用反射完成动态代理。(省略接口和接口实现类的代码)
方法一:实现接口完成动态代理
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/
public class DynamicProxy {
public static void main(String[] args) {
Factory phoneFactory = new PhoneFactory();
Factory proxyInstance = (Factory) DynamicProxyFactory.getProxyInstance(phoneFactory);
proxyInstance.createProduct();
}
}
class DynamicProxyFactory {
public static Object getProxyInstance(Object obj) {
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
class MyInvocationHandler implements InvocationHandler {
private Object obj;
public void bind(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// proxy代理类 method需要执行的方法 args 执行方法的参数。
System.out.println("动态代理执行前的一些的工作");
// 执行的是被代理类的方法,不是代理类的方法。
Object returnValue = method.invoke(obj, args);
System.out.println("动态代理执行后的一些工作");
return returnValue;
}
}
- Proxy类: static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) 返回指定接口的代理实例,该接口将方法调用分派给指定的调用处理程序。
- InvocationHanler接口:Object invoke(Object proxy, 方法 method, Object[] args) 处理代理实例上的方法调用并返回结果。返回值Object对应方法的返回值。
- 代理类执行方法的流程:待了解
方法二:继承目标类完成动态代理
未完待续。。。