Java反射机制底层原理

反射机制

这篇文章我是参考了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 类的完整类名


方法四:对于基本数据类型(int、char、boolean、float 等),通过 基本数据类型.class 获取:

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();

调用Class.newInstance()的局限是,它只能调用该类的public无参构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的构造方法,Java 的反射 API 提供了Constructor类对象,它包含一个构造方法的所有信息,通过Constructor类对象可以创建一个类的实例对象。Constructor类对象和Method类对象非常相似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回一个类的实例对象:

import java.lang.reflect.Constructor;public class Main {public static void main(String[] args) throws Exception {// 获取构造方法 Integer(int),形参为 intConstructor cons1 = Integer.class.getConstructor(int.class);// 调用构造方法:// 传入的形参必须与构造方法的形参类型相匹配Integer n1 = (Integer) cons1.newInstance(123);System.out.println(n1);// 获取构造方法Integer(String),形参为 StringConstructor 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();

动态代理

我们来比较 Java 的类class和接口interface的区别:

  • 可以实例化类class(非abstract);
  • 不能实例化接口interface。

所有接口interface类型的变量总是通过某个实现了接口的类的对象向上转型再赋值给接口类型的变量:

CharSequence cs = new StringBuilder();

有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?

这是可能的,因为 Java 标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的。我们来看静态代理代码怎么写:

一、定义接口:

public interface Hello {
    void morning(String name);
}


二、编写实现类:

public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}

三、创建实例,转型为接口并调用:

Hello hello = new HelloWorld();
hello.morning("Bob");

这种方式就是我们通常编写代码的方式。
还有一种方式是动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过 JDK 提供的一个Proxy.newProxyInstance()方法创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代理。JDK 提供的动态创建接口对象的方式,就叫动态代理。

一个最简单的动态代理实现如下:

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

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

在运行期动态创建一个interface实例的方法如下:定义一个InvocationHandler实例,它负责实现接口的方法调用;通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:使用的ClassLoader,通常就是接口类的ClassLoader;需要实现的接口数组,至少需要传入一个接口进去;用来处理接口方法调用的InvocationHandler实例。将返回的Object强制转型为接口。动态代理实际上是JVM在运行期动态创建class字节码并加载的过程,它并没有什么黑魔法,把上面的动态代理改写为静态实现类大概长这样:

public class HelloDynamicProxy implements Hello {
    InvocationHandler handler;
    public HelloDynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }
    public void morning(String name) {
        handler.invoke(
           this,
           Hello.class.getMethod("morning", String.class),
           new Object[] { name });
    }
}
 

其实就是 JVM 帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。
小结

Java 标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;

动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

动态代理实现方式

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

  1. 通过实现接口的方式 -> JDK动态代理
  2. 通过继承类的方式 -> CGLIB动态代理

注:使用ASM对使用者要求比较高,使用Javassist会比较麻烦。

JDK动态代理

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;}@Overridepublic 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. 获取对应的 ClassLoaderClassLoader classLoader = userServiceImpl.getClass().getClassLoader();// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,Class[] interfaces = userServiceImpl.getClass().getInterfaces();// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用//     这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImplInvocationHandler 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)用于获取指定代理对象所关联的调用处理器

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文件

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.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑

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

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

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

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

CGLIB动态代理

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*/@Overridepublic 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执行的是子类的方法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()));}
}

测试

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

