Java核心 - 反射机制和动态代理

Java核心 - 反射机制和动态代理

1 反射机制

1.1 概念:

  • 在运行阶段决定需要创建的对象和调用的方法的机制
  • 动态编程技术

1.2 Class类

  • java.lang.Class类的实例(对象)可描述Java应用程序中的类和接口,即是一种数据类型
    • 普通类的实例(对象)本质为堆区中的一块内存区域
    • Class类的实例(对象)加载到方法区时正在运行的类
  • 该类没有公共构造方法,实例(对象)由Java虚拟机和类加载器自动构建完成,本质为加载到内存中的运行时类
1.2.1 Class实例(对象)的获取方式
  • 数据类型.class方式可获取对应类型的Class对象

    • 调用Class类的toString方法时

    • 引用数据类型打印class空格加上全路径名

    • 基本数据类型和void打印类型名称

    • Class c = String.class;
             System.out.println(c); // class java.lang.String
             c = int.class;
             System.out.println(c); //int
             c = void.class;
             System.out.println(c); //void
      
  • 对象.getClass()方式 获取对应类的Class对象

    • 基本数据类型的变量不能使用该方法得到Class对象 - 因为基本数据类的变量不能调用方法
  • 使用包装类.TYPE方式来获取对应基本数据类型的Class对象

    • c = Integer.TYPE;
              System.out.println(c); //int
              c = Integer.class;
              System.out.println(c); //class java.lang.Integer
      
  • 调取Class类中的forName()方法来获取对应的Class对象

    • static修饰,类名.调用

    • 完整的对象名称:包名.类名

    • 基本数据类型没法获取

    • c = Class.forName("java.lang.String");
              System.out.println(c); //class java.lang.String
      
  • 类加载器的方式来获得Class对象

    • 确保能够拿到类加载器,最好使用本类的Class对象来调用getClassLoader()方法

    • 方法中传入完全对象名称:包名.类名

    • 基本数据类型没法获取

    • 当使用String等Java.lang下封装的类对象获取到的类加载器为根加载器( Bootstrap),值为null

    • ClassLoader classLoader = test03.class.getClassLoader();
              c = classLoader.loadClass("java.lang.String");
              System.out.println(c);
      
1.2.2 Class类的常用方法
  • static Class<?> forName(String className)

    • 获取参数指定的类型的Class对象并返回
  • T newInstance()

    • 创建该Class对象所表示类的新实例
    • 该方法已经过时
  • Constructor getConstructor(Class<?>... parameterTypes)

    • 获取此Class对象表示类型中参数指定的公共构造方法
  • Constructor<?>[] getConstructors()

    • 获取此Class对象表示类型中所有的公共构造方法
  • Filed getDeclaredField(String name)

    • 用来获取私有的成员变量
    • 此Class类表示类中参数指定的单个成员变量信息
  • Filed[] getDeclaredFields()

    • 获取所有私有的成员变量
    • 此Class类表示类中所有成员变量信息
  • Method getMethod(String name, Class<?>... paramaterTypes)

    • 获取该类中方法为name参数为paramaterTypes的指定公共成员方法
  • Method[] getMethods()

    • 获取该Class对象表示类中所有公共成员方法

如何动态创建对象:

  • 在编译阶段不知道要创建对象的类型
  • 在运行阶段再动态的去创建对象
//常规无参构造创建
        Person person = new Person();
        System.out.println(person);//Person{name='null', age=0}

        //使用Class类动态创建对象
        //该方法中的String字符串可以在运行阶段手动输入,也可以读取配置文件加载
        String className = "com.lagou.Person";
        Class c = Class.forName(className);
        Object o = c.newInstance();
        System.out.println(o);//Person{name='null', age=0}

1.3 Constructor类

1.3.1 概念
  • java.lang.reflect.Constructor

  • 通过Class类的getConstructor/getConstructors方法获取

1.3.2 常用方法
  • T newInstance(Object... initargs)

    • 使用此Constructor对象描述的构造方法来构造代表类型的实例
  • int getModifiers()

    • 获取方法的访问修饰符
    • public - 1
    • static - 8
    • protected - 4
    • private - 2
  • String getName()

    • 获取方法的名称
    • 为该类的全路径名称
  • Class<?>[] getParameterTypes()

    • 获取方法所有参数的类型
    • 参数的Class类对象

