1. Java反射机制概述
1.1 动态语言&静态语言
- 动态语言:指在运行时可以改变其结构的语言。如:Object-C、C#、JavaScript、PHP、Python、Erlang
- 静态语言:运行时结构不可变的语言。如:Java、C、C++
- Java不是动态语言,但Java可以利用反射机制、字节码操作获得类似动态语言的特性,称为“准动态语言”
1.2 反射概述
- 反射是动态语音的关键,反射机制允许程序在执行期间借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象,该对象包含了完整的类的结构信息。在这个对象就像一面镜子,透过镜子看到类的结构称为“反射”
1.3 Java反射机制的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
1.4 反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
2. Class类&Class实例
2.1 类的加载过程
- 程序经过javac.exe命令后,会生成一个或多个字节码文件(.class文件)
- 接着使用java.exe命令对某个字节码文件进行解释运行,此过程将该字节码文件加载到内存中,称为类的加载
- 加载到内存中的类,称为运行时类,代表Class类的一个实例,即一个Class类的实例就对应一个运行时类
2.2 获取Class实例的4种方式
- 通过运行时类的class属性获取
- 通过运行时类的对象调用getClass()方法获取
- 调用Class的静态方法forName(String classPath)获取(体现反射的动态性)
- 使用类的加载器ClassLoader获取
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);
Person person = new Person();
Class<? extends Person> clazz2 = person.getClass();
System.out.println(clazz2);
Class<?> clazz3 = Class.forName("Person");
System.out.println(clazz3);
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class<?> clazz4 = classLoader.loadClass("Person");
System.out.println(clazz4);
2.3 可以创建Class实例的类型
- class:外部类、成员内部类、静态内部类、局部内部类、匿名内部类、CLass本身
- 接口、数组、枚举类、注解、基本数据类型、void
3. 类的加载与ClassLoader的理解
3.1 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过以下3个步骤来对该类进行初始化:
- 类的加载(Load):将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表该类的java.lang.Class对象,作为方法区中类数据的引用地址。所有需要访问和使用类数据只能通过这个Class对象,这个加载过程需要类加载器参与
- 类的链接(Link):将Java类的二进制代码合并到JVM的运行状态之中。确保加载的类信息符合JVM规范,正式为类变量(static)分配内存并设置类变量的默认初始值,虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)
- 类的初始化(Initialize):执行类构造器< clinit >()方法的过程(类构造器是构造类的信息,不是构造类对象的构造器)。将编译期收集的类变量的赋值动作和静态代码块中的语句执行
3.2 类加载器(ClassLoader)
类的加载器用来把类装载仅内存,JVM规定了几种类型的类加载器:
- 引导类加载器(Bootstap ClassLoader):用C++编写的、JVM自带的加载器,负责加载Java平台核心库,该加载器无法直接获取
- 扩展类加载器(Extension ClassLoader):负责将jre/lib/ext目录下的jar包装入工作库
- 系统类加载器(System ClassLoader):负责将java.class.path目录下的类和jar包装入工作库,是最常用的加载器
- 自定义类加载器
//自定义类,使用系统类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//引导类加载器(无法直接获取,返回null)
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
标准的JavaSE类加载器可以按要求查找类,一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,JVM垃圾回收机制可以回收这些Class对象
3.3 使用CLassLoader加载配置文件
Properties properties = new Properties();
//方式一:默认路径在当前module下
//FileInputStream fileInputStream = new FileInputStream("jdbc.properties");
//properties.load(fileInputStream);
//方式二:默认路径在当前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("jdbc.properties");
properties.load(resourceAsStream);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
System.out.println(user);
System.out.println(password);
4. 运行时类
4.1 创建运行时类的对象
调用Class对象的newInstance()方法,该方法会调用类的空参构造器来创建运行时类的对象。该方法正常运行需要2个要求:① 运行时类必须提供空参构造器;② 空参构造器的权限需要够
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
System.out.println(person);
4.2 获取运行时类的完整结构
4.2.1 获取属性
- getFields():获取当前运行时类及其父类中声明为public权限的属性
- getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类)
对所获得的属性调用相应的方法,可获得对应属性的:权限修饰符、数据类型、变量名
4.2.2 获取方法
- getMethods():获取当前运行时类及其父类中声明为public权限的方法
- getDeclaredMethods():获取当前运行时类中声明的所有方法
对所获得的方法调用相应的方法,可获得对应方法的:注解、权限修饰符、返回值类型、方法名、形参列表、异常
- getAnnotations():获取方法上的注解(注解必须是RUNTIME类型才可获取)
4.2.3 获取构造器
- getConstructors():获取当前运行时类中声明为public的构造器(不包括父类)
- getDeclaredConstructors():获取当前运行类中所有构造器
4.2.4 获取父类及父类的泛型
- getSuperclass():获取当前运行时类的父类
- getGenericSuperclass():获得当前运行时类带泛型的父类
- getActualTypeArguments():获取父类的泛型
4.2.5 获取接口、包、注解
- getInterfaces():获取当前运行时类的接口
- getPackage():获取当前运行时类所在的包
- getAnnotations():获取当前运行时类上的注解
4.3 调用运行时类的指定结构
4.3.1 调用属性
- getField(String name):获取对象的某个public属性(不通用)
- getDeclaredField(String name):获取对象的某个属性
- setAccessible(true):将某个属性设为可访问
- get(obj):获取某个对象该属性的值(静态属性可写null)
- set(obj,value):设置某个对象该属性的值
4.3.2 调用方法
- getMethod(String name,Class… parameterTypes):获取public方法(不通用)
- getDeclaredMethod(String name,Class… parameterTypes):获取方法
- setAccessible(true):将某个方法设为可访问
- invoke(Object obj,Object… args):执行obj对象的该方法,返回所调用方法的返回值(静态方法不用知道具体对象,可写null)
4.3.3 调用构造器
- newInstance():调用空参构造器生成对象(运行时类调用,最常用)
- getDeclaredConstructor(Class… parameterTypes):获取知道构造器
- setAccessible(true):将某个构造器设为可访问
- newInstance(Object… args):调用此构造器创建运行时类的对象(运行时类的构造器调用)
Class<Person> aClass = Person.class;
Constructor<Person> constructor = aClass.getConstructor(String.class, int.class);
Person tom = constructor.newInstance("Tom", 12);
System.out.println(tom.toString());
Field age = aClass.getDeclaredField("age");
age.setAccessible(true);
age.set(tom, 10);
System.out.println(tom);
Method show = aClass.getDeclaredMethod("show");
show.invoke(tom);
5. 反射的应用:动态代理
5.1 代理设计模式
5.1.1 原理
使用一个代理将对象包装起来,然后用该代理取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
5.1.2 静态代理
代理类和目标对象类(被代理类)都是在编译期间确定下来,不利于程序的扩展。且每个代理类只能为一个接口服务,这样程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
5.1.3 动态代理
客户通过代理类来调用其他对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象
5.2 静态代理
interface ClothFacotry {
void produceCloth();
}
class ProxyClothFactory implements ClothFacotry {
private ClothFacotry facotry;
public ProxyClothFactory(ClothFacotry facotry) {
this.facotry = facotry;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做准备工作");
facotry.produceCloth();
System.out.println("代理工厂做收尾工作");
}
}
class NikeClothFactory implements ClothFacotry{
@Override
public void produceCloth() {
System.out.println("Nike工厂生产");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
NikeClothFactory nike = new NikeClothFactory();
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
5.3 动态代理
动态代理步骤:
- 创建一个实现接口InvocationHandler的类,其invoke()方法完成代理的具体操作
- 创建被代理的类及接口
- 通过Proxy类的静态方法newProxyInstance创建一个代理类
- 使用代理类调用方法
interface Human {
String getBelief();
void eat(String food);
}
class SuperMan implements Human {
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
class ProxyFactory {
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 {
return method.invoke(obj, args);
}
}
public class DynamicProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
System.out.println(proxyInstance.getBelief());
proxyInstance.eat("麻辣烫");
}
}