Java反射、静态代理、动态代理

devtools/2025/1/17 6:52:44/

往期推荐

Java io模型-CSDN博客

如何设计一个能根据任务优先级来执行的线程池-CSDN博客

Web实时消息推送的几种方案_setmessageinnerhtml is not defined-CSDN博客

yum、dnf、apt包管理工具-CSDN博客

概述

反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

Spring、mybatis、动态代理、注解都是使用了反射。

优点:可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。

缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

获取Class对象的4种方式 

如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。

  • 知道具体类名,直接类名.class
  • 通过对象实例instance.getClass()获取
  • 通过 Class.forName()传入类的全路径获取
  • 通过类加载器xxxClassLoader.loadClass()传入类路径获取
java">//定义反射类
public class TargetObject {private String value;public TargetObject() {value = "QingQiu";}public void publicMethod(String s) {System.out.println("I love " + s);}private void privateMethod() {System.out.println("value is " + value);}
}//使用反射操作上面的类方法及属性
public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {/*** 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例*/Class<?> targetClass = Class.forName("com.serein.TargetObject");TargetObject targetObject = (TargetObject) targetClass.newInstance();/*** 获取 TargetObject 类中定义的所有方法*/Method[] methods = targetClass.getDeclaredMethods();for (Method method : methods) {System.out.println(method.getName());}/*** 获取指定方法并调用*/Method publicMethod = targetClass.getDeclaredMethod("publicMethod",String.class);publicMethod.invoke(targetObject, "QingQiu");/*** 获取指定参数并对参数进行修改*/Field field = targetClass.getDeclaredField("value");//为了对类中的参数进行修改我们取消安全检查field.setAccessible(true);field.set(targetObject, "serein");/*** 调用 private 方法*/Method privateMethod = targetClass.getDeclaredMethod("privateMethod");//为了调用private方法我们取消安全检查privateMethod.setAccessible(true);privateMethod.invoke(targetObject);}
}

常见的反射应用场景

  • 加载数据库驱动 ,Class.forName(com.mysql.cj.jdbc.Driver)
  • 加载配置文件,Spring通过xml装载Bean的过程:
    • 将xml配置文件加载入内存
    • java类里面解析xml的内容,得到对应实体类的字节码字符串以及相关的属性信息
    • 使用反射机制,根据这个字符串获得某个类的Class实例动态配置实例的属性

代理

通过代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。比喻:活动方要请明星出席,不会直接去找明星(真实对象),而是去找其经纪人(代理对象)

静态代理

静态代理实现步骤

  1. 定义一个接口及其实现类
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
代码演示 
java">//定义发送短信的接口
public interface SmsService {String send(String message);
}//实现发送短信的接口
public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}
}//创建代理类并同样实现发送短信的接口
public class SmsProxy implements SmsService {private final SmsService smsService;public SmsProxy(SmsService smsService) {this.smsService = smsService;}@Overridepublic String send(String message) {//调用方法之前,我们可以添加自己的操作System.out.println("before method send()");smsService.send(message);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method send()");return null;}
}//实际使用
public class Main {public static void main(String[] args) {SmsService smsService = new SmsServiceImpl();SmsProxy smsProxy = new SmsProxy(smsService);smsProxy.send("java");}
}

动态代理

JDK动态代理

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

java">// 常用
// loader :类加载器,用于加载代理对象。
// interfaces : 被代理类实现的一些接口;
// h : 实现了 InvocationHandler 接口的对象;public static Object newProxyInstance (ClassLoader loader, 
Class<?>[] interfaces, InvocationHandler h)//这个私有方法通常是在实现代理类时,由 JVM 或框架内部的机制调用,来处理更复杂的代理类生成逻辑。
private static Object newProxyInstance(Class<?> caller,Constructor<?> cons,
InvocationHandler h)

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

java">public interface InvocationHandler {/*** 当你使用代理对象调用方法的时候实际会调用到这个方法*/public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;// proxy :动态生成的代理类// method : 与代理类对象调用的方法相对应// args : 当前 method 方法的参数
}
JDK动态代理实现步骤
  • 定义一个接口及其实现类;
  • 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(目标类的方法)并自定义一些处理逻辑;
  • 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
代码演示 
java">//定义发送短信的接口
public interface SmsService {String send(String message);
}//实现发送短信的接口
public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}
}//定义JDK动态代理类
public class DebugInvocationHandler implements InvocationHandler {/*** 代理类中的真实对象*/private final Object target;public DebugInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {//调用方法之前,我们可以添加自己的操作System.out.println("before method " + method.getName());//当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。Object result = method.invoke(target, args);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method " + method.getName());return result;}
}//获取代理对象的工厂类
public class JdkProxyFactory {public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 目标类的类加载器target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler);}
}//实际使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

cglib动态代理

JDK动态代理通过实现接口实现代理,而CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

