1 概述
1.1 定义
在程序执行期间,通过反射提供的API,可以获取类的内部信息,也可以操作任何对象的属性和方法。
1.2 作用
- 在运行时判断一个对象所属的类;
- 在运行时动态创建对象;
- 在运行时获取类内部的完整结构;
- 在运行时操作对象的属性和方法;
- 在运行时处理注解;
- 在运行时生成动态代理;
1.3 注意事项
一般情况下,使用new来创建对象;但是如果在程序运行过程中才能确定创建的对象,使用反射来创建对象。
1.4 反射与封装性的关系
可以通过反射来获取对象的私有内容,但是这与封装性并不冲突。因为封装性存在的意义是隐藏私有内容,为了给使用者提供更方便、更好用的公共接口。但是调用者应该有权限获取对象中私有的内容。
1.5 反射的应用
Class clazz = Person.class;
// 1. 通过反射,调用Person类的构造器,创建Person类对象
// 获取Person的构造器
Constructor constructor = clazz.getConstructor(String.class,int.class);
// 通过构造器创建对象
Object jack = constructor.newInstance("jack", 20);
Person p = (Person) jack;
System.out.println(p.toString());
// 2. 通过反射,调用对象的属性和方法
// 调用属性
Field age = clazz.getDeclaredField("age");
age.set(p, 10); // 给属性赋值
System.out.println(p.toString());
// 调用方法
Method test = clazz.getDeclaredMethod("test");
test.invoke(p);
// 通过反射,可以调用类的私有结构. 例如:私有构造器,方法,属性
// 私有构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(true);
Person o = (Person) declaredConstructor.newInstance("123");
System.out.println(o.toString());
// 私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(o, "zs");
System.out.println(o.toString());
// 私有方法
Method test1 = clazz.getDeclaredMethod("test1");
test1.setAccessible(true);
test1.invoke(o);
2 Class类
2.1 Class类的理解
- java源文件经过javac.exe编译之后生成一个或多个字节码文件,然后使用java.exe对某个字节码文件进行解释运行,就相当于将这个字节码文件加载到内存中,这就是类的加载过程。加载到内存中的类,称作运行时类,运行时类是Class类的一个实例,也就是说类也是一个对象。
- Class类是描述类的类。
- 一个加载的类在 JVM 中只会有一个Class实例,也就是说每个类的运行时类是唯一的,相当于是单例模式。
- 作用:通过Class对象,可以获取运行时类的完整结构。
2.2 获取Class实例的四种方式
在开发中,经常使用方式3来获取Class实例,因为方式3可以传入任意类名,在编译期不会出现异常,更有利于动态性的实现。
// 方式1:通过运行时类的属性.class
Class<Person> clazz1 = Person.class;
// 方式2:通过类的实例的getClass方法
Person person = new Person();
Class<? extends Person> clazz2 = person.getClass();
// 方式3:通过Class类的静态方法forName,需要传入全类名,即包括包名的类名,需要处理异常
Class<?> clazz3 = Class.forName("包名.Person");
// 方式4:类加载器,此处传入的也是全类名
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?> clazz4 = classLoader.loadClass("包名.Person");
System.out.println(clazz1 == clazz2); // true 同一个对象
System.out.println(clazz2 == clazz3); // true
2.3 哪些类型可以有Class对象?
除了类(外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类)以外,接口、枚举类、注解、数组、void、基本数据类型都有Class对象。
注意:对于数组来说,只要类型和维度一样,那么就是同一个Class实例。
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11); // true
2.4 类的加载过程
2.4.1 类加载器
类加载器作用是用来把类(class)装载进内存的。下面三种加载器是父子类,引导类加载器是扩展类加载器的父类,扩展类加载器是系统类加载器的父类。
- 引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库(例如String),该加载器无法直接获取,即我们不能使用引导类加载器加载自定义类。
- 扩展类加载器:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库。
- 系统类加载器:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器。自定义类就是使用此加载器进行加载的。
2.4.2 实例
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m); // 100
} }
class A {
静态代码块和静态变量的执行顺序取决于在程序中的出现位置
static { m = 300;}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并
产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }
2.4.3 使用ClassLoader加载配置文件
// 两种方式都是使用Properties进行加载的
Properties properties = new Properties();
// 方式1:使用IO流读取配置文件
FileInputStream fis = new FileInputStream("src\\test.properties");
properties.load(fis);
// 方式2:使用类加载器读取配置文件
ClassLoader classLoader = this.getClass().getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("test.properties");
properties.load(resourceAsStream);
String name = properties.getProperty("name");
String age = properties.getProperty("age");
System.out.println(name + age);
2.5 反射创建对象
- 在开发中,大部分情况下都会使用Class类中的newInstace方法创建运行时类的对象。 因为newInstance方法调用的是运行时类的空参构造器,通用性更强
- 如果想要通过有参构造器创建对象,可以使用通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器,然后调用形参类型的构造器的newInstance方法来创建对象。
// 通过反射,创建运行时类的对象
Class<Person> clazz = Person.class;
/**
* 在开发中,大部分情况下都会使用Class类中的newInstace方法创建运行时类的对象。
* 因为newInstance方法调用的是运行时类的空参构造器,通用性更强。
*
* 调用newInstance方法需要满足的条件:
* 1. 运行时类必须要有空参构造器;
* 2. 空参构造器的访问权限要够,通常设置为public
*/
Person person = clazz.newInstance();
System.out.println(person);
// 获取Person的构造器
Constructor constructor = clazz.getConstructor(String.class,int.class);
// 通过构造器创建对象
Object jack = constructor.newInstance("jack", 20);
Person p = (Person) jack;
System.out.println(p.toString());
2.6 javaBean中要求提供空参构造器的原因
- 使用反射创建运行时类的对象时,需要调用空参构造器;
- 子类构造器中如果没有使用super显示调用父类中的构造器,那么会默认有一个super()调用父类的空参构造器。
3 通过反射获取运行时类的完整结构
3.1 获取类中的属性
// 获取Person的Class实例
Class<Person> personClass = Person.class;
// 1. 获取Person类及其父类中所有public的属性
Field[] fields = personClass.getFields();
// 2. 获取Person类中所有的属性,不受权限修饰符的限制(不能获取父类中的属性)
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.print(declaredField); //private java.lang.String Person.name
// 获取属性的权限修饰符
System.out.print(Modifier.toString(declaredField.getModifiers())); //private
// 获取属性的数据类型
System.out.print(declaredField.getType()); // java.lang.String
// 获取属性的变量名
System.out.print(declaredField.getName()); //name
System.out.println();
}
3.2 获取类中的方法
// 获取Person类以及其父类中的public方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println();
// 获取Person类中所有的方法,不受权限修饰符的限制(不能获取父类中的方法)
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
// 获取权限修饰符
System.out.println(Modifier.toString(declaredMethod.getModifiers()));
// 获取返回值类型
System.out.println(declaredMethod.getReturnType());
// 获取方法名
System.out.println(declaredMethod.getName());
// 获取参数列表
// getParameterTypes()--参数类型
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
if (!(parameterTypes.length == 0 && parameterTypes == null))
for (Class<?> parameterType : parameterTypes) {
System.out.println(parameterType.getName());
}
// 获取抛出的异常
Class<?>[] exceptionTypes = declaredMethod.getExceptionTypes();
if (exceptionTypes.length > 0) {
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(exceptionType.getName());
}
}
// 获取方法的注解
Annotation[] annotation = declaredMethod.getAnnotations();
if(annotation.length > 0){
for (Annotation annotation1 : annotation) {
System.out.println(annotation);
}
}
3.3 获取类所在的包
Class<Person> personClass = Person.class;
Package aPackage = personClass.getPackage();
3.4 获取类的构造器
获取构造器中的权限修饰符、返回值类型、构造器名称、参数列表和类的方法一样。
// 获取Person的Class实例
Class<Person> personClass = Person.class;
// getConstructors():获取当前运行时类中public修饰的构造器,不能获取父类的构造器
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
// getDeclaredConstructors():获取当前运行时类中所有的构造器,不受权限修饰符的限制
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
3.5 获取类的父类
3.4.1 获取类的父类(不带泛型)
Class<Person> personClass = Person.class;
System.out.println(personClass.getSuperclass());
3.4.2 获取类的父类(带泛型)
Class<Person> personClass = Person.class;
System.out.println(personClass.getGenericSuperclass());
3.4.3 获取类的父类的泛型
// 获取Person的Class实例
Class<Person> personClass = Person.class;
Type genericSuperclass = personClass.getGenericSuperclass();
ParameterizedType p = (ParameterizedType) genericSuperclass;
Type[] typeName = p.getActualTypeArguments();
for (Type type : typeName) {
// 方法1:调用getTypeName()
System.out.println(type.getTypeName());
// 方法2:强转为Class,然后调用getName()
System.out.println(((Class)type).getName());
}
3.6 获取类的接口
Class<Person> personClass = Person.class;
Class<?>[] interfaces = personClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface.getName());
}
3.7 获取类的注解
Annotation[] annotations = personClass.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
4 通过反射调用运行时类的指定结构
4.1 属性
注意:需要设置setAccessible(true)
,如果不设置,操作非public的属性会抛出异常。
// 获取运行时类的Class对象
Class<Person> personClass = Person.class;
// 创建运行时类Person对象
Person person = personClass.newInstance();
// 获取运行时类Person的name属性
Field name = personClass.getDeclaredField("name");
// 设置当前属性是可访问的
name.setAccessible(true);
// 设置person对象的name属性值
name.set(person,"zs");
// 获取person对象的name属性值
Object o = name.get(person);
静态属性的调用
因为gender是静态属性,可以通过类直接调用,又因为gender是通过personClass获取的,所以set中的参数1可以为null,参数2是给gender赋值
// 静态属性的调用
Field gender = personClass.getDeclaredField("gender");
gender.setAccessible(true);
// 因为gender是静态属性,可以通过类直接调用,又因为gender是通过personClass获取的,所以set
// 中的参数1可以为null,参数2是给gender赋值
gender.set(null,12);
Object o1 = gender.get(null);
System.out.println(o1);
4.2 方法
注意:需要设置setAccessible(true)
,如果不设置,操作非public的方法会抛出异常。
// 操作类中的方法
Class<Person> personClass = Person.class;
// 创建运行时类Person对象
Person person = personClass.newInstance();
// 获取method方法,参数1是方法名,参数2是参数类型的Class实例
Method method = personClass.getDeclaredMethod("method",String.class);
// 设置方法为可访问的
method.setAccessible(true);
// 调用方法,参数1是运行时类的对象,参数2是方法的实参。
// invoke的返回值是方法method的返回值
Object invoke = method.invoke(person, "123");
System.out.println(invoke);
静态方法
Method test11 = personClass.getDeclaredMethod("test11", int.class);
test11.setAccessible(true);
// 参数1是null与静态属性同理
Object invoke1 = test11.invoke(null, 10);
System.out.println(invoke1);
4.3 构造器
开发中经常使用的是空参构造器,因为更通用。方法2不经常使用。
- 空参构造器
Class<Person> personClass = Person.class;
Person person1 = personClass.newInstance();
System.out.println(person1);
- 指定带参构造器
Class<Person> personClass = Person.class;
// 获取运行时类指定的构造器,参数是构造器的形参列表
Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
// 设置构造器是可访问的
declaredConstructor.setAccessible(true);
// 调用此构造器创建对象
Person person = declaredConstructor.newInstance("1231");
System.out.println(person);
5 反射的应用:动态代理
5.1 代理模式的原理
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
5.2 细节
- 代理类完成一些通用的功能,被代理类完成核心功能。例如租房,找房和谈价格由房产中介来完成,而付钱和居住是由用户来完成,只有付钱之后才能居住。
- 代理类和被代理类需要实现同一接口。
5.3 静态代理
5.2.1 缺点
代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
5.2.2 实例1
多线程中实现Runnable接口来创建线程:
两者都实现了Runnable接口
class RunnableTest implement Runnable{} 被代理类
class Thread implement Runnable{} 代理类
main(){
Thread t = new Thread(new RunnableTest());
t.start();
}
5.2.3 实例2
public class ProxyTest {
public static void main(String[] args) {
// 创建被代理类的对象
AntaFactory antaFactory = new AntaFactory();
// 创建代理类的对象
ProxyFactory p = new ProxyFactory(antaFactory);
// 调用代理类中的方法
p.produce();
}
}
/*
接口,被代理类和代理类都需要实现这个接口
*/
interface ClothFactory{
void produce();
}
/*
被代理类
*/
class AntaFactory implements ClothFactory{
@Override
public void produce() {
System.out.println("生产运动鞋");
}
}
/*
代理类
*/
class ProxyFactory implements ClothFactory{
private AntaFactory antaFactory; // 被代理类的对象
public ProxyFactory(AntaFactory antaFactory){
this.antaFactory = antaFactory;
}
@Override
public void produce() {
System.out.println("准备");
antaFactory.produce();
System.out.println("善后");
}
}
5.4 动态代理
5.4.1 定义
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
5.4.2 优点
在运行期创建代理类,更加灵活,而且创建代理类的类是通用的。
5.4.3 步骤
- 创建接口和被代理类
/*
接口,被代理类和代理类都需要实现这个接口
*/
interface ClothFactory {
void produce(String type);
String design();
}
/*
被代理类
*/
class AntaFactory implements ClothFactory {
@Override
public void produce(String type) {
System.out.println("生产" + type);
}
@Override
public String design() {
System.out.println("设计");
return "";
}
}
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作。invoke方法主要的作用是当通过代理类的对象调用方法a时,动态的去调用被代理类中的同名方法a。
class MyInvocationHandler implements InvocationHandler {
private Object obj; // 被代理类的对象
public void bind(Object obj){
this.obj = obj; // 给被代理类的对象赋值
}
/*
proxy:代理类
method:代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
args:方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法前添加代码");
// obj是被代理类的对象,returnValue是方法的返回值
Object returnValue = method.invoke(obj, args);
System.out.println("调用方法后添加代码");
return returnValue;
}
}
- 通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)动态创建代理类,并调用方法
public class ProxyTest {
public static void main(String[] args) {
// 创建被代理对象
AntaFactory antaFactory = new AntaFactory();
// 将被代理对象绑定到handler,用于方法的调用
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(antaFactory);
// 通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)动态创建代理类
// 参数传入代理类加载器,接口,用于方法调用的handler
ClothFactory o = (ClothFactory)Proxy.newProxyInstance(antaFactory.getClass().getClassLoader(), antaFactory.getClass().getInterfaces(), handler);
// 通过代理类调用方法
String design = o.design();
o.produce("shoes");
}
}
也可以采用匿名内部类的方式创建InvocationHandler的实现类对象
ClothFactory o = (ClothFactory)Proxy.newProxyInstance(antaFactory.getClass().getClassLoader(), antaFactory.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
插入通用处理
Object invoke = method.invoke(antaFactory, args);
插入通用处理
return invoke;
}
});
5.5 动态代理与AOP(Aspect Orient Programming)
- 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象(被代理类)生成动态代理。
- 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象(被代理类),AOP代理包含了目标对象(被代理类)的全部方法。但AOP代理中的方法与目标对象(被代理类)的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。
class MyInvocationHandler implements InvocationHandler {
private Object obj; // 被代理类的对象
public void bind(Object obj){
this.obj = obj; // 给被代理类的对象赋值
}
/*
proxy:代理类
method:代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
args:方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法前添加代码"); // 通用处理
// obj是被代理类的对象,returnValue是方法的返回值
Object returnValue = method.invoke(obj, args);
System.out.println("调用方法后添加代码"); // 通用处理
return returnValue;
}
}