【Java SE】二十一、反射

反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

Java 反射机制提供的功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

基本操作

下面的例子中我们已知一个 Person 类,如下:

public class Person {
    private String name;
    public int age;
    
    public Person(String name, int age) {...} // 公有构造器初始化两个属性
    private Person(String name) {...} // 私有构造器初始化一个属性
    
    // ...Get/Set方法以及一个公有方法shou()和一个有返回值的私有方法showNation()
}

正常情况下,在 Person 类外部,不可以通过 Person,类的对象调用其内部私有结构,现在我们来试试反射:

public void test() throws Exception {
    // 通过反射,创建对象
    Class<Person> clazz = Person.class; // 每个类都是Class对象
    Constructor cons = clazz.getConstructor(String.class, int.class); // 获取Person指定构造器对象
    Person p = cons.newInstance("Tom", 12); // 创建Person对象
    System.out.println(p.getClass()); // 获取该对象的类,依旧是Class对象
    System.out.println(p.toString());
    
    // 通过反射,调用对象的方法和属性
    Field age = clazz.getDeclaredField("age"); // 获取属性
    age.set(p, 10); // 设置属性
    System.out.println(p.toString());
	Method show = clazz.getDeclaredMethod("show"); // 获取方法,可继续放入该方法的参数类型
	show.invoke(p); // 调用P的show方法,可继续放入该方法的参数

    // 通过反射,调用对象的私有方法和属性
    Constructor cons1 = clazz.getDeclaredConstructor(String.class);  // 获取私有构造器
    cons1.setAccessible(true);
    Person p1 = cons1.newInstance("Jerry");
    
    Field name = clazz.getDeclaredField("name"); // 获取私有属性
    name.setAccessible(true);
    name.set(p1, "HanMeimei"); // 设置属性
    
    Method showNation = clazz.getDeclaredMethod("showNation", String.class); // 获取私有方法
    showNation.setAccessible(true);
    String str = (String) showNation.invoke(p1, "中国"); // 调用方法以及接收返回值

    System.out.println(p1.toString() + " " + str);
}

该例展示了反射的动态特性,接下来我们一一讲解。

创建运行时类的对象

我们虽然能够通过构造器类的 newInstance() 创建对象,但我们通过 Class 类的 newInstance() 来实现。

Class<Person> clazz = Person.class;
Person obj = clazz.newInstance(); // 会调用空参构造器,确保运行时类有,且访问权限够
System.out.println(obj);

在 javabean 中要求提供一个 public 的空参构造器。原因如下:

  • 便于通过反射,创建运行时类的对象
  • 便于子类继承此运行时类时,默认调用 super() 时,保证父类有此构造器

获取运行时类的属性和方法结构

除了上述几种方式,还有其他方法:

Class clazz = Person.class;

// 获取运行时类及其父类的所有public属性和方法
Field[] fields = clazz.getFields();
Method[] methods = clazz.getMethods();
for(Field f : fields) {System.out.println(f);}
for(Method m : Methods) {System.out.println(f);}

// 获取运行时类声明的所有属性,不限访问权限,不含继承的属性和方法
Field[] declaredFields = clazz.getDeclaredFields();
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Field f : declaredFields) {System.out.println(f);}
for(Method m : methods) {System.out.println(m);}

// 获取属性其他结构
for(Field f : declaredFields) {
	// 1. 权限修饰符
    int modifier = f.getModifiers(); // 获取用数字表示的修饰符
    System.out.println(Modiflier.toString(modidier)); // 用内置类方法翻译一下
    
    // 2.数据类型
    Class type = f.getType(); // 获取数据类型
	System.out.println(type);
    
    // 3.变量名
    String name = f.getName();
    System.out.println(name);
}

// 获取方法其他结构
for(Method m : methods) {
	// 1.注解
    Annotation[] annos = m.getAnnotations();
    for(Annotation a : annos) {System.out.println(a);}
    
    // 2.权限修饰符
	System.out.print(Modifier.toString(m.getModifiers()) + "\t");
	
    // 3.返回值类型
    System.out.print(m.getReturnType().getName() + "\t");
    
    // 4.方法名
	System.out.print(m.getName() + "\n");

	// 5.形参列表
    Class[] parameterTypes = m.getParameterTypes();
    if(parameterTypes > 0) { // 确保有参数
        for(Class parameterType : parameterTypes) {
        	System.out.print(parameterType.getName() + " ");
        }
    }
    
    // 6.抛出的异常
    Class[] exceptionTypes = m. getExceptionTypes();
    if(exceptionTypes > 0){
        System.out.println("throws -> ");
        for(Class exceptionType : exceptionTypes){
            System.out.print(exceptionType.getName() + " ");
        }
    }

}

注:获取构造器也是如此,有 getXxx()getDeclaredXxx() 两个不同的方法

获取运行时类的父类及父类的泛型

Class clazz = Person.class;

Class superclass = clazz.getSuperclass(); // 获取父类
System.out.println(superclass);

Type genericSuperclass = clazz.getGenericSuperclass(); // 获取带泛型的父类
System.out.println(genericSuperclass);

ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = paramType.getActualTypeArguments(); // 获取泛型类型
System.out.println(actualTypeArguments[e]); // 还可以用.getTypeName()只获取名称

获取运行时类的其他结构

Class clazz = Person. class; 

Class[] interfaces = clazz.getInterfaces(); // 获取接口
for(Class c : interfaces) {System.out.println(c);}

Package pack = clazz.getPackage(); // 获取所在包
System.out.println(pack);

Annotation[] annotations = clazz.getAnnotations(); // 获取类注解
for(Annotation annos : annotations) {System.out.println(annos);}