java">// obj : 被代理的对象(需要增强的对象)
// method : 被拦截的方法(需要增强的方法)
// args : 方法入参
// proxy : 用于调用原始方法public interface MethodInterceptor
extends Callback{// 拦截被代理类中的方法public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
CGLIB动态代理类使用步骤
  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;
代码演示 
java"><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>//实现一个使用阿里云发送短信的类
public class AliSmsService {public String send(String message) {System.out.println("send message:" + message);return message;}
}//自定义 MethodInterceptor(方法拦截器)
public class DebugMethodInterceptor implements MethodInterceptor {/*** @param o           被代理的对象(需要增强的对象)* @param method      被拦截的方法(需要增强的方法)* @param args        方法入参* @param methodProxy 用于调用原始方法*/@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//调用方法之前,我们可以添加自己的操作System.out.println("before method " + method.getName());Object object = methodProxy.invokeSuper(o, args);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method " + method.getName());return object;}}//获取代理类
public class CglibProxyFactory {public static Object getProxy(Class<?> clazz) {// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(clazz.getClassLoader());// 设置被代理类enhancer.setSuperclass(clazz);// 设置方法拦截器enhancer.setCallback(new DebugMethodInterceptor());// 创建代理类return enhancer.create();}
}//实际使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

看到这里似乎发现cglib也是实现了一个接口重写intercept()来增强的,那么继承体现在哪里呢?其实在使用 CGLIB 进行代理时,CGLIB 会通过继承目标类,生成一个新的子类 Target$Proxy,并重写目标类的非 final 方法。CGLIB 会在这些重写的方法中加入代理逻辑。

静态代理和动态代理对比

  • 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类(cglib),并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
  • JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

JDK代理和CGLIB代理对比

  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  • 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显

http://www.ppmy.cn/devtools/151202.html

相关文章

在使用 GitLab API 时,如果只能获取少量的 Merge Request(MR)信息,而没有完整的数据

在使用 GitLab API 时&#xff0c;如果只能获取少量的 Merge Request&#xff08;MR&#xff09;信息&#xff0c;而没有完整的数据&#xff0c;通常是因为以下原因之一&#xff1a; 1. 分页限制 GitLab API 默认会分页返回数据&#xff0c;每页的默认数量是 20 条&#xff08…

关于《关卡1:Pandas处理时间数据》学习心得分享

碎碎念&#xff1a;由于我在时间序列分析方面的知识尚显薄弱&#xff0c;因此参加了和鲸举办的时间序列数据处理训练营&#xff0c;期望能够提升相关技能。同时&#xff0c;我借助了GPT来补充一些内容&#xff0c;希望这些分享能对各位读者有所帮助。欢迎大家一起学习交流&…

深入理解 Entity、VO、QO、DTO 的区别及其在 MVC 架构中的应用

文章背景 在现代软件开发中&#xff0c;我们经常会接触到各种数据结构的概念&#xff0c;比如 Entity、VO&#xff08;Value Object&#xff09;、QO&#xff08;Query Object&#xff09;、DTO&#xff08;Data Transfer Object&#xff09;等。这些概念尽管看似相似&#xff…

【算法】图解二叉树的前中后序遍历

目录 1.递归序实现 2.非递归实现 二叉树的节点结构 public static class Node {public int value;public Node left;public Node right;public Node(int data) {this.value data;} } 1.递归序实现 递归的方法遍历二叉树时每一个节点都会被访问三次 public static void f…

【微信小程序】let和const-综合实训

let 和 const 都是用于声明变量的关键字&#xff0c;它们与传统的 var 关键字相比&#xff0c;有很多不同之处。 let 声明块级作用域变量&#xff0c;可再赋值&#xff1b;const 声明块级作用域常量&#xff0c;不可再赋值。 以下是它们的详细介绍&#xff1a; 一、基本概念…

Ruby语言的网络编程

Ruby语言的网络编程 引言 Ruby是一种高度抽象的动态编程语言&#xff0c;以其简洁的语法和强大而灵活的功能而闻名。自1995年由松本行弘&#xff08;Yukihiro Matsumoto&#xff09;发布以来&#xff0c;Ruby便吸引了无数开发者&#xff0c;尤其是在Web开发领域。随着互联网的…

【Uniapp-Vue3】pages.json页面路由globalStyle的属性

项目的全局配置在pages.json中。 一、导航栏设置 二、下拉刷新设置 下拉就可以看到设置的样式 三、上拉触底 这个页面中&#xff0c;向下滑动页面到底部就会输出“到底了” 现在将触底距离设置为500 走到半路就会输出“到底了”

FastDDS安装测试记录

1、安装依赖的软件 sudo apt install cmake g python3-pip wget git sudo apt install libasio-dev libtinyxml2-dev sudo apt install libssl-dev sudo apt install libp11-dev libengine-pkcs11-openssl sudo apt install softhsm22、安装foonathan_memory_vendor cd ~/Fas…