12. Java反射

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("麻辣烫");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值