实例:

使用Constructor动态创建Person实例

  • 通过Class.forName方法获得Person类的Class对象
  • 调用Class类的getConstructor方法获取构造方法对象
  • 调用Constructor类的newInstance方法创建实例
 //获取Class对象
        String className = "com.lagou.Person";
        Class c = Class.forName(className);
        //获取此Class对象的Constructor对象,该方法可以获取对应类中对应的无参和有参构造
        //获取无参构造
        Constructor constructor = c.getConstructor();
        //通过该无参构造方法类新建实例时也不需要传参数
        Object o = constructor.newInstance();
        System.out.println(o);//Person{name='null', age=0}
        //获取对应的有参构造 new Person(String name,int age)
        Constructor constructor1 = c.getConstructor(String.class,int.class);
        //通过有参构造对象新建实例时需要传参数
        Object o1 = constructor1.newInstance("张飞", 10);
        System.out.println(o1);//Person{name='张飞', age=10}

注意:当调用getConstructor方法时传入不存在的有参构造方法时

java.lang.NoSuchMethodException

当Constructor调用newInstance方法时传入参数与有参构造方法不符合时

java.lang.IllegalArgumentException

1.4 Field类

1.4.1 概念
  • java.lang.reflect.Field类
  • 用来描述获取到的单个成员变量
1.4.2 Filed类常用方法
  • Object get(Object obj)

    • 获取传入obj实例所对应的Field值
  • void set(Object obj, Object value)

    • obj参数为要修改的实例,value参数为将要修改该实例中的对应成员变量的值为什么值

以上两个方法如果操作的为类中的私有成员变量会报错 - java.lang.IllegalAccessException

  • void setAccessible(boolean flag)

    • 暴力反射
    • true值可以在反射对象时取消java语言访问检查
  • int getModifiers()

    • 获取成员变量的访问修饰符
  • Class<?> getType()

    • 获取成员变量的数据类型
  • String getName()

    • 获取成员变量名称
    • 变量名不包括路径
Class c1 = Class.forName("com.lagou.Person");
        Constructor cs = c1.getConstructor(String.class, int.class);
        Object p1 = cs.newInstance("张飞", 20);
        Field field = c1.getDeclaredField("name");
        field.setAccessible(true);
        System.out.println(field.get(p1));//张飞
        field.set(p1,"刘备");
        System.out.println(field.get(p1));//刘备

1.5 Method类

1.5.1 概念
  • java.lang.reflect.Method类主要用来描述获取到的单个成员方法信息
1.5.2 Method类常用方法
  • Object invoke(Object obj, Object... args)
    • obj传入该类的一个实例,args为该method类所代表的方法需要传入的参数,调用方法为static修饰的,第一个参数obj为null
c1 = Class.forName("com.lagou.Person");
        cs = c1.getConstructor(String.class, int.class);
        p1 = cs.newInstance("张飞",20);
        System.out.println(p1);//Person{name='张飞', age=20}
        Method method = c1.getMethod("setName", String.class);
        method.invoke(p1,"关羽");
        System.out.println(p1);//Person{name='关羽', age=20}
  • int getModifiers()

    • 获取访问修饰符
  • Class<?> getReturnType()

    • 获取方法的返回值类型
  • String getName()

    • 获取方法的名称
  • Class<?>[] getParameterTypes()

    • 获取方法所有参数的类型
  • Class<?>[] getExceptionTypes()

    • 获取方法的异常信息

1.6 其他结构信息

  • Package getPackage()

    • 获取所在包的信息
    • package 包全路径
  • Class<? super T> getSuperclass()

    • 获取继承的父类信息
    • class 类全路径
  • Class<?>[] getInterfaces()

    • 获取实现的所有接口
    • interface 接口全路径
  • Annotation[] getAnnotations()

    • 获取注解信息
  • Type[] getGenericInterfaces()

    • 获取泛型信息
    • 类型 全路径<泛型类型全路径>

1.7 Class.forName()解析

forName方法:

forName0 方法

1.8 反射的问题

1.8.1 性能问题
  • 反射调用需要一系列的安全校验
  • 反射需要调用native方法来实现
  • 寻找字节码的过程,加载,解析相比new方式较慢,new方式无需寻找因为Linking解析阶段已经将符号引用转为了直接引用
  • 入参校验

