Java反射机制底层原理

Java反射机制底层原理这篇文章我是参考了然后我在这里做一下总结 因为原文章真的很好 我才疏学浅没什么进行补充 只能做出自己的总结并且写一下自己对这个的理解

大家好,欢迎来到IT知识分享网。

反射机制

这篇文章我是参考了Java 中的反射机制(两万字超全详解)_java反射-CSDN博客

然后我在这里做一下总结,因为原文章真的很好,只能做出自己的总结补充并且写一下自己对这个的理解。

原理:

我们学过JVM都知道我们Java首先通过javac编译器变异成class字节码文件,然后我们通过ClassLoader将我们用到的class类加载到我们的方法区中,方法区在jdk8中使用元空间进行实现。元空间中保存了类的元数据(包括方法代码、变量名、方法名、访问权限等等)。但是在我们同时会在堆空间中创建一个Class类的对象。有些同志对这个Class类对象就不理解了,注意这个Class是一个类,这个类继承自Object,这个Class类对象可以和我们实际的类进行关联。请看下面的例子:

String类为例,当 JVM 加载String类时,它首先读取String.class文件到内存,然后,在堆中为String类创建一个Class类对象并将两者关联起来

Class cls = new Class(String); 

注意:这个Class类对象是 JVM 内部创建的,如果我们查看 JDK 源码,可以发现Class类的构造方法是private,即只有 JVM 能创建Class类对象,我们程序员自己的 Java 程序是无法创建Class类对象的。

所以,JVM持有的每个Class类对象都指向一个数据类型(class或interface):

┌───────────────────────────┐ │      Class Instance       │──────> String ├───────────────────────────┤ │name = "java.lang.String"  │ └───────────────────────────┘ ┌───────────────────────────┐ │      Class Instance       │──────> Random ├───────────────────────────┤ │name = "java.util.Random"  │ └───────────────────────────┘ ┌───────────────────────────┐ │      Class Instance       │──────> Runnable ├───────────────────────────┤ │name = "java.lang.Runnable"│ └───────────────────────────┘ 

一个Class类对象包含了其对应的类class的所有完整信息:

┌───────────────────────────┐ │      Class Instance       │──────> String ├───────────────────────────┤ │name = "java.lang.String"  │ ├───────────────────────────┤ │package = "java.lang"      │ ├───────────────────────────┤ │super = "java.lang.Object" │ ├───────────────────────────┤ │interface = CharSequence...│ ├───────────────────────────┤ │field = value[],hash,...   │ ├───────────────────────────┤ │method = indexOf()...      │ └───────────────────────────┘ 

由于JVM为每个加载的类class创建了对应的Class类对象,并在实例中保存了该类class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class类对象,我们就可以通过这个Class类对象获取到其对应的类class的所有信息。

这种通过Class实例获取类class信息的方法称为反射(Reflection)。

使用反射的好处

 使用反射的好处是什么呢?可以动态加载类的字节码文件,将字节码文件的加载推迟到程序运行过程中。第二个就是可以获取一个类的字段方法以及可以创建一个类的实例。