运行结果

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 {@Overridepublic 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 {@Overridepublic 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动态代理与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方法,无法进行代理。

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

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

CGlib 对接口实现代理?

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;/*** 创建代理类的工厂 该类要实现 MethodInterceptor 接口。* 该类中完成三样工作:* (1)声明目标类的成员变量,并创建以目标类对象为参数的构造器。用于接收目标对象* (2)定义代理的生成方法,用于创建代理对象。方法名是任意的。代理对象即目标类的子类* (3)定义回调接口方法。对目标类的增强这在这里完成*/
public class CGLibFactory implements MethodInterceptor {// 声明目标类的成员变量private UserService target;public CGLibFactory(UserService target) {this.target = target;}// 定义代理的生成方法,用于创建代理对象public UserService myCGLibCreator() {Enhancer enhancer = new Enhancer();// 为代理对象设置父类,即指定目标类enhancer.setSuperclass(UserService.class);/*** 设置回调接口对象 注意,只所以在setCallback()方法中可以写上this,* 是因为MethodIntecepter接口继承自Callback,是其子接口*/enhancer.setCallback(this);return (UserService) enhancer.create();// create用以生成CGLib代理对象}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("start invoke " + method.getName());Object result = method.invoke(target, args);System.out.println("end invoke " + method.getName());return result;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/709316.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

配置artifactory的反向代理和域名访问

一、概述 在许多情况下&#xff0c;组织会通过反向代理来提供对 Artifactory 的访问。在某些情况下&#xff0c;例如使用 Artifactory 作为 Docker 注册表&#xff0c;这种设置甚至是强制性的。为了简化反向代理的配置&#xff0c;Artifactory 提供了生成反向代理的功能&#x…

spring boot学习第十三篇:使用spring security控制权限

该文章同时也讲到了如何使用swagger。 1、pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instanc…

vue - - - - - vue3使用draggable拖拽组件

vue3使用draggable拖拽组件 一、组件安装二、插件使用三、遇到的问题1. missing required prop&#xff1a; “itemKey” 一、组件安装 yarn add vuedraggablenext // or npm i -S vuedraggablenext二、插件使用 <template><draggableitem-key"id"class&q…

吴恩达机器学习笔记十三 多分类问题(multiclass) Softmax 神经网络的softmax输出 softmax改进

多分类问题指可能会有多于两个的输出标签&#xff0c;而不只是0或1的问题。 Softmax算法是逻辑回归的一种推广。 例如 y 有四种可能的取值时&#xff1a; 成本函数 例如有十种类别的输出&#xff0c;此时称这个神经网络有一个softmax输出层或上层是softmax层 softmax layer有…

android高级面试视频,从入门到精通

大佬带你走进Android开发的世界&#xff0c;掌握了这些知识点&#xff0c;学习Android也可以很轻松。 核心分析内容 对于怎么学习Android&#xff0c;主要解决的是3个问题&#xff1a;学什么、怎么学 & 怎么用。 具体如下&#xff1a; 下面&#xff0c;我将带着上述几个问…

视频学习胜过读书吗

现在&#xff0c;网上的课程视频和讲座视频&#xff0c;越来越多。同样的内容&#xff0c;可以读书学习&#xff0c;也可以视频学习&#xff0c;大家喜欢哪一种&#xff1f; 我比较喜欢读书&#xff0c;实在没耐心视频学习。 书籍只要随手一翻&#xff0c;就知道大概的内容了&…

为什么Kafka这么快(Kafka高吞吐、高性能)

文章目录 问什么 Kafka 可以这么快&#xff1f;消息发送端消息存储1.零拷贝机制2.磁盘顺序读写3.稀疏索引4.页缓存5.分区和副本6.分段存储的好处 消息消费 Kafka是分布式消息系统&#xff0c;需要处理海量的消息&#xff0c;Kafka的设计是把所有的消息都写入速度低容量大的硬盘…

宝塔FTP服务设置并结合cpolar内网穿透实现远程传输文件

文章目录 1. Linux安装Cpolar2. 创建FTP公网地址3. 宝塔FTP服务设置4. FTP服务远程连接小结 5. 固定FTP公网地址6. 固定FTP地址连接 宝塔FTP是宝塔面板中的一项功能&#xff0c;用于设置和管理FTP服务。通过宝塔FTP&#xff0c;用户可以创建FTP账号&#xff0c;配置FTP用户权限…

Unity 常用操作

2D素材网站 https://craftpix.net/ https://itch.io/game-assets/tag-2d/tag-backgrounds 3D素材资源网址 https://www.mixamo.com/#/ 场景常用操作&#xff1a; 快捷键&#xff1a;QWER Q&#xff1a;Q键或鼠标中键&#xff0c;可以拉动场景。 W&#xff1a;选中物体后&…

新闻网站封锁AI爬虫 AI与新闻媒体博弈继续

随着ChatGPT等新兴AI模型的兴起&#xff0c;它们所依赖的网络爬虫正面临来自全球主流新闻网站的大规模封锁。Richard Fletcher博士团队对十个国家主流新闻网站的统计发现&#xff0c;到2023年底&#xff0c;48%的网站屏蔽了OpenAI的爬虫&#xff0c;24%屏蔽了Google的爬虫。那么…

010 Linux 进程间通信_匿名管道

前言 本文将会向你介绍匿名管道的原理以及用法&#xff0c;以及管道的使用存在的情况和管道的特性 文章重点 重点&#xff1a;匿名管道的原理&#xff0c;使用情况&#xff0c;以及特性 进程间通信 进程间通信的本质&#xff1a; 让不同的进程先看到同一份资源&#xff0c…

c#使用log4net的3种调用方法

https://blog.csdn.net/summer_top/article/details/107961245 第一步&#xff1a;下载log4net。 右键项目引用&#xff0c;进入管理NuGet包。 搜索log4net&#xff0c;下载安装。 第二步&#xff1a;创建LogHelper类。 public class LogHelper { private LogHelp…

部署PhotoMaker通过堆叠 ID 嵌入自定义逼真的人物照片

PhotoMaker只需要一张人脸照片就可以生成不同风格的人物照片&#xff0c;可以快速出图&#xff0c;无需额外的LoRA培训。 安装环境 python 3.10gitVisual Studio 2022 安装依赖库 git clone https://github.com/bmaltais/PhotoMaker.git cd PhotoMaker python -m venv venv…

C++_STL使用手册

STL基础 STL全称为 standard template library&#xff0c;中文可译为标准模板库或者泛型库&#xff0c;其包含有大量的模板类和模板函数&#xff0c;是 C 提供的一个基础模板的集合;STL由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成&#xff0c;其中后面…

为什么这么多项目经理选择考取PMP证书?

PMP认证是目前国际上项目管理领域认可度和含金量最高的证书。通过PMP就证明你的项目管理水平达到了国际标准&#xff0c;有能力从事项目管理工作&#xff0c;普通人需要很多年才能积累悟出的职场做事逻辑&#xff0c;学一个PMP就搞定了。考证花的钱&#xff0c;持证后加薪分分钟…

传感器为智能化基础,L3车规落地打开激光雷达新空间(下)

3 L3车规落地打开激光雷达新空间&#xff0c;未来进一步迭代降本 3.1 技术落地&#xff0c;智能驾驶迈入L3关键节点 L3是区分辅助驾驶与智能驾驶的关键节点&#xff0c;L0-L2“人为主、车为辅”&#xff0c;L3之后“车为主、人为辅”。随着技术持续升级&#xff0c;智能驾驶…

洛谷 P2249 【深基13.例1】查找

思路&#xff1a;这是最典型和基础的二分查找题&#xff0c;因为符合单调性和有界性&#xff0c;如果直接暴力查找的话&#xff0c;会超时&#xff0c;只需要用一下二分即可 左边界为&#xff1a;0&#xff08;因为编号最小为1&#xff09; 右边界为&#xff1a;n1&#xff0…

LeetCode # 206. 反转链表

206. 反转链表 题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1] 示例…

Java SpringBoot 获取 yml properties 自定义配置信息

Java SpringBoot 获取 yml properties 自定义配置信息 application.yml server:port: 9090servlet:context-path: /app第一种方法 HelloController package com.zhong.demo01.controller;import org.springframework.beans.factory.annotation.Value; import org.springfram…