1.9 类加载

1.9.1 加载.class文件的途径
  • 本地文件系统中加载
  • 网络下载
  • zip.jar 包加载
  • 存储中间件中加载(数据库,缓存...)
  • 在JVM运行期间通过动态字节码重组的方式(ASM)
1.9.2 初始化

JVM执行类的初始化语句,为类的静态变量赋值

  • 如果该类还没有被加载和连接(loading,linking),就先进行加载和连接
  • 如果该类存在父类且父类没有初始化,先初始化父类
  • 父类存在初始化语句(static块),依次执行初始化语句
1.9.3 类完成初始化的时机
  • 创建类的实例

    • new xxxClass()
    • Class.newInstance()
    • constructor.newInstance()
  • 访问静态变量或对静态变量赋值

  • 调用类的静态方法

  • Class.forName() 该方法innit参数默认为true

  • 完成子类的初始化也会完成本类的初始化()接口除外

  • 该类为程序的引导入口(main或者test入口)

2 代理模式

2.1 代理模式的概念和分类

2.1.1 概念
  • 代理设计模式,指通过代理对象去访问目标对象,通过此方法便可以在不修改目标对象的前提下对目标对象进行功能增强
  • 代理模式的核心为代理对象和目标对象,即代理对象对目标对象进行一定扩展的同时并且调用代理对象
2.1.2 代理模式的分类

代理模式分为:

  • 静态代理
  • 动态代理
2.1.3 静态代理
2.1.3.1 静态代理的实现
  • 对于代理对象和被代理对象,都需要实现目标接口并实现接口中的抽象方法
  • 代理对象中需要被代理对象的引用并且在代理对象所实现接口的方法中调用被代理对象引用的方法
  • 在代理对象所实现的接口方法中对被代理对象进行增强

示例:

  • 准备一个接口Animal,其中有两个方法
    • moving()
    • eating(String food)
public interface Animal {
    void moving();

    void eating(String food);
}
  • 再准备一个接口Biology,其中有一个方法
    • breath()
public interface Biology {
    void breath();
}
  • 准备一个Animal接口和Biology接口的实现类 - Cat
public class Cat implements Animal, Biology{

    @Override
    public void moving() {
        System.out.println("this is cat");
    }

    @Override
    public void eating(String food) {
        System.out.println("cat is eating"+food);
    }

    @Override
    public void breath() {
        System.out.println("cat is breathing");
    }
}
  • 准备一个静态代理类staticProxy
    • 该静态代理类需要实现被代理对象所实现的全部接口
    • 需要声明被代理对象的引用,即声明接口的实现类
    • 在所实现的接口的方法中调用该接口实现类(即被代理对象的引用)的方法并进行增强
public class staticProxy implements Animal, Biology{

    private Animal animal;

    private Biology biology;


    public staticProxy(Biology biology) {
        this.biology = biology;
    }

    public staticProxy(Animal animal) {
        this.animal = animal;
    }

    @Override
    public void moving() {
        System.out.println("业务处理");
        animal.moving();
        System.out.println("业务处理");
    }

    @Override
    public void eating(String food) {
        animal.eating(food);
    }

    @Override
    public void breath() {
        biology.breath();
    }
}

使用方式:

		Animal cat = new Cat();
        staticProxy staticProxy = new staticProxy(cat);
        staticProxy.moving();

结果

业务处理
this is cat
业务处理

知识点:

  • 在设计时静态代理类中声明的引用为被代理对象的接口类型,这样使用时可以通过多态得到同一个接口下的不同实现类并调用不同实现类中重写的不同的方法
  • 缺点一:
    • 一个代理对象只能为一个被代理对象(即一种类型的接口专门服务),当一个多实现的类或者不同接口的不同实现类都需要用到代理对象时,则需要为每种接口都创建一个代理类并实现对应的接口

  • 缺点二:
    • 由于该静态代理类需要实现被代理对象的接口,因此每当接口中的方法进行改动需要同时维护接口的实现类和相应的代理类
2.1.4 动态代理 ( JDK Proxy)
  • 在运行阶段通过反射的机制来动态的创建代理对象
2.1.4.1 动态代理的实现