public class ClassLoad {     public static void main(String[] args) {         Scanner sc = new Scanner(System.in);         int key = sc.nextInt();         switch(key) {             case 0:                 Cat cat = new Cat();                 break;             case 1:                 // 通过反射创建一个Dog 类对象,不提供代码,只是文字说明                 break;         }     } }

上面代码中,根据 key 的值选择创建 Cat/Dog 对象,但是在代码编译时,编译器会先检查程序中是否存在 Cat 类,如果没有,则会编译报错;编译器不会检查是否存在 Dog 类,因为 Dog 类是使用反射的方式创建的,所以即使程序中不存在 Dog 类,也不会编译报错,而是等到程序运行时,我们真正选择了 key = 1 后,才会去检查 Dog 类是否存在。

获取类的对象方法等API

关于获取类的对象方法,以及创建对象都是调用的Class中的方法:

首先获取class对应的Class实例,有五种方式:

如何获取一个class的Class实例?有5个方法:

方法一:直接通过一个类class中的静态变量class获取:

Class cls = String.class;// class 是 String 类中的一个静态变量

方法二:如果我们有一个类class的对象,可以通过该对象引用提供的getClass()方法获取:

String s = "Hello"; Class cls = s.getClass();// 调用 String类对象 s的 getClass() 方法获取

方法三:如果知道一个类class的完整类名,可以通过Class类的静态方法Class.forName()获取:

Class cls = Class.forName("java.lang.String");// java.lang.String 是 String 类的完整类名
Class integerClass = int.class; Class characterClass = char.class; Class booleanClass = boolean.class; System.out.println(integerClass);// int 

方法五:对于基本数据类型对应的包装类,可以通过类中的静态变量TYPE获取到Class类对象:

Class type1 = Integer.TYPE; Class type2 = Character.TYPE; System.out.println(type1);// int

获取字段值:

我们先看看如何通过Class类对象获取其对应的类定义的字段信息。Class类提供了以下几个方法来获取字段:

Field getField(name):根据字段名获取某个 public 的 field(包括父类)

Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类)

Field[] getFields():获取所有 public 的 field(包括父类)

Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)

public class Main { public static void main(String[] args) throws Exception { Class stdClass = Student.class; // 获取public字段"score": System.out.println(stdClass.getField("score")); // 获取继承的public字段"name": System.out.println(stdClass.getField("name")); // 获取private字段"grade": System.out.println(stdClass.getDeclaredField("grade")); } } class Student extends Person { public int score; private int grade; } class Person { public String name; } 

调用方法:

我们已经能通过Class类的Field类对象获取其对应的类class中定义的所有字段信息,同样的,可以通过Class类获取所有Method信息。Class类提供了以下几个方法来获取类class中定义的Method:

Method getMethod(name, Class…):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

// 一般情况下调用 String 类的 substring() 方法 String s = "Hello world"; String r = s.substring(6); // "world" import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { // String 对象: String s = "Hello world"; // 获取 String substring(int)方法,形参为 int: Method m = String.class.getMethod("substring", int.class); // 在 s 对象上调用该方法并获取结果: String r = (String) m.invoke(s, 6); // 打印调用结果: System.out.println(r); } } 

调用构造方法:

一般情况下,我们通常使用new操作符创建新的对象:

Person p = new Person();

如果通过反射来创建新的对象,可以调用Class提供的newInstance()方法:

Person p = Person.class.newInstance();
import java.lang.reflect.Constructor; public class Main {     public static void main(String[] args) throws Exception {         // 获取构造方法 Integer(int),形参为 int         Constructor cons1 = Integer.class.getConstructor(int.class);         // 调用构造方法:         // 传入的形参必须与构造方法的形参类型相匹配         Integer n1 = (Integer) cons1.newInstance(123);         System.out.println(n1);         // 获取构造方法Integer(String),形参为 String         Constructor cons2 = Integer.class.getConstructor(String.class);         Integer n2 = (Integer) cons2.newInstance("456");         System.out.println(n2);     } } 

通过Class实例获取Constructor的方法如下:

getConstructor(Class…):获取某个public的Constructor;
getDeclaredConstructor(Class…):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。

创建对象:

// 获取 String 的 Class 类对象: Class cls = String.class; // 通过 String 的 Class 类对象创建一个 String 类的实例对象: String s = (String) cls.newInstance(); 

代理模式

1. 概念

代理模式为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。

2. 代理模式类图

Client不直接调用TargetObject中的方法,而是通过ProxyObject调用TargetObject中的方法

Java反射机制底层原理

3. 代理模式的形式

代理模式有不同的形式,主要有三种:静态代理、动态代理和Cglib代理

静态代理

1. 概念

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。

2. 类图

定义一个接口ITeacherDao,目标对象TeacherDAO实现接口ITeacherDAO。使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO。调用的时候通过调用代理对象的方法来调用目标对象。

        注:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。

3. 代码示例 

ITeacherDao接口,抽象定义授课方法

public interface ITeacherDao {

    void teach();//授课
}
        TeacherDao类,目标对象,实现ITeacherDao接口

public class TeacherDao implements ITeacherDao{

    @Override
    public void teach() {

        System.out.println(“老师授课中….”);
    }
}

TeacherDaoProxy类,代理对象,也实现ITeacherDao接口,通过接口聚合目标对象,执行代理方法

public class TeacherDaoProxy implements ITeacherDao {

    private ITeacherDao target;//目标对象,通过接口聚合
 
    public TeacherDaoProxy(ITeacherDao target) {

        this.target = target;
    }
 
    @Override
    public void teach() {

        System.out.println(“代理开始,完成某些操作”);
        target.teach();
        System.out.println(“提交”);
    }
}

 Client类,创建目标对象(被代理对象);创建代理对象,同时将目标对象传递给代理对象;通过代理对象,调用到目标对象的方法

public class Client {

    public static void main(String[] args) {

        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(new TeacherDao());
        teacherDaoProxy.teach();
    }
}

输出结果

Java反射机制底层原理

静态代理的缺点

虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。

1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:

  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类

2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

如何改进?

当然是让代理类动态的生成啦,也就是动态代理。

动态代理 

动态代理实现方式

为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:

  1. 通过实现接口的方式 -> JDK动态代理

通过 JDK 提供的一个Proxy.newProxyInstance()方法创建了一个实现了目标对象实现接口的代理对象

  1. 通过继承类的方式 -> CGLIB动态代理

通过字节码技术创建一个目标对象的子类代理对象

JDK是基于反射机制,生成一个实现代理接口的匿名类,然后重写方法,实现方法的增强.
它生成类的速度很快,但是运行时因为是基于反射,调用后续的类操作会很慢.
而且他是只能针对接口编程的.

CGLIB是基于继承机制,继承被代理类,所以方法不要声明为final,然后重写父类方法达到增强了类的作用.
它底层是基于asm第三方框架,是对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理.
生成类的速度慢,但是后续执行类的操作时候很快.
可以针对类和接口.

JDK动态代理

1. 概念

动态代理在使用时,代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成,是利用JDK的API(java.lang.reflect.Proxy.newProxyInstace()), 动态的在内存中构建代理对象。动态代理也叫做:JDK代理、接口代理。

2. 类图

Java反射机制底层原理

3. 代码示例

 ITeacherDao接口,抽象定义授课方法

public interface ITeacherDao {

    void teach();//授课
}
        TeacherDao类,目标对象,实现ITeacherDao接口

public class TeacherDao implements ITeacherDao{

    @Override
    public void teach() {

        System.out.println(“老师授课中….”);
    }
}

ProxyFactory类,维护一个目标对象,利用JDK的API,动态的在内存中构建代理对象

        public static object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

        1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法固定

        2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型

        3. InvocationHandler h: 事情处理,执行目标对象的方法时,会触发事情处理器方法,会把当前执行的目标对象作为参数传入

public class ProxyFactory {

    //维护一个目标对象,Object
    private Object target;
 
    public ProxyFactory(Object target) {

        this.target = target;
    }
    //给目标对象,生成一个代理对象
    public Object getProxyInstance(){

        //
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        System.out.println(“JDK代理开始..”);
                        //反射机制调用目标对象的方法
                        Object returnVal = method.invoke(target, args);
                        System.out.println(“JDK代理提交..”);
                        return returnVal;
                    }
                });
    }
}