调用运行时类中的指定内部结构

Class clazz = Person.class;
Person p = (Person) clazz.newInstance(); // 创建运行时类的对象

// 调用指定的属性
Field id = clazz.getField("id"); // 只能获取public修饰的,通常不采用
id.set(p, 1001); // 设置当前属性的值,参数1: 指明设置哪个对象的属性;参数2: 将此属性值设置为多少
System.out.println(id.get(p));

Field name = clazz.getDeclaredField("name") ; // 可获取私有属性,开发常用
name.setAccessible(true); // name为private修饰,需要设置可修改
name.set(p, "Tom");
System.out.println(name.get(p));

// 调用指定的某个方法
Method show = clazz.getDeclaredMethod("show", String.class); // 参数1: 指明获取的方法名;参数2: 指明方法的形参列表
show.setAccessible(true); // show为private修饰,也需要设置可修改
// invoke()的返回值即为对应类中调用的方法的返回值,默认类型为Object
System.out.println(show.invoke(p, "CHN")); // 调用方法,参数1: 方法的调用者;参数2: 给方法形参赋值的实参

Method showDesc = clazz.getDeclaredMethod("showDesc"); // 该方法为静态方法,无参数和返回值
showDesc.setAccessible(true); // 调用静态属性与静态方法类似
showDesc.invoke(Person.class); // 调用静态方法,调用对象也可以写null,如invoke(null),效果相同

// 调用指定的构造器 - 不常用
Constructor constructor = clazz.getDeclaredConstructor(String.class); // 得指明参数列表
constructor.setAccessible(true); // 确保可访问
Person per = (Person) constructor.newInstance("Tom"); // 创建对象

注:调用私有结构时,记得添加 setAccessible(true) 确保可访问

获取 Class 对象的方式

加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

public class ReflectionTest {
    public void test() {
        // 方式一: 调用运行时类的属性: .class
        Class clazz1 = Person.class;
        
        // 方式二: 通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        
        // 方式三: 调用Class的静态方法: forName(String classPath) -> 常用
        class clazz3 = Class.forName("com.atguigu.java.Person"); // 会抛异常,要处理
        System.out.println(clazz1 == clazz2 == clazz3); // true
        
        // 方式四: 使用类加载器: CLassLoader
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
        System.out.println(clazz4);
    }
}

注意事项:

  • 类、接口、数组、枚举、注解 (RUNTIME)、基本数据类型、void 以及 Class,这些类型都可以有 Class 对象
  • void 也是数据类型,而 Class 也是类,它们都有 Class 对象
  • 只要数组的元素类型维度一样,就是同一个 Class

类加载器

类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下三个类型的加载器:

  1. 引导类加载器:用 C++ 编写的,是 JVM 自带的类加载器,负责 Java 平台核心库,用来装载核心类库,该加载器无法直接获取
  2. 扩展类加载器:负责 re/ib/ext 目录下的 jar 包或 -D java.ext.dirs 指定目录下的 jar 包装入工作库。
  3. 系统类加载器:负责 java -classpath 或 -D java.class.path 所指的目录下的类 jar 包装入工作,是最常用的加载器。
public class ClassLoaderTest {
    public void test() {
        //对于自定义类,使用系统类加载器进行加戴
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);
        //调用系统类加载器的getParent():获取扩展类加载器
        ClassLoader classLoader1 = classLoader.getParent();
        System.out.println(classLoader1);
        //调用扩展类加载器的getParent():无法获取引导类加载器,其主要负责加载java的核心类库,无法加载自定义类。
        ClassLoader classLoader2 = classLoader1.getParent(); 
        System.out.println(classLoader2);
        // 无法获取核心类的加载器,也就是引导类加载器
        ClassLoader classLoader3 = String.class.getClassLoader();
        System.out.println(classLoader3); 
    }
}

我们也可以用类加载器来读取配置文件,如下代码接上:

public void test2() throws Exception {
    Properties pros = new Properties(); // 创建配置对象
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); // 获取当前类加载器
    classLoader.getResourceAsStream("jdbc.properties"); // 读取配置
    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    System.out.println("user = "+ user + ",password = " + password);
}

动态代理

使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

这里先写一个静态代理的例子方便对比:

interface ClothFactory{ // 要干的事
	void produceCloth();
}

//代理类
class ProxyClothFactory implements ClothFactory {
    private ClothFactory factory; // 用被代理类对象进行实例化
    
    public ProxyClothFactory(ClothFactory factory) {
    	this.factory = factory;
    }
    
    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");
        factory.produceCloth();
        System.out.println("代理工厂做一些后续的收尾工作");
    }
}

//被代理类
class NikeClothFactory implements ClothFactory {
    @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();
    }
}

静态代理特点:代理类和被代理类在编译期间就确定下来了。

下面是一个动态代理的例子:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

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) { // obj: 被代理类的对象
        MyInvocationHandler hander = new MyInvocationHandler();
        hander.bind(obj);
    	return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), hander);
	}
}
                           
class MyInvocationHandler implements InvocationHandler {
    private Object obj;// 需要使用被代理类的对象进行赋值
    
    public void bind(Object obj){
    	this.obj = obj;
    }

    // 当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法: invoke()
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	// method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
		return method.invoke(obj, args); // obj: 被代理类的对象
    }
}

//测试类
public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        // proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        // 当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        System.out.println(proxyInstance.getBelief());
        proxyInstance.eat("四川麻辣烫");
        
        /* 为前面的静态代理中的被代理类生成动态代理
        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyClothFactory.produceCloth();
        */
    }
}