java反射
何为反射?
如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
反射的应用场景了解么?
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。
public class DebugInvocationHandler implements InvocationHandler { /** * 代理类中的真实对象 */ private final Object target;
public DebugInvocationHandler(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); System.out.println("after method " + method.getName()); return result; }}另外,像 Java 中的一大利器 注解 的实现也用到了反射。
为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
谈谈反射机制的优缺点
优点:可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
反射实战
获取 Class 对象的四种方式
如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:
-
知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class;但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
-
通过 **
Class.forName()**传入类的全路径获取:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");- 通过对象实例**
instance.getClass()**获取:
TargetObject o = new TargetObject();Class alunbarClass2 = o.getClass();-
通过类加载器**
xxxClassLoader.loadClass()**传入类路径获取:ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
反射的一些基本操作
-
创建一个我们要使用反射操作的类
TargetObject。package cn.javaguide;public class TargetObject {private String value;public TargetObject() {value = "JavaGuide";}public void publicMethod(String s) {System.out.println("I love " + s);}private void privateMethod() {System.out.println("value is " + value);}} -
使用反射操作这个类的方法以及参数
package cn.javaguide;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {/*** 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例*/Class<?> targetClass = Class.forName("cn.javaguide.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, "JavaGuide");/*** 获取指定参数并对参数进行修改*/Field field = targetClass.getDeclaredField("value");//为了对类中的参数进行修改我们取消安全检查field.setAccessible(true);field.set(targetObject, "JavaGuide");/*** 调用 private 方法*/Method privateMethod = targetClass.getDeclaredMethod("privateMethod");//为了调用private方法我们取消安全检查privateMethod.setAccessible(true);privateMethod.invoke(targetObject);}}
java代理
代理模式
代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
代理模式有静态代理和动态代理两种实现方式
静态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
静态代理实现步骤:
- 定义一个接口及其实现类;
- 创建一个代理类同样实现这个接口
- 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
下面通过代码展示!
-
定义发送短信的接口
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");}}
运行上述代码之后,控制台打印出:
before method send()send message:javaafter method send()可以输出结果看出,我们已经增加了 SmsServiceImpl 的send()方法。
动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。
动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
guide-rpc-framework 使用的是 JDK 动态代理,我们先来看看 JDK 动态代理的使用。
另外,虽然 guide-rpc-framework 没有用到 CGLIB 动态代理 ,我们这里还是简单介绍一下其使用以及和JDK 动态代理的对比。
JDK 动态代理机制
在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。
Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... }这个方法一共有 3 个参数:
- loader :类加载器,用于加载代理对象。
- interfaces : 被代理类实现的一些接口;
- h : 实现了
InvocationHandler接口的对象;
要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
public interface InvocationHandler {
/** * 当你使用代理对象调用方法的时候实际会调用到这个方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}invoke() 方法有下面三个参数:
- proxy :动态生成的代理类
- method : 与代理类对象调用的方法相对应
- args : 当前 method 方法的参数
也就是说:你通过**Proxy** 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现**InvocationHandler** 接口的类的 **invoke()**方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。
-
JDK 动态代理类使用步骤
- 定义一个接口及其实现类;
- 自定义
InvocationHandler并重写invoke方法,在invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑; - 通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象;
-
代码示例
-
定义发送短信的接口
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 动态代理类
import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/*** @author shuang.kou* @createTime 2020年05月11日 11:23:00*/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());Object result = method.invoke(target, args);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method " + method.getName());return result;}}invoke()方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是invoke()方法,然后invoke()方法代替我们去调用了被代理对象的原生方法。 -
获取代理对象的工厂类
public class JdkProxyFactory {public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 目标类的类加载器target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler);}}getProxy():主要通过Proxy.newProxyInstance()方法获取某个类的代理对象 -
实际使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());smsService.send("java");
运行上述代码之后,控制台打印出:
before method sendsend message:javaafter method send -
CGLIB 动态代理机制
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。
public interface MethodInterceptorextends Callback{ // 拦截被代理类中的方法 public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;}- obj : 被代理的对象(需要增强的对象)
- method : 被拦截的方法(需要增强的方法)
- args : 方法入参
- proxy : 用于调用原始方法
你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。
-
CGLIB 动态代理类使用步骤
- 定义一个类;
- 自定义
MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke方法类似; - 通过
Enhancer类的create()创建代理类;
-
代码示例
不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>-
实现一个使用阿里云发送短信的类
package github.javaguide.dynamicProxy.cglibDynamicProxy;public class AliSmsService {public String send(String message) {System.out.println("send message:" + message);return message;}} -
自定义
MethodInterceptor****(方法拦截器)import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** 自定义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;}} -
获取代理类
import net.sf.cglib.proxy.Enhancer;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");
运行上述代码之后,控制台打印出:
before method sendsend message:javaafter method send -
JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
静态代理和动态代理的对比
- 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
Java Reflection
What is Reflection?
If you have studied the underlying principles of frameworks or you’ve written your own frameworks, you are surely familiar with the concept of reflection.
Reflection is often called the soul of a framework mainly because it enables us to analyze classes at runtime and invoke methods within those classes.
Through reflection you can obtain all the fields and methods of any class, and you can also call these methods and access these fields.
Do you know the use cases for reflection?
Most of the time we are writing business code and rarely encounter scenarios that directly use reflection.
However, this doesn’t mean reflection is useless. On the contrary, it’s because of reflection that you can use various frameworks so easily. Frameworks like Spring/Spring Boot, MyBatis, and others make extensive use of reflection.
These frameworks also heavily use dynamic proxies, and the implementation of dynamic proxies relies on reflection.
For example, the following is a sample code that implements dynamic proxy using the JDK, which uses the reflection class Method to invoke a specified method.
public class DebugInvocationHandler implements InvocationHandler { /** * The real object inside the proxy */ private final Object target;
public DebugInvocationHandler(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); System.out.println("after method " + method.getName()); return result; }}In addition, the implementation of Java annotations, a powerful feature, also uses reflection.
Why when you use Spring, an @Component annotation declares a class as a Spring Bean? Why can you read a value from the configuration file with an @Value annotation? How does this actually work?
All of these are because you can analyze classes using reflection and then obtain annotations on classes/fields/methods/method parameters. After you obtain the annotations, you can do further processing.
Pros and cons of the reflection mechanism
Pros: It makes our code more flexible and provides convenience for turning various frameworks into plug-and-play functionality.
Cons: It gives us the ability to analyze classes at runtime, which also increases security concerns. For example, it can bypass the safety checks of generic parameters (the safety checks for generic parameters occur at compile time). Also, reflection’s performance is somewhat slower, but for frameworks, the impact is not significant.
Reflection in practice
Four ways to obtain a Class object
If we want to obtain this information dynamically, we need to rely on the Class object. The Class object provides information about a class’s methods, variables, etc. Java provides four ways to obtain a Class object:
-
If you know the concrete class, you can use:
Class alunbarClass = TargetObject.class;However, we generally don’t know the concrete class; we usually obtain the Class object by iterating over the classes under a package. Obtaining the Class object this way will not initialize the class.
-
Through
Class.forName()to obtain by fully-qualified name:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");- Through an object instance
instance.getClass()to obtain:
TargetObject o = new TargetObject();Class alunbarClass2 = o.getClass();- Through a class loader
xxxClassLoader.loadClass()to obtain by path:
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");Obtaining a Class object via a class loader does not initialize the class, which means a series of steps including initialization are not performed; static blocks and static objects will not be executed.
Some basic operations of reflection
- Create a class that we will operate on with reflection,
TargetObject.
package cn.javaguide;
public class TargetObject { private String value;
public TargetObject() { value = "JavaGuide"; }
public void publicMethod(String s) { System.out.println("I love " + s); }
private void privateMethod() { System.out.println("value is " + value); }}- Use reflection to operate on this class’s methods and parameters
package cn.javaguide;
import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;
public class Main { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException { /** * Get the Class object of the TargetObject class and create an instance of TargetObject */ Class<?> targetClass = Class.forName("cn.javaguide.TargetObject"); TargetObject targetObject = (TargetObject) targetClass.newInstance(); /** * Get all methods defined in TargetObject */ Method[] methods = targetClass.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName()); }
/** * Get a specific method and call it */ Method publicMethod = targetClass.getDeclaredMethod("publicMethod", String.class);
publicMethod.invoke(targetObject, "JavaGuide");
/** * Get a specific parameter and modify it */ Field field = targetClass.getDeclaredField("value"); //To modify the field's value, disable the security check field.setAccessible(true); field.set(targetObject, "JavaGuide");
/** * Call a private method */ Method privateMethod = targetClass.getDeclaredMethod("privateMethod"); //To call a private method, disable the security check privateMethod.setAccessible(true); privateMethod.invoke(targetObject); }}Java Proxies
Proxy Pattern
The proxy pattern is a design pattern that is relatively easy to understand. In simple terms, we use a proxy object to replace access to the real object, so we can provide additional functionality without modifying the original target object, thereby extending the target object’s capabilities.
The main purpose of the proxy pattern is to extend the target object’s functionality, for example by adding some customized operations before or after a method of the target object executes.
There are two implementations of the proxy pattern: static proxy and dynamic proxy.
Static Proxy
In static proxy, enhancing each method of the target object is done manually, which is very inflexible (for example, if the interface adds a new method, both the target object and the proxy object must be modified) and cumbersome (you need to write a proxy class for each target class). The actual usage scenarios are very, very rare; static proxies are almost never seen in daily development.
From the JVM perspective, static proxy, at compile time, converts interfaces, implementation classes, and proxy classes into actual class files.
Static proxy steps:
- Define an interface and its implementation class;
- Create a proxy class that also implements this interface
- Inject the target object into the proxy class, and then in the proxy class call the corresponding method in the target class. This way, we can shield access to the target object through the proxy class and perform some actions before and after the target method executes.
Now, let’s show it with code!
-
Define the Sms sending interface
public interface SmsService {String send(String message);} -
Implement the Sms sending interface
public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}} -
Create a proxy class that also implements the SmsService interface
public class SmsProxy implements SmsService {private final SmsService smsService;public SmsProxy(SmsService smsService) {this.smsService = smsService;}@Overridepublic String send(String message) {// Before invoking, we can add our own operationsSystem.out.println("before method send()");smsService.send(message);// After invoking, we can also add our own operationsSystem.out.println("after method send()");return null;}} -
Usage
public class Main {public static void main(String[] args) {SmsService smsService = new SmsServiceImpl();SmsProxy smsProxy = new SmsProxy(smsService);smsProxy.send("java");}}
After running the above code, the console prints:
before method send()send message:javaafter method send()From the output, you can see that we have added the send() method of SmsServiceImpl.
Dynamic Proxy
Compared with static proxies, dynamic proxies are more flexible. We don’t need to create a separate proxy class for every target class, and we don’t need to implement an interface; we can proxy the implementation class directly (the CGLIB dynamic proxy mechanism).
From the JVM’s perspective, dynamic proxies are generated at runtime, and the bytecode is loaded into the JVM.
When talking about dynamic proxies, Spring AOP and RPC frameworks are two indispensable references; their implementations rely on dynamic proxies.
Dynamic proxies are used less in daily development, but in frameworks they are almost indispensable. Once you master dynamic proxies, it will help you understand and learn the principles behind various frameworks.
In Java, there are many ways to implement dynamic proxies, such as JDK dynamic proxies and CGLIB dynamic proxies, etc.
guide-rpc-framework uses JDK dynamic proxies; let’s first look at the usage of JDK dynamic proxies.
In addition, although guide-rpc-framework does not use CGLIB dynamic proxies, here we still briefly introduce its usage and compare with JDK dynamic proxies.
JDK Dynamic Proxy Mechanism
In the Java dynamic proxy mechanism, the InvocationHandler interface and the Proxy class are core.
The most frequently used method in the Proxy class is: newProxyInstance(), which is mainly used to generate a proxy object.
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... }This method has three parameters:
- loader : the class loader used to load the proxy object.
- interfaces : the interfaces implemented by the proxied class;
- h : the object that implements the
InvocationHandlerinterface;
To implement dynamic proxies, you must also implement InvocationHandler to customize the handling logic. When our dynamic proxy object calls a method, that call will be forwarded to the invoke method of the class implementing the InvocationHandler interface.
public interface InvocationHandler {
/** * When you call a method on the proxy object, this method is actually invoked */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}invoke() method has three parameters:
- proxy : the dynamically generated proxy class
- method : the method being invoked on the proxy object
- args : the parameters of the current method
In other words: the proxy object created by the Proxy class’s newProxyInstance() will actually call the invoke() method of the class that implements InvocationHandler when a method is invoked. You can customize the handling logic in the invoke() method, such as what to do before or after the method execution.
-
JDK dynamic proxy usage steps
- Define an interface and its implementation class;
- Create a custom
InvocationHandlerand override theinvokemethod; in theinvokemethod we will call the native method (the method of the proxied class) and add some custom processing; - Create the proxy object via
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
-
Code example
-
Define the Sms sending interface
public interface SmsService {String send(String message);} -
Implement the Sms sending interface
public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}} -
Define a JDK dynamic proxy class
import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/*** @author shuang.kou* @createTime 2020年05月11日 11:23:00*/public class DebugInvocationHandler implements InvocationHandler {/*** The real object inside the proxy*/private final Object target;public DebugInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// Before invoking, we can add our own operationsSystem.out.println("before method " + method.getName());Object result = method.invoke(target, args);// After invoking, we can also add our own operationsSystem.out.println("after method " + method.getName());return result;}}The
invoke()method: when our dynamic proxy object calls a native method, what is ultimately called is theinvoke()method, and then theinvoke()method handles calling the proxied object’s native method. -
Factory class to obtain proxy objects
public class JdkProxyFactory {public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // target class's class loadertarget.getClass().getInterfaces(), // interfaces to implement by the proxy, can specify multiplenew DebugInvocationHandler(target) // the custom InvocationHandler for the proxy object);}}getProxy()mainly usesProxy.newProxyInstance()to obtain a proxy object for a class. -
Usage
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());smsService.send("java");
After running the above code, the console prints:
before method sendsend message:javaafter method send -
CGLIB Dynamic Proxy Mechanism
One of the most critical limitations of JDK dynamic proxies is that they can only proxy classes that implement interfaces.
To solve this, you can use CGLIB dynamic proxy to avoid this limitation.
CGLIB(Code Generation Library) is a bytecode generation library based on ASM that allows us to modify and dynamically generate bytecode at runtime. CGLIB proxies by inheritance. Many well-known open-source frameworks use CGLIB, for example in Spring’s AOP module: if the target object implements an interface, the default is to use a JDK dynamic proxy; otherwise, CGLIB dynamic proxy is used.
In the CGLIB dynamic proxy mechanism MethodInterceptor interface and Enhancer class are core.
You need to customize a MethodInterceptor and override the intercept method; intercept is used to intercept and enhance the proxied class’s methods.
public interface MethodInterceptorextends Callback{ // Intercept methods in the proxied class public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;}- obj : the proxied object (the object to be enhanced)
- method : the intercepted method (the method to be enhanced)
- args : method parameters
- proxy : used to call the original method
You can dynamically obtain the proxied class via the Enhancer class; when the proxy class calls a method, the actual call goes to the intercept method in the MethodInterceptor.
-
CGLIB dynamic proxy usage steps
- Define a class;
- Create a custom
MethodInterceptorand override theinterceptmethod;interceptis used to intercept and enhance the proxied class’s methods, similar to theinvokemethod in JDK dynamic proxies; - Use the
Enhancerclass’screate()to create the proxy class;
-
Code example
Unlike JDK dynamic proxy, no extra dependency is required. CGLIB(Code Generation Library) is actually an open-source project; if you want to use it, you need to manually add the related dependency.
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>-
Implement a class that uses Alibaba Cloud to send SMS
package github.javaguide.dynamicProxy.cglibDynamicProxy;public class AliSmsService {public String send(String message) {System.out.println("send message:" + message);return message;}} -
Create a custom
MethodInterceptor** (method interceptor)**import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** Custom MethodInterceptor*/public class DebugMethodInterceptor implements MethodInterceptor {/*** @param o the proxied object (the object to be enhanced)* @param method the intercepted method (the method to be enhanced)* @param args method arguments* @param methodProxy used to call the original method*/@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// Before invoking, we can add our own operationsSystem.out.println("before method " + method.getName());Object object = methodProxy.invokeSuper(o, args);// After invoking, we can also add our own operationsSystem.out.println("after method " + method.getName());return object;}} -
Get the proxy class
import net.sf.cglib.proxy.Enhancer;public class CglibProxyFactory {public static Object getProxy(Class<?> clazz) {// Create dynamic proxy enhancement classEnhancer enhancer = new Enhancer();// Set the class loaderenhancer.setClassLoader(clazz.getClassLoader());// Set the proxied classenhancer.setSuperclass(clazz);// Set the method interceptorenhancer.setCallback(new DebugMethodInterceptor());// Create the proxy classreturn enhancer.create();}} -
Usage
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);aliSmsService.send("java");
After running the above code, the console prints:
before method sendsend message:javaafter method send -
JDK Dynamic Proxy vs CGLIB Dynamic Proxy
- JDK dynamic proxies can only proxy classes that implement interfaces, whereas CGLIB can proxy classes that do not implement any interfaces. Additionally, CGLIB proxies by generating a subclass of the proxied class, so it cannot proxy final classes and final methods.
- In terms of efficiency, in most cases JDK dynamic proxies are better; with newer JDK versions, this advantage becomes more pronounced.
Static Proxy vs Dynamic Proxy
- Flexibility:Dynamic proxies are more flexible; you don’t need to necessarily implement an interface, you can proxy the implementation class directly, and you don’t need to create a proxy class for every target class. Also, with static proxies, if a new method is added to the interface, both the target object and the proxy object must be modified—that’s very troublesome!
- JVM level:Static proxies convert the interfaces, implementations, and proxies into actual class files at compile time. Dynamic proxies generate bytecode at runtime and load it into the JVM.
Javaリフレクション
リフレクションとは?
フレームワークの仕組みの底層原理を研究したことがある人、あるいは自分たちでフレームワークを作ったことがある人は、リフレクションという概念に馴染みがあるはずです。
リフレクションがフレームワークの魂と呼ばれる理由は、実行時にクラスを分析し、クラス内のメソッドを実行する能力を私たちに与えるからです。
リフレクションを通じて、任意のクラスのすべての属性とメソッドを取得でき、これらのメソッドや属性を呼び出すこともできます。
リフレクションの適用シーンを知っていますか?
私たちは普段ビジネスコードの作成を主に行い、直接リフレクション機構を使用する場面に触れることは少ないです。
しかし、これはリフレクションが役に立たないという意味ではありません。むしろリフレクションのおかげで、さまざまなフレームワークをこのように容易に利用できます。Spring/Spring Boot、MyBatis などのフレームワークはリフレクションを大量に使用しています。
これらのフレームワークではダイナミックプロキシも大量に用いられており、ダイナミックプロキシの実現にはリフレクションが依存しています。
以下はJDKを用いて動的プロキシを実装したサンプルコードで、その中でリフレクションクラスの Method を使って指定したメソッドを呼び出しています。
public class DebugInvocationHandler implements InvocationHandler { /** * 代理类中的真实对象 */ private final Object target;
public DebugInvocationHandler(Object target) { this.target = target; }
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); System.out.println("after method " + method.getName()); return result; }}另外、Java の大きな武器である アノテーション の実装もリフレクションを用いています。
なぜ Spring を使うとき、1つの @Component アノテーションだけでクラスを Spring Bean に宣言できるのでしょうか。なぜ 1つの @Value アノテーションで設定ファイルの値を読み取れるのでしょうか。結局どう作用しているのでしょうか?
これらはすべて、リフレクションを基にクラスを分析し、クラス/属性/メソッド/メソッドのパラメータ上的な注釈を取得できるためです。注釈を取得したら、さらに処理を行うことができます。
リフレクション機構の長所と短所
長所:私たちのコードをより柔軟にし、さまざまなフレームワークに対してすぐに使える機能を提供する利便さ。
短所:実行時にクラスを分析する能力を得る一方で、セキュリティ上の問題が増える可能性があります。例えば、ジェネリックパラメータの安全性チェックを回避できてしまうことです(ジェネリックの安全性チェックはコンパイル時に行われます)。また、リフレクションのパフォーマンスはやや低下しますが、フレームワークにとっては実際には大きな影響はありません。
リフレクション実戦
Class オブジェクトを取得する4つの方法
これらの情報を動的に取得するには Class オブジェクトに依存します。Classオブジェクトはクラスのメソッドや変数などの情報を実行時のプログラムに伝えます。Java には Class オブジェクトを取得する4つの方法が用意されています:
-
具体的なクラスが分かっている場合に使用:
Class alunbarClass = TargetObject.class;ただし私たちは通常、具体的なクラスが分からないので、パッケージ内のクラスを走査してClassオブジェクトを取得します。この方法で取得したClassオブジェクトは初期化を行いません。
-
Class.forName()を用いて、クラスの全パスを渡して取得:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");- オブジェクトのインスタンスから取得:
instance.getClass()
TargetObject o = new TargetObject();Class alunbarClass2 = o.getClass();- クラスローダーを介して
xxxClassLoader.loadClass()を用いてクラスパスを渡して取得:
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");クラスローダーを介して Class オブジェクトを取得しても初期化は行われません。つまり初期化を含む一連の手順は実行されず、静的コードブロックや静的オブジェクトは実行されません。
リフレクションの基本操作
- リフレクションで操作する対象のクラス
TargetObjectを作成します。
package cn.javaguide;
public class TargetObject { private String value;
public TargetObject() { value = "JavaGuide"; }
public void publicMethod(String s) { System.out.println("I love " + s); }
private void privateMethod() { System.out.println("value is " + value); }}- このクラスのメソッドとパラメータをリフレクションで操作する
package cn.javaguide;
import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;
public class Main { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException { /** * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例 */ Class<?> targetClass = Class.forName("cn.javaguide.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, "JavaGuide");
/** * 获取指定参数并对参数进行修改 */ Field field = targetClass.getDeclaredField("value"); //为了对类中的参数进行修改我们取消安全检查 field.setAccessible(true); field.set(targetObject, "JavaGuide");
/** * 调用 private 方法 */ Method privateMethod = targetClass.getDeclaredMethod("privateMethod"); //为了调用private方法我们取消安全检查 privateMethod.setAccessible(true); privateMethod.invoke(targetObject); }}Javaプロキシ
プロキシパターン
プロキシパターンは理解しやすいデザインパターンの一つです。簡単に言えば、私たちは代理オブジェクトを用いて実オブジェクト(real object)へのアクセスを代替し、元のターゲットオブジェクトを変更することなく、追加機能を提供し、ターゲットオブジェクトの機能を拡張します。
プロキシパターンの主な作用はターゲットオブジェクトの機能を拡張することです。例えば、ターゲットオブジェクトの某メソッドの前後に自分の処理を追加することができます。
プロキシパターンには静的プロキシと動的プロキシの2種類の実装方法があります。
静的プロキシ
静的プロキシでは、ターゲットオブジェクトの各メソッドの拡張は手動で行われるため非常に柔軟性に欠けます(例えば、インターフェースに新しいメソッドが追加されると、ターゲットオブジェクトと代理オブジェクトの両方を修正する必要があります)し、煩雑です(各ターゲットクラスごとに代理クラスを個別に作成する必要があります)。 実際の適用シーンは非常に少なく、日常の開発では静的プロキシの場面はほとんど見られません。
上記は実装と適用の観点からの静的プロキシについて述べています。JVMレベルで見ると、静的プロキシはコンパイル時にインターフェース、実装クラス、代理クラスなどをすべて実際のクラスファイルに変換します。
静的プロキシの実装手順:
- 定義一个インタフェースとその実装クラス;
- 代理クラスを作成して同じインタフェースを実装する
- 目標オブジェクトを代理クラスに注入し、代理クラスの対応メソッドからターゲットクラスの対応メソッドを呼び出す。こうすることで代理クラスを介してターゲットオブジェクトへのアクセスを遮断し、ターゲットメソッドの実行前後に自分のしたいことを実行できます。
以下はコードで示します!
- SMS送信インターフェースを定義
public interface SmsService { String send(String message);}- SMS送信インターフェースを実装
public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; }}- 代理クラスを作成し、同じくSMS送信インターフェースを実装
public class SmsProxy implements SmsService {
private final SmsService smsService;
public SmsProxy(SmsService smsService) { this.smsService = smsService; }
@Override public 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"); }}运行上述コードを実行すると、コンソールには次のように表示されます:
before method send()send message:javaafter method send()結果から、私たちはすでに SmsServiceImpl の send() メソッドを追加したことが分かります。
動的プロキシ
静的プロキシと比較して、動的プロキシはより柔軟です。各ターゲットクラスごとに代理クラスを個別に作成する必要がなく、インターフェースを実装することを必須とせず、実装クラスを直接代理できます(CGLIB 動的プロキシ機構)。
JVM の観点から見ると、動的プロキシは実行時にクラスのバイトコードを動的に生成し、JVM にロードします。
動的プロキシといえば、Spring AOP、RPC フレームワークは2つとも必ず挙げるべき例です。実装はどちらも動的プロキシに依存しています。
日常の開発で動的プロキシを使う場面はそれほど多くありませんが、フレームワークではほぼ必須の技術です。動的プロキシを習得すると、さまざまなフレームワークの原理を理解し学ぶ際にも非常に役立ちます。
Java には動的プロキシの実装方法がいくつかあり、例えば JDK 動的プロキシ、CGLIB 動的プロキシ などがあります。
guide-rpc-framework は JDK 動的プロキシを使用しています。まずは JDK 動的プロキシの使い方を見てみましょう。
また、guide-rpc-framework は CGLIB 動的プロキシ を使っていませんが、ここではその使用方法とJDK 動的プロキシとの比較を簡単に紹介します。
JDK 動的プロキシ機構
Java の動的プロキシ機構では InvocationHandler インターフェースと Proxy クラスがコアです。
Proxy クラスで最も頻繁に使われるメソッドは:newProxyInstance() で、このメソッドは主に代理オブジェクトを生成します。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... }このメソッドは3つのパラメータがあります:
- loader :クラスローダー。代理オブジェクトをロードします。
- interfaces : 代理されるクラスが実装するいくつかのインターフェース;
- h :
InvocationHandlerインターフェースを実装したオブジェクト;
動的プロキシを実現するには、InvocationHandler を実装して独自の処理ロジックを定義する必要があります。動的プロキシのオブジェクトがメソッドを呼び出すと、その呼び出しは実装された InvocationHandler の invoke メソッドへ転送されて呼び出されます。
public interface InvocationHandler {
/** * 当你使用代理对象调用方法的时候实际会调用到这个方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}invoke() メソッドには以下の3つのパラメータがあります:
- proxy :動的に生成されたプロキシクラス
- method : プロキシクラスのオブジェクトが呼び出すメソッドに対応
- args : 現在の method のパラメータ
つまり:あなたが Proxy クラスの newProxyInstance() で作成した代理オブジェクトがメソッドを呼び出すとき、実際には InvocationHandler を実装したクラスの invoke() メソッドが呼び出されます。invoke() メソッド内で処理をカスタマイズできます。例えばメソッドの実行前後に何をするか。
-
JDK 動的プロキシクラスの使用手順
- インターフェースとその実装クラスを定義する;
- カスタム
InvocationHandlerを定義し、invokeメソッドをオーバーライドします。invokeメソッド内で元のメソッド(被代理クラスのメソッド)を呼び出して、独自の処理を追加します; Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)メソッドで代理オブジェクトを作成します;
-
コード例
-
SMS送信のインターフェースを定義
public interface SmsService {String send(String message);} -
SMS送信のインターフェースを実装
public class SmsServiceImpl implements SmsService {public String send(String message) {System.out.println("send message:" + message);return message;}} -
JDK 動的プロキシクラスを定義
import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/*** @author shuang.kou* @createTime 2020年05月11日 11:23:00*/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());Object result = method.invoke(target, args);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method " + method.getName());return result;}}invoke()メソッド: 動的プロキシオブジェクトが元のメソッドを呼び出すと、最終的にinvoke()メソッドが呼び出され、invoke()が代理対象の元のメソッドを呼び出します。 -
代理オブジェクトのファクトリークラスを取得
public class JdkProxyFactory {public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 目標クラスのクラスローダーtarget.getClass().getInterfaces(), // 代理が実装するインターフェース、複数指定可能new DebugInvocationHandler(target) // 代理オブジェクトに対応するカスタム InvocationHandler);}}getProxy():主にProxy.newProxyInstance()メソッドによって特定のクラスの代理オブジェクトを取得します。 -
実際の使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());smsService.send("java");
実行すると、コンソールには次のように表示されます:
before method sendsend message:javaafter method send -
CGLIB 動的プロキシ機構
JDK 動的プロキシには最も致命的な問題があり、それはインターフェースを実装したクラスのみを代理できることです。
この問題を解決するために、CGLIB動的プロキシ機構を用いて回避することができます。
CGLIB(Code Generation Library)は、ASM に基づくバイトコード生成ライブラリで、実行時にバイトコードを変更したり動的生成したりすることを可能にします。CGLIB は継承によって代理を実現します。多くの有名なオープンソースフレームワークが CGLIB を使用しています。例えば Spring の AOP モジュールでは、ターゲットオブジェクトがインターフェースを実装している場合はデフォルトで JDK 動的プロキシを使用し、そうでなければ CGLIB 動的プロキシを使用します。
在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类は核心です。
自分で MethodInterceptor を定義し、intercept メソッドをオーバーライドします。intercept は、代理されたクラスのメソッドを拡張する際の処理を挟むために使われます。
public interface MethodInterceptorextends Callback{ // 拦截被代理类中的方法 public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;}- obj : 被代理のオブジェクト(拡張が必要なオブジェクト)
- method : 拦截するメソッド(拡張が必要なメソッド)
- args : メソッドの引数
- proxy : 元のメソッドを呼び出すため
Enhancer クラスを通じて動的に被代理クラスを取得します。代理クラスのメソッド呼び出し時には、実際には MethodInterceptor の intercept メソッドが呼び出されます。
-
CGLIB 動的プロキシクラスの使用手順
- クラスを定義する;
- 自作の
MethodInterceptorを定義し、interceptメソッドを上書きします。interceptは拡張される被代理クラスのメソッドを拡張するのに使用され、JDK 動的プロキシのinvokeメソッドと同様です; Enhancerクラスのcreate()でプロキシクラスを作成します;
-
コード例
静的な JDK 動的プロキシとは異なり、追加の依存関係は不要です。CGLIB(Code Generation Library) は実際にはオープンソースプロジェクトで、もし使用する場合は関連依存関係を手動で追加する必要があります。
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>-
AliSmsService のクラスを実装
package github.javaguide.dynamicProxy.cglibDynamicProxy;public class AliSmsService {public String send(String message) {System.out.println("send message:" + message);return message;}} -
自作の
MethodInterceptor(メソッドインターセプター)import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** 自定义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;}} -
代理クラスを取得
import net.sf.cglib.proxy.Enhancer;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");
実行すると、コンソールには次のように表示されます:
before method sendsend message:javaafter method send -
JDK 動的プロキシと CGLIB 動的プロキシの比較
- JDK 動的プロキシは、インターフェースを実装したクラスを代理するか、直接インターフェースを代理することしかできません。 一方、CGLIB はインターフェースを実装していないクラスも代理できます。 また、CGLIB 動的プロキシは代理クラスのサブクラスを生成して被代理クラスのメソッド呼び出しをインターセプトするため、final として宣言されたクラスやメソッドを代理することはできません。
- それぞれの効率性については、多くの状況で JDK 動的プロキシのほうが優れており、JDK のバージョンが上がるにつれてこの利点はより顕著になります。
静的プロキシと動的プロキシの比較
- 柔軟性:動的プロキシはより柔軟で、必須でインターフェースを実装する必要はなく、実装クラスを直接代理できます。また、静的プロキシでは、インターフェースに新しいメソッドが追加されると、ターゲットオブジェクトと代理オブジェクトの両方を修正する必要があり、これは非常に煩雑です!
- JVM レイヤー:静的プロキシはコンパイル時にインターフェース、実装クラス、代理クラスといったものを多くの実際のクラスファイルへと変換します。動的プロキシは実行時にクラスのバイトコードを動的に生成し、JVM にロードします。
部分信息可能已经过时