        Client类

public class Client {

    public static void main(String[] args) {

        ITeacherDao target = new TeacherDao();
        ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
        //class com.sun.proxy.$Proxy0内存中动态生成了代理对象
        System.out.println(proxyInstance.getClass());
        proxyInstance.teach();
    }
}

输出结果  

Java反射机制底层原理

案例

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler,我们仍然通过案例来学习编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke 方法中编写方法调用的逻辑处理。

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Date; public class LogHandler implements InvocationHandler { Object target; // 被代理的对象,实际的方法执行者 public LogHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); // 调用 target 的 method 方法 after(); return result; // 返回方法的执行结果 } // 调用invoke方法之前执行 private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } // 调用invoke方法之后执行 private void after() { System.out.println(String.format("log end time [%s] ", new Date())); } } 

编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法,具体步骤可见代码和注释

import proxy.UserService; import proxy.UserServiceImpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Client2 { public static void main(String[] args) throws IllegalAccessException, InstantiationException { // 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名 // System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 1. 创建被代理的对象,UserService接口的实现类 UserServiceImpl userServiceImpl = new UserServiceImpl(); // 2. 通过反射方式 获取对应的 ClassLoader ClassLoader classLoader = userServiceImpl.getClass().getClassLoader(); // 3. 通过反射方式 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService, Class[] interfaces = userServiceImpl.getClass().getInterfaces(); // 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用 // 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl 传入的是被代理对象 InvocationHandler logHandler = new LogHandler(userServiceImpl); /* 5.根据上面提供的信息,创建代理对象 在这个过程中, a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码 b.然后根据相应的字节码转换成对应的class, c.然后调用newInstance()创建代理实例 */ UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler); // 调用代理的方法 proxy.select(); proxy.update(); // 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy // ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy"); } } 

运行结果

log start time [Thu Dec 20 16:55:19 CST 2018] 查询 selectById log end time [Thu Dec 20 16:55:19 CST 2018] log start time [Thu Dec 20 16:55:19 CST 2018] 更新 update log end time [Thu Dec 20 16:55:19 CST 2018] 

InvocationHandler 和 Proxy 的主要方法介绍如下:

java.lang.reflect.InvocationHandler

Object invoke(Object proxy, Method method, Object[] args)定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用

java.lang.reflect.Proxy

static InvocationHandler getInvocationHandler(Object proxy)用于获取指定代理对象所关联的调用处理器  super.h返回的就是InvocationHandler

static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)返回指定接口的代理类

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法

static boolean isProxyClass(Class<?> cl)返回 cl 是否为一个代理类

代理类的调用过程

生成的代理类到底长什么样子呢?借助下面的工具类,把代理类保存下来再探个究竟(通过设置环境变量sun.misc.ProxyGenerator.saveGeneratedFiles=true也可以保存代理类)

import sun.misc.ProxyGenerator; import java.io.FileOutputStream; import java.io.IOException; public class ProxyUtils { / * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下 * params: clazz 需要生成动态代理类的类 * proxyName: 为动态生成的代理类的名称 */ public static void generateClassFile(Class clazz, String proxyName) { // 根据类信息和提供的代理类名称,生成字节码 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces()); String paths = clazz.getResource(".").getPath(); System.out.println(paths); FileOutputStream out = null; try { //保留到硬盘中 out = new FileOutputStream(paths + proxyName + ".class"); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } 

然后在 Client2 测试类的main的最后面加入一行代码

// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");

IDEA 再次运行之后就可以在 target 的类路径下找到 UserServiceProxy.class,双击后IDEA的反编译插件会将该二进制class文件

Java反射机制底层原理

Spring生成的UserServiceProxy 的代码如下所示:

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxy.UserService; public final class UserServiceProxy extends Proxy implements UserService { private static Method m1; private static Method m2; private static Method m4; private static Method m0; private static Method m3; public UserServiceProxy(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { // 省略... } public final String toString() throws { // 省略... } public final void select() throws { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { // 省略... } public final void update() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m4 = Class.forName("proxy.UserService").getMethod("select"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("proxy.UserService").getMethod("update"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } 

从 UserServiceProxy 的代码中我们可以发现:

  • UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
  • 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
  • 调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h 实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑,super.h.invoke 调用这个LogHandler的方法。

而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法

 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); // 调用 target 的 method 方法 after(); return result; // 返回方法的执行结果 } 

JDK动态代理执行方法调用的过程简图如下:

Java反射机制底层原理

代理类的调用过程相信大家都明了了,而关于Proxy的源码解析,还请大家另外查阅其他文章或者直接看源码

关于super.h.invoke内部详解

super.h.invoke(this, m3, (Object[])null);super也就是Proxy,看下Proxy的h如下图:

Java反射机制底层原理

所以h代表的就是InvocationHandler这个接口,而他的具体实现类是ProxyHandler,这样最终proxySubject.getName();执行的代码是ProxyHandler中的invoke方法。

CGLIB动态代理

1. 概念

静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是Cglib代理。Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。

        Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。广泛被许多AOP的框架使用,例如Spring AOP,实现方法拦截。

        在AOP编程中如何选择代理模式: 1.目标对象需要实现接口,用JDK代理 

                                                             2.目标对象不需要实现接口,用Cglib代理

        Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

注意事项:

        1. 使用Cglib代理需要先导入相应包 

Java反射机制底层原理

    2. 在内存中动态构建子类,注意代理的类不能为final,否则报错

    3. 目标对象的方法如果为final/static,那么就不会被拦截

2. 类图

        ​​​​​Java反射机制底层原理

3. 代码示例

’TeacherDao类,无接口

public class TeacherDao {

    public void teach(){

        System.out.println(“老师授课中..cglib代理,不需要实现接口”);
    }
}

ProxyFactory类,实现MethodInterceptor接口,通过设置目标对象子类来代理目标对象,重写intercept()方法,调用目标对象方法

public class ProxyFactory implements MethodInterceptor{

    //维护一个目标对象  这个就是父类的代理啊。
    private Object target;
    //构造器,传入目标对象
    public ProxyFactory(Object target) {

        this.target = target;
    }
    //返回一个代理对象,是target对象的代理对象
    public Object getProxyInstance(){

        //1.创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(target.getClass());
        //3.设置回调函数
        enhancer.setCallback(new MyInterceptor());
        //4.创建子对象,即代理对象
        return enhancer.create();
    }
    //重写 intercept方法,会调用目标对象的方法
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        System.out.println(“Cglib代理模式—开始”);
        Object returnVal = method.invoke(target,args);
        System.out.println(“Cglib代理模式—提交”);
        return null;
    }
}

         Client类,创建目标对象;获取到代理对象,并且将目标对象传递给代理对象;执行代理对象的方法,触发intercept 方法,从而实现对目标对象的调用

public class Client {

    public static void main(String[] args) {

        //创建目标对象
        TeacherDao target = new TeacherDao();
        //获取到代理对象,并且将目标对象传递给代理对象
        TeacherDao proxyInstance =(TeacherDao) new ProxyFactory(target).getProxyInstance();
        //执行代理对象的方法,触发intercept 方法,从而实现对目标对象的调用
        proxyInstance.teach();
    }
}

输出结果 

 Java反射机制底层原理

案例

maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()

public class UserDao { public void select() { System.out.println("UserDao 查询 selectById"); } public void update() { System.out.println("UserDao 更新 update"); } } 

编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调

import java.lang.reflect.Method; import java.util.Date; public class LogInterceptor implements MethodInterceptor { / * @param object 表示要进行增强的对象 * @param method 表示拦截的方法 * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用 * @return 执行结果 * @throws Throwable */ @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法 当然我们也可以向上面一样传递一个target然后使用metho.invoke(target,args) after(); return result; } private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log end time [%s] ", new Date())); } } 

测试

public class CglibTest { public static void main(String[] args) { LogInterceptor logInterceptor = new LogInterceptor(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是通过继承来实现的 enhancer.setCallback(logInterceptor); UserDao dao = (UserDao)enhancer.create(); // 创建代理类 dao.select(); dao.update(); } }

运行结果

log start time [Fri Dec 21 00:06:40 CST 2018] UserDao 查询 selectById log end time [Fri Dec 21 00:06:40 CST 2018] log start time [Fri Dec 21 00:06:40 CST 2018] UserDao 更新 update log end time [Fri Dec 21 00:06:40 CST 2018] 

还可以进一步多个 MethodInterceptor 进行过滤筛选

public class LogInterceptor2 implements MethodInterceptor { @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); after(); return result; } private void before() { System.out.println(String.format("log2 start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log2 end time [%s] ", new Date())); } } // 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。 public class DaoFilter implements CallbackFilter { @Override public int accept(Method method) { if ("select".equals(method.getName())) { return 0; // Callback 列表第1个拦截器 } return 1; // Callback 列表第2个拦截器,return 2 则为第3个,以此类推 } } 

再次测试

public class CglibTest2 { public static void main(String[] args) { LogInterceptor logInterceptor = new LogInterceptor(); LogInterceptor2 logInterceptor2 = new LogInterceptor2(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是通过继承来实现的 enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE}); // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不做任何处理 enhancer.setCallbackFilter(new DaoFilter()); UserDao proxy = (UserDao) enhancer.create(); // 创建代理类 proxy.select(); proxy.update(); } } 

运行结果

log start time [Fri Dec 21 00:22:39 CST 2018] UserDao 查询 selectById log end time [Fri Dec 21 00:22:39 CST 2018] log2 start time [Fri Dec 21 00:22:39 CST 2018] UserDao 更新 update log2 end time [Fri Dec 21 00:22:39 CST 2018] 

CGLIB 创建动态代理类的模式是:

  1. 查找目标类上的所有非final 的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象;
  4. 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求

实现原理

jdk

底层原理解析
JDK动态代理是基于Java的反射机制实现的。在创建代理对象时,JDK会动态生成一个新的类,实现了目标对象所实现的接口,并将代理逻辑写入 invoke 方法中。当调用代理对象的方法时,实际上是调用了 invoke 方法,然后由 invoke 方法根据方法名来调用目标对象的方法。

代理类的创建
Java动态代理的实现是通过 java.lang.reflect.Proxy 类来完成的。当我们调用 Proxy.newProxyInstance 方法时,它会做以下几件事情:

1.检查提供的接口是否全部为公共接口。
2.确定要使用的类加载器。
3.生成代理类的二进制字节码。
4.加载这些二进制字节码,定义代理类。

Proxy.newProxyInstance方法分析
public class Proxy {     // 获取或创建一个动态代理类的实例的静态方法     public static Object newProxyInstance(ClassLoader loader,                                           Class<?>[] interfaces,                                           InvocationHandler h)         throws IllegalArgumentException     {         // 确保提供的InvocationHandler不为null         Objects.requireNonNull(h);         // 克隆接口数组,以避免对原数组的潜在修改         final Class<?>[] intfs = interfaces.clone();         // 获取系统的安全管理器         final SecurityManager sm = System.getSecurityManager();         // 如果有安全管理器,检查创建代理实例的权限         if (sm != null) {             checkProxyAccess(Reflection.getCallerClass(), loader, intfs);         }         /*          * 查找或生成指定的代理类。          */         // 获取代理类         Class<?> cl = getProxyClass0(loader, intfs);         /*          * 调用其构造函数并用指定的调用处理器InvocationHandler作为参数。          */         try {             // 如果安全管理器存在,进一步检查权限             if (sm != null) {                 checkNewProxyPermission(Reflection.getCallerClass(), cl);             }             // 查找代理类的构造函数,这个构造函数预期接收一个InvocationHandler作为参数             final Constructor<?> cons = cl.getConstructor(constructorParams);             final InvocationHandler ih = h;             // 如果代理类不是public的,需要特权访问权限,以便设置构造函数为可访问状态             if (!Modifier.isPublic(cl.getModifiers())) {                 AccessController.doPrivileged(new PrivilegedAction<Void>() {                     public Void run() {                         cons.setAccessible(true);                         return null;                     }                 });             }             // 使用提供的InvocationHandler实例化代理类             return cons.newInstance(new Object[]{h});         } catch (IllegalAccessException|InstantiationException e) {             // 如果代理对象无法正常创建,抛出内部错误             throw new InternalError(e.toString(), e);         } catch (InvocationTargetException e) {             // 如果构造函数调用抛出异常,解包异常并处理             Throwable t = e.getCause();             if (t instanceof RuntimeException) {                 throw (RuntimeException) t;             } else {                 throw new InternalError(t.toString(), t);             }         } catch (NoSuchMethodException e) {             // 如果没有找到预期的构造函数,抛出内部错误             throw new InternalError(e.toString(), e);         }     }     // 其他省略的代码... } 

方法的执行流程如下:

(1)参数验证:验证传入的 InvocationHandler h 是否为 null。
(2)安全权限检查(如果存在安全管理器):使用 checkProxyAccess 方法检查是否有创建代理实例的权限。
(3)代理类的查找或生成:调用 getProxyClass0 方法来获取或生成相应的代理类。
(4)调用代理类的构造函数创建实例:找到代理类的唯一构造函数(该构造函数以 InvocationHandler 为参数),并判断是否需要通过权限检查调用 setAccessible 来允许访问非公共构造函数。
(5)创建并返回代理实例:最后,通过反射调用构造器的 newInstance 方法,并将 InvocationHandler h 传递为参数,创建代理对象的实例。

注意

getProxyClass0() 方法的底层源码并没有公开,因为它是 JDK 内部的私有方法。但是,它的具体实现可以简单理解为使用了类似于 ASM(Java 字节码操作框架)这样的工具来动态生成字节码。

看到下面生成的代理对象的字节码文件,是不是一切都明白你了,原理竟然如此简单!

public final class $Proxy0 extends Proxy implements ServiceInterface {

    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {

        super(var1);
    }

    public final void execute() throws  {

        try {

            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {

            throw var2;
        } catch (Throwable var3) {

            throw new UndeclaredThrowableException(var3);
        }
    }

    …..省略toString/hashCode/equals等方法

    static {

        try {

            m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, Class.forName(“java.lang.Object”));
            m2 = Class.forName(“java.lang.Object”).getMethod(“toString”);
            m3 = Class.forName(“com.useful.tool.ServiceInterface”).getMethod(“execute”);
            m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”);
        } catch (NoSuchMethodException var2) {

            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {

            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
 

通过该文件可以看出:

(1)代理类继承了Proxy类,其主要目的是为了传递InvocationHandler。
(2)代理类实现了被代理的接口ServiceInterface,这也是为什么代理类可以直接强转成接口的原因。
(3)有一个公开的构造函数,参数为指定的InvocationHandler,并将参数传递到父类Proxy中。
(4)每一个实现的方法,都会调用InvocationHandler中的invoke方法,并将代理类本身、Method实例、入参三个参数进行传递。这也是为什么调用代理类中的方法时,总会分派到InvocationHandler中的invoke方法的原因。

 注意的地方

    public final void execute() throws  {

        try {

            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {

            throw var2;
        } catch (Throwable var3) {

            throw new UndeclaredThrowableException(var3);
        }
    }
 

上面的super也就是我们的Proxy类,super.h也就是Proxy里面的InvocationHandler h变量

Java反射机制底层原理

cjlib

  • CGLib采用了非常底层的字节码技术,其原理是通过目标类的字节码为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
  • 底层使用字节码处理框架ASM,来转换字节码并生成新的类。

直接看源码

Enhancer.generateClass(ClassVisitor v)    //AbstractClassGenerator.create(Object key)   protected Object create(Object key) {       try {           Class gen = null;           synchronized (source) {               ClassLoader loader = getClassLoader();               Map cache2 = null;               cache2 = (Map)source.cache.get(loader);               if (cache2 == null) {                   cache2 = new HashMap();                   cache2.put(NAME_KEY, new HashSet());                   source.cache.put(loader, cache2);               } else if (useCache) {                   Reference ref = (Reference)cache2.get(key);                   gen = (Class) (( ref == null ) ? null : ref.get());                }               if (gen == null) {                   Object save = CURRENT.get();                   CURRENT.set(this);                   try {                       this.key = key;                       if (attemptLoad) {                           try {                               gen = loader.loadClass(getClassName());                           } catch (ClassNotFoundException e) {                               // ignore                           }                       }                       if (gen == null) {                           //生成的Class的二进制存储                           byte[] b = strategy.generate(this);                           String className = ClassNameReader.getClassName(new ClassReader(b));                           //将类名放入Cache中,Cache实际上是一个Hashset的实例                           getClassNameCache(loader).add(className);                           //将生成的类装载路JVM                           gen = ReflectUtils.defineClass(className, b, loader);                       }                       if (useCache) {                           //将装载的类放入cache                           cache2.put(key, new WeakReference(gen));                       }                       return firstInstance(gen);                   } finally {                       CURRENT.set(save);                   }               }           }           return firstInstance(gen);       } catch (RuntimeException e) {           throw e;       } catch (Error e) {           throw e;       } catch (Exception e) {           throw new CodeGenerationException(e);       }   }   //ReflectUtils.defineClass(className, b, loader)   public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {       Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN };       //DEFINE_CLASS 是通过静态块来实例化的一个java.lang.ClassLoader.defineClass的方法对象       Class c = (Class)DEFINE_CLASS.invoke(loader, args);       // Force static initializers to run.       Class.forName(className, true, loader);       return c;   }    

 实际上总结为

public class Enhancer { // ... public Object create() { // ... 省略部分代码 // 生成代理类的字节码 byte[] bytecode = generateClass(classVisitor); // 加载代理类并创建实例 return firstInstance(generate(classLoader, bytecode)); } // 使用字节码数组生成代理类 protected Object firstInstance(Class type) throws IllegalAccessException, InstantiationException { return type.newInstance(); } // 通过字节码生成 Class 对象 protected Class generate(ClassLoader classLoader, byte[] bytecode) { return new ClassLoaderData(classLoader, bytecode).loadClass(); } // 生成代理类的字节码 protected byte[] generateClass(ClassVisitor v) { // 使用 ASM 库等生成字节码的工具来生成代理类的字节码 } // ... } 

可以知道了,我么cjlib使用的 ASM 库等生成字节码的工具来生成代理类的字节码,然后使用反射机制生成Class并生成对象的对象实例

相关问题

JDK动态代理与CGLIB动态代理对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy 的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
  • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
  • 代码实现简单。

基于类似 cglib 框架的优势:

  • 无需现接口,达到代理类无侵入
  • 只操作我实们关心的类,而不必为其他相关类增加工作量。
  • 高性能

面试题

描述动态代理的几种实现方式?分别说出相应的优缺点

代理可以分为 “静态代理” 和 “动态代理”,动态代理又分为 “JDK动态代理” 和 “CGLIB动态代理” 实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object

  • 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
  • 缺点:不同的接口要有不同的代理类实现,会很冗余

JDK 动态代理

为了解决静态代理中,生成大量的代理类造成的冗余;JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象 jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口。

优点:解决了静态代理中冗余的代理实现类问题。

缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

CGLIB 代理

由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。 同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。

缺点:技术实现相对难理解些。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/156214.html

(0)
上一篇 2025-02-14 15:20
下一篇 2025-02-14 15:25

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信