新建一个代理类来实现InvocationHandler接口,该类需要:

  • Object类型的被代理对象 target

  • 调用Proxy的静态方法 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 得到被代理对象的接口类型

    • 该方法的返回值为Object 类型的对象,在使用的时候需要强制类型转换成传入的被代理对象target的接口类型,否则会报java.lang.ClassCastException错误
    • 该方法的第一个参数为被代理对象的Class对象的类加载
    • 该方法的第二个参数为被代理对象所实现的接口
    • 该方法的第三个参数为InvocationHandler 接口的一个实现类,即我们自己编写的动态代理对象
  • 重写InvocationHandler接口中的invoke方法,该方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

    在该方法中实现对被代理对象的增强并调用被代理对象中重写的接口的方法

    • 该方法第一个参数为代理对象的实例
    • 该方法第二个参数为调用的方法,即被代理对象所调用的方法
    • args为该调用方法的参数,如果没有参数则不用填写

示例:

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

public class dynamicProxy implements InvocationHandler {

    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("动态代理逻辑处理");
        method.invoke(target,args);
        return null;
    }
}

使用方式:

 	Animal instance = (Animal) new dynamicProxy().getInstance(new Cat());
	instance.moving();
2.1.5 动态代理(CGLib)
  • CGLib可以代理没有实现接口的目标类
  • 对于被final修饰的类无法被代理,被final修饰的方法也无法被代理
2.1.5.1 CGLib的实现
  • 引入CGLib框架
		<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
  • 编写CGLib代理对象类
    • 实现MethodInterceptor接口并重写intercept方法
    • Object 类型的被代理对象targetinter
    • 创建getInstance方法获取代理对象实例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGlibProxy implements MethodInterceptor {
    private Object target;

    public Object getInstance(Object target){
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("业务处理");
        methodProxy.invokeSuper(o,objects);
        return null;
    }
}

CGLib代理类的使用

		Cat instance = (Cat) new CGlibProxy().getInstance(new Cat());
        instance.moving();
        instance.breath();
  • 实例化代理对象时可以直接强制类型转换为目标对象的类型,Proxy代理时只能强制转换为接口类型
2.1.5.2 CGLib解析
  • 如何生成目标类Target的代理类

    • 创建Enhancer实例
    • 通过setSuperclass来设置目标类
    • 通过setCallback方法来设置拦截对象
    • create方法生成目标类的代理类并返回实例
  • 代理类会继承目标类

  • 代理类会为目标类中的方法生成两个方法,比如a()

    • @Override a() 重写
    • CGLIB$a$0 继承的目标类
  • 当测试时使用生成的代理实例调用目标方法实际上调用代理类中重写的方法

  • 当调用代理类中重写的方法时会先判断是否存在实现了MethodInterceptor接口的拦截对象,没有便会调取CGLIB$BIND_CALLBACKS方法来获取

    • 该方法先调用CGLIB$THREAD_CALLBACKS的get方法来获取拦截对象,如果没有则从CGLIB$STATIC_CALLBACKS中获取,还没有就不代理
  • setCallback方法时参数传入拦截对象,并在实例化代理对象时由Enhancer的firstInstance方法再往下调用

    • 最终由在CGLIB$SET_THREAD_CALLBACKS方法中调用了CGLIB​THREAD_CALLBACKS的set方法来保存拦截对象
  • intercept 参数解析

    • @para1 obj :代理对象本身
    • @para2 method : 被拦截的方法对象
    • @para3 objects:方法调用入参
    • @para4 methodProxy:用于调用被拦截方法的方法代理对象
  • invokeSuper(obj, objects)调用被拦截的方法而不是method.invoke反射调用

    • Fastclass直接调用
    • Fastclass两个重要方法getIndex和invoke方法
    • getIndex对每个方法都创建一个索引 - 通过签名生成hash值返回相应index - 签名由方法名参数返回值类型拼接而成
    • invoke 传入索引,对象和参数
2.1.6 CGLib和JDK Proxy区别

JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
JDK Proxy 是通过拦截器加反射的方式实现的;
JDK Proxy 只能代理继承接口的类;
JDK Proxy 实现和调用起来比较简单;
CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的

posted @ 2020-12-03 16:21  Pengc931482  阅读(111)  评论(0)    收藏  举报