反射

150 阅读7分钟

反射相关API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器

Class类

在Object类中定义了public final Class getClass()方法,此方法返回值的类型是一个Class类,此类是Java反射的源头,通过对象反射求出类的名称。

image.png

获取Class对象的方式:

  • 调用类的属性:.class
  • 调用类的对象:getClass()
  • 调用Class的静态方法:forName(String classPath)

当获取到Class对象,会在内存中缓存一定的时间,在此时间用不同的方式获取都是同一个Class对象。


以下类型有Class对象:

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • interface:接口
  • []:数组(数组的元素类型、维度一样,就是同一个Class对象)
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void

创建Class类的对象

newInstance():创建对应的Class类的对象,内部调用了Class类的空参构造器

调用Class类的指定方法

  1. getMethod(String name,Class…parameterTypes):获得一个Method对象,并设置此方法操作时所需要的参数类型。

  2. Object invoke(Object obj, Object...args):返回原方法返回值

    1. obj:方法调用者,若原方法若为静态方法,可为null
    2. args:具体参数

setAccessible():参数值为true,则指示反射的对象在使用时取消Java语言访问检查

类的初始化过程与ClassLoader

没学 JVM 之前不用深究,做个简单了解即可

什么时候开始类的初始化:

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化

image.png

  • Load:将class文件字节码内容加载到内存中,生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器(ClassLoader)参与。
  • Link:正式为类变量(static)分配内存并设置类变量默认初始值的阶段。
  • Initialize:执行类构造器()方法的过程,构造类信息,给类变量赋值。跟对象构造器不是同一个东西

ClassLoader:

image.png

系统类加载器(加载自定义类)常用方法:

  • ClassLoader classloader = ClassLoader.getSystemClassLoader():获取一个系统类加载器
  • classloader = classloader.getParent():获取系统类加载器的父类加载器,即扩展类加载器
  • classloader = classloader.getParent():获取扩展类加载器的父类加载器,即引导类加载器

代理

代理设计模式:

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

静态代理

PA

特点:

  1. 静态代理要求原始对象和代理对象实现同一个业务接口。代理对象中的核心功能是由原始对象来完成,代理对象负责增强功能
  2. 代理对象在编译时就已经确定

EC

需求:有个明星(原始对象),档期很满,我们想约这个明星来学校表演。我们只能通过他的助理来约他,助理就是(代理对象)。具体的时间、地点、场合、费用(功能增强)都只能跟助理来谈。助理完全负责明星的所有行程,并且安排明星来表演。

image.png

实现代码如下:

  1. 定义业务接口
 //描述:定义服务层业务接口--->谁实现接口谁就唱歌
 public interface Service {
     //表演唱歌的
     void sing();
 }
  1. 定义原始对象
 public class SuperStarLiu implements Service {
  
     @Override
     public void sing() {
         System.out.println("我是刘德华,我正在唱歌...");
     }
 }
  1. 定义代理对象
 //明星唱歌表演的代理对象,完成歌手唱歌表演之外的其它业务
 public class Agent implements Service {
     //1.类中的成员变量设计为接口
     public Service target; //目标对象
  
     //2.方法的形参设计为接口
     public Agent(Service target) { //传入目标对象
         this.target = target;
     }
  
     @Override
     public void sing() {
         System.out.println("预订时间");
         System.out.println("预订场地");
         //3.调用时接口指向实现类
         target.sing();//目标对象开始唱歌(谁来谁唱歌)
         System.out.println("结算费用");
     }
 }
  1. main 方法调用
 public static void main(String[] args) {
     Service agent = new Agent();
     agent.sing();
 }

动态代理

动态代理是指客户通过代理类来调用目标对象的方法,并在程序运行时根据需要动态创建目标类的代理对象,这种情况下,代理类并不是在Java代码中定义的。

对比静态代理的优点:

  • 不用定义代理类
  • 不用关心目标对象,一个动态代理工厂就能生成任何目标对象的代理对象

相关 API

Proxy :专门完成代理的操作类,是所有动态代理类的父类,作用是为一个或多个接口动态地生成实现类

  • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) :创建一个动态代理类所对应的 Class 对象

  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) :创建一个动态代理对象

    • loader:类加载器
    • interfaces:目标类实现的全部接口
    • h:InvocationHandler 接口的实现类对象

InvocationHandler:动态代理机制中的一个接口,在实现类的 invoke() 中编写增强逻辑,当代理对象调用方法时,会自动调用方法 invoke()

Object invoke(Object proxy, Method method, Object[] args)

  • proxy:代理对象
  • method:目标对象中要调用的方法
  • args:方法调用时传递的参数

代码实现

需要解决的两个重难点:

  1. 如何根据加载到内存中的目标类,动态的创建一个代理类及其对象
  2. 通过代理类的对象调用方法时,如何动态的去调用目标类中的同名方法

代码执行步骤如下:

  1. 调用动态代理工厂生成目标对象的代理对象
  2. 代理对象调用方法时,会走 invoke() 方法,里面可以调用目标对象的方法,也能写增强逻辑
  3. 代理对象调用方法返回值是 invoke() 的返回值

具体代码如下:

  1. 定义接口:
 interface Human {
     String getBelief();
 ​
     void eat(String food);
 }
  1. 定义目标类
 class SuperMan implements Human {
     @Override
     public String getBelief() {
         return "我相信我会飞";
     }
 ​
     @Override
     public void eat(String food) {
         System.out.println("我喜欢吃" + food);
     }
 }
  1. 定义动态代理工厂
 class ProxyFactory{
     //调用此方法返回一个代理对象,解决重难点1
     public static Object getProxyInstance(Object obj){ //obj:目标对象
         MyInvocationHandler handler = new MyInvocationHandler();
 ​
         handler.bind(obj);
 ​
         return Proxy.newProxyInstance(obj.getClass().getClassLoader(), 
                                       obj.getClass().getInterfaces(), 
                                       handler);
     }
 }
  1. 定义 MyInvocationHandler
 class MyInvocationHandler implements InvocationHandler{
 ​
     private Object obj;
 ​
     public void bind(Object obj){ //对目标对象进行赋值
         this.obj = obj;
     }
 ​
     //代理类的对象调用方法时,会自动调用如下方法 invoke
     //将目标类执行的方法功能声明在 invoke
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 ​
         //目标对象调用的方法,解决重难点2
         Object returnValue = method.invoke(obj, args);
         //目标对象调用方法的返回值作为当前方法的返回值
         return returnValue;
     }
 }
  1. 编写 main 调用
 public static void main(String[] args) {
 ​
     //传入目标对象
     SuperMan superMan = new SuperMan();
     //proxyInstance:代理对象
     Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
     //当通过代理对象调用方法时,会调用目标类中同名方法(因为第四步的注释"解决重难点2")
     System.out.println(proxyInstance.getBelief());
     proxyInstance.eat("西红柿");
 ​
 }