可以直接看总结和粗体和有颜色的字体
问:很多框架(如 Spring)利用 Java 的动态代理机制来实现拦截功能。当一个类实现了接口时,框架可以相对容易地使用 JDK 动态代理来创建一个代理对象。这个代理对象会包装原始的实现类对象,并在方法调用前后插入拦截器的逻辑。继续详细的说明一下这个代理对象的创建
以下是关于 Spring 等框架利用 JDK 动态代理创建代理对象的详细说明:
一、JDK 动态代理的工作原理
-
接口与实现类的关系:
- 当一个类实现了某个接口时,它承诺遵守该接口定义的契约,即实现接口中声明的所有方法。这使得 JDK 动态代理可以基于这个接口来创建代理对象。
- 例如,有一个接口
MyInterface
和实现类MyImplementation
,接口中定义了方法doSomething()
,实现类具体实现了这个方法。通过接口,JDK 动态代理可以在运行时创建一个代理对象,该对象看起来像是实现了MyInterface
的类,但实际上它在方法调用前后插入了额外的逻辑。
-
代理对象的创建过程:
- JDK 动态代理使用
java.lang.reflect.Proxy
类来创建代理对象。这个类有一个静态方法newProxyInstance
,它接受三个参数:ClassLoader
:用于加载代理类的类加载器。通常可以使用目标对象的类加载器,以确保代理类能够在正确的环境中被加载。Class<?>[] interfaces
:一个数组,包含代理对象要实现的接口。在这种情况下,就是目标类实现的接口。InvocationHandler
:一个实现了InvocationHandler
接口的对象,它定义了代理对象的方法调用行为。
- JDK 动态代理使用
例如:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkDynamicProxyExample {
public static void main(String[] args) {
MyInterface target = new MyImplementation();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{MyInterface.class},
new MyInvocationHandler(target));
proxy.doSomething();
}
}
interface MyInterface {
void doSomething();
}
class MyImplementation implements MyInterface {
@Override
public void doSomething() {
System.out.println("原始实现类的方法被调用");
}
}
class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在方法调用前执行一些逻辑");
Object result = method.invoke(target, args);
System.out.println("在方法调用后执行一些逻辑");
return result;
}
}
- 在这个例子中,
Proxy.newProxyInstance
方法创建了一个实现了MyInterface
的代理对象。当调用代理对象的doSomething
方法时,实际上会调用MyInvocationHandler
中的invoke
方法,在这个方法中可以在目标方法调用前后执行额外的逻辑。
二、在框架中的应用(以 Spring 为例)
- 拦截器与代理对象的结合:
- Spring 框架利用 JDK 动态代理来实现拦截器功能。当一个 bean 实现了某个接口时,Spring 可以为这个 bean 创建一个代理对象,该代理对象在方法调用前后会调用注册的拦截器。
- 拦截器实现了
HandlerInterceptor
接口,其中的preHandle
、postHandle
和afterCompletion
方法分别在请求处理的不同阶段被调用。这些方法可以在目标方法执行前后进行一些预处理和后处理操作,比如日志记录、权限检查、性能监控等。 - 例如,在 Spring 的配置类中注册拦截器:
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
拦截器:
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("在请求处理前执行拦截器逻辑");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("在请求处理后执行拦截器逻辑");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("在视图渲染后执行拦截器逻辑");
}
}
- 当一个请求进入 Spring 的处理流程时,如果目标对象实现了接口,Spring 会创建一个代理对象,使得在调用目标对象的方法时,拦截器的逻辑能够被插入到方法调用前后。
- 代理对象的优势:
- 解耦:通过代理对象,拦截器的逻辑与业务逻辑实现了解耦。业务代码不需要关心拦截器的存在,而拦截器可以在不修改业务代码的情况下对方法调用进行干预。
- 灵活性:可以方便地添加、修改或删除拦截器,而不需要修改业务逻辑代码。这使得系统具有更高的灵活性和可维护性。
- 可扩展性:可以根据不同的需求创建不同类型的拦截器,并将它们组合起来应用到不同的业务场景中,提高了系统的可扩展性。
总之,JDK 动态代理在框架中扮演着重要的角色,通过创建代理对象并在方法调用前后插入拦截器的逻辑,实现了对业务逻辑的非侵入式增强和控制。当一个类实现了接口时,框架可以更容易地利用这种机制来实现各种功能,如拦截器、事务管理、安全控制等。
问:通过接口,JDK 动态代理可以在运行时创建一个代理对象。这个代理对象是根据这个接口创建的是吗
是的,通过接口,JDK 动态代理可以在运行时创建一个代理对象,这个代理对象是根据接口创建的。
具体来说,当使用 java.lang.reflect.Proxy
的 newProxyInstance
方法时,需要传入一个包含接口的数组,代理对象会实现这些接口。
这个代理对象具有以下特点:
一、实现接口方法
代理对象实现了指定的接口,这意味着它可以像一个真正实现了该接口的类的实例一样被使用。对于每个接口方法的调用,都会被转发到一个特定的 InvocationHandler
实现中,由这个 InvocationHandler
来决定如何处理方法调用。
例如:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface MyInterface {
void doSomething();
}
class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("doSomething")) {
System.out.println("代理对象执行了 doSomething 方法");
}
return null;
}
}
public class Main {
public static void main(String[] args) {
MyInterface target = new MyInterface() {
@Override
public void doSomething() {
// 这里可以是具体的实现逻辑,但在代理的情况下可能不会直接执行
}
};
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{MyInterface.class},
new MyInvocationHandler());
proxy.doSomething();
}
}
在这个例子中,代理对象 proxy
实现了 MyInterface
,当调用 proxy.doSomething()
时,实际上会调用 MyInvocationHandler
中的 invoke
方法来处理这个方法调用。
二、与接口的紧密关联
-
方法签名一致性:
- 代理对象的方法签名必须与接口中定义的方法签名完全一致。这包括方法名称、参数类型和返回类型。这样,当客户端代码调用代理对象的方法时,它看起来就像是在调用一个真正实现了该接口的对象的方法。
- 例如,如果接口
MyInterface
中有一个方法void doSomething(String arg)
,那么代理对象也必须有一个具有相同签名的方法,并且在调用这个方法时,会将其转发到InvocationHandler
的invoke
方法中进行处理。
-
接口规范遵循:
- 由于代理对象实现了接口,它必须遵循接口定义的规范。这包括方法的行为约定、抛出的异常等。客户端代码可以依赖接口的规范来使用代理对象,而不需要知道它实际上是一个代理。
- 例如,如果接口
MyInterface
中的方法规定在特定情况下抛出一个MyException
异常,那么代理对象在处理相应方法调用时也应该遵循这个约定,要么自己抛出这个异常,要么确保在InvocationHandler
的invoke
方法中处理异常并采取适当的行动。
总之,JDK 动态代理通过接口来创建代理对象,使得代理对象可以在运行时替代真正实现了该接口的对象,并在方法调用前后插入额外的逻辑。这种机制在很多框架中被广泛应用,以实现面向切面编程(AOP)等功能,提高了代码的可维护性和可扩展性。
问:当调用代理对象的 doSomething方法时,实际上会调用 MyInvocationHandler 中的 invoke方法,在这个方法中可以在目标方法调用前后执行额外的逻辑。他是怎么调用到invoke方法的
当调用代理对象的方法时,会自动调用 InvocationHandler
的 invoke
方法,这是通过 Java 的反射机制实现的。具体过程如下:
- 当创建代理对象时,通过
Proxy.newProxyInstance
方法传入了一个实现了InvocationHandler
接口的对象(在你的例子中是MyInvocationHandler
的实例)。这个代理对象内部持有对这个InvocationHandler
的引用。 - 当客户端代码调用代理对象的方法时,例如
proxy.doSomething()
,Java 虚拟机实际上会检查这个对象是否是一个代理对象。 - 如果是代理对象,Java 虚拟机会自动将方法调用转发到代理对象关联的
InvocationHandler
的invoke
方法。在转发过程中,会将以下信息作为参数传递给invoke
方法:proxy
:代理对象本身,在你的例子中就是通过Proxy.newProxyInstance
创建的对象。method
:被调用的方法对象,表示客户端调用的具体方法,比如doSomething
方法对应的Method
对象。args
:方法调用的参数数组,如果doSomething
方法有参数,这里就会包含这些参数。
- 在
InvocationHandler
的invoke
方法中,可以根据这些参数决定如何处理方法调用。可以在调用目标方法之前执行一些额外的逻辑,然后通过method.invoke(target, args)
来调用目标对象的实际方法(如果有目标对象的话),最后还可以在目标方法调用之后执行一些其他逻辑。
问:那mybatis的拦截器没有实现这个InvocationHandler 类,他怎么实现的动态代理呢
MyBatis 的拦截器实现机制与 JDK 动态代理的 InvocationHandler
有所不同。
MyBatis 拦截器是通过插件机制实现的。当你实现 Interceptor
接口并在其中实现特定的方法时,MyBatis 在运行时会根据配置和特定的触发条件调用拦截器的方法来实现对某些操作的拦截。
例如,在 plugin
方法中,MyBatis 会判断传入的目标对象是否是需要被拦截的对象类型,如果是,则使用 Plugin.wrap
方法来对目标对象进行包装,返回一个代理对象。这个代理对象在执行特定方法时会触发拦截器中定义的逻辑。
总的来说,MyBatis 有自己独立的拦截器实现方式,并不依赖于 JDK 动态代理的 InvocationHandler
问:
MyBatis 拦截器是通过插件机制实现的。当你实现 Interceptor 接口并在其中实现特定的方法时,MyBatis 在运行时会根据配置和特定的触发条件调用拦截器的方法来实现对某些操作的拦截。细说这个特定的触发条件是什么
一、拦截的对象类型
-
MappedStatement
对象:- MyBatis 在执行 SQL 语句时,会创建一个
MappedStatement
对象来代表一个特定的 SQL 映射语句。如果拦截器的plugin
方法判断传入的对象是MappedStatement
类型或者其派生类型,就有可能触发拦截。 - 例如,拦截器可以在 SQL 执行前修改 SQL 语句、添加分页逻辑等。
- MyBatis 在执行 SQL 语句时,会创建一个
-
StatementHandler
、ParameterHandler
、ResultSetHandler
和Executor
对象:- MyBatis 在执行 SQL 的不同阶段会使用这些对象来处理 SQL 语句的执行、参数设置、结果集处理等操作。
- 如果拦截器针对这些对象进行拦截,可以在 SQL 执行的不同阶段进行干预。例如,在
StatementHandler
上拦截可以修改 SQL 的执行参数、控制 SQL 的执行方式等;在ResultSetHandler
上拦截可以对查询结果进行后处理。
问:拦截器的 plugin 方法中的拦截是怎么拦截的呢
在 MyBatis 中,拦截器的plugin
方法主要用于对目标对象进行包装,以实现拦截功能。以下是具体的拦截过程:
-
判断拦截对象类型:
- 在
plugin
方法中,首先会判断传入的目标对象是否是需要拦截的对象类型。通常会根据特定的接口或类进行判断。 - 例如,可能会判断目标对象是否是
StatementHandler
、Executor
等 MyBatis 执行 SQL 过程中的关键对象类型。
- 在
-
创建代理对象:
- 如果目标对象是需要拦截的类型,那么会使用
Plugin.wrap
方法来创建一个代理对象。这个代理对象会包装原始的目标对象。 Plugin.wrap
方法内部通常会使用动态代理技术(如 JDK 动态代理或 CGLIB)来创建代理对象。
- 如果目标对象是需要拦截的类型,那么会使用
-
代理对象的方法调用拦截:
- 当通过代理对象调用方法时,代理对象会根据方法调用的情况决定是否触发拦截逻辑。
- 具体的拦截逻辑通常在拦截器实现的其他方法中定义,比如
intercept
方法。
-
intercept
方法实现拦截逻辑:intercept
方法是拦截器实现的核心方法,它接收一个Invocation
对象,这个对象包含了被拦截的方法调用信息。- 在
intercept
方法中,可以进行以下操作:- 读取被拦截方法的参数、方法名等信息。
- 在被拦截方法执行前进行一些预处理操作,比如修改 SQL 语句、设置参数等。
- 调用
Invocation.proceed()
方法来执行被拦截的方法。 - 在被拦截方法执行后进行一些后处理操作,比如处理查询结果、记录日志等。
// 是一个mybatis拦截器中实现了Interceptor 这个接口需要重写的方法
@Override
public Object plugin(Object target) {
// todo MyBatis 拦截器通过plugin方法创建代理对象,这里是代理对象的地方 Plugin这个类 实现了InvocationHandler接口
// todo 可以看看Plugin这个类 可能会明白一点
return Plugin.wrap(target,this);
}
最后自己总结:
在 Java 中,拦截器的实现通常是在InvocationHandler
的invoke
方法中相应的位置插入逻辑。
一般来说,不是直接调用被拦截的方法,而是在invoke
方法中进行一些预处理判断后再决定是否调用目标方法,并在调用前后执行额外的逻辑。
就是实现了InvocationHandler这个接口,就可以动态代理了。Proxy
类通常使用Proxy.newProxyInstance
方法来创建代理对象,这个方法需要传入目标对象的类加载器、目标对象实现的接口数组以及一个InvocationHandler
实现对象。但是拦截器不同,是通过plugin这个wrap方法来实现的代理。Plugin实现了invocationhandler这个类。然后到把这个拦截器注册到拦截链中
最后到Configuration这个类里。