JDK * ,代理接口没有实现类,实现 * 方式
作者:DayFight_DayUp 发布时间:2021-12-21 11:55:22
JDK * ,代理接口没有实现类,实现 *
JDK代理,代理的是接口,那么笔者想一想,既然代理的是接口,那如果没有实现类怎么办,能不能代理。答案是可以的,Mybatis就是这样的。
Mybatis使用JDK * 来实现Mapper接口,事先保存好Mapper接口,和接口声明的方法,返回值,参数类型,然后代理类的方法调用的时候使用MapperMethod这个事先放入方法缓存里的对象来真实调用功能。
笔者极度简化了一下代码:
被代理的接口:
public interface Subject2 {
String selectById();
}
这个接口可以看成是Mapper接口
代理对象:
public class SubjectProxy2<T> implements InvocationHandler {
private Class<T> proxyInterface;
//这里可以维护一个缓存,存这个接口的方法抽象的对象
SubjectProxy2(Class<T> proxyInterface){
this.proxyInterface = proxyInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("selectById")){
//String result = (String) method.invoke(proxyInterface,args);
//这里可以得到方法抽象对象来调用真的的查询方法
System.out.println("selectById调用成功");
}
return null;
}
public T getProxy(){
return (T) Proxy.newProxyInstance(proxyInterface.getClassLoader(),new Class[]{proxyInterface},this);
}
}
这个代理类使用了泛型,说明这个代理类可以代理所有的mapper接口。
那么接下来测试一下:
public class ProxyTest2 {
public static void main(String[] args) {
SubjectProxy2<Subject2> subjectProxy2 = new SubjectProxy2(Subject2.class);
Subject2 subject2 = subjectProxy2.getProxy();
subject2.selectById();
}
}
结果不言而喻。肯定会有相应的输出
没有看mybatis源码的时候,我以为 * 一定要要有实现类才能代理,但是看了优秀的顶级大牛的源码之后,我才发现,原来还可以这样。
jdk * 为什么要接口
jdk的 * 为什么用接口,内部是什么原理呢?看了几篇文章貌似都没讲的清楚明白,因此来解释一下。
先通过一个简单例子实现功能:
//接口
public interface SayService {
void say(String name);
}
//实现类
public class SayServiceImpl implements SayService{
@Override
public void say(String name) {
System.out.println(name);
}
}
然后再自定义一个增强类, 实现InvocationHandler接口:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class WavingInvocationHandler implements InvocationHandler{
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行之前!");
Object obj = method.invoke(target, args);
System.out.println("方法执行之后!");
return obj;
}
}
编写测试方法:
import sun.misc.ProxyGenerator;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//接口
SayService sayService = new SayServiceImpl();
//织入类
WavingInvocationHandler handler = new WavingInvocationHandler();
handler.setTarget(sayService);
//代理类--增强的对象
SayService s = (SayService) Proxy.newProxyInstance(
sayService.getClass().getClassLoader(),
sayService.getClass().getInterfaces(), handler);
s.say("say()");//执行代理对象完成业务
/**
方法执行之前!
say()
方法执行之后!
*/
//将jdk中生成代理类输出到本地.Class文件,之后可以通过反编译软件打开查看
createProxyClassFile("test12345",sayService.getClass().getInterfaces());
}
private static void createProxyClassFile(String name,Class<?> [] interfaces){
byte[] data = ProxyGenerator.generateProxyClass(name,interfaces);//该方法为jdk中生成代理类的核心方法
FileOutputStream out =null;
try {
out = new FileOutputStream(name+".class");
System.out.println((new File(name)).getAbsolutePath());
out.write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=out) try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
好奇心强的小伙伴一定看过newProxyInstance方法看过了,
里面的getProxyClass方法创建代理类:
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
具体是里面的哪个方法生成的,小伙伴们不用找半天了,慢慢debug后会发现,就是上面提到的
ProxyGenerator.generateProxyClass()
通过该方法生成的代理类如下:
通过反编译查看源码,一看便知接口的作用
JDK的 * 是靠多态和反射来实现的,它生成的代理类需要实现你传入的接口,并通过反射来得到接口的方法对象(下文中的m3),并将此方法对象传参给增强类(上文中的WavingInvocationHandler类)的invoke方法去执行,从而实现了代理功能,故接口是jdk * 的核心实现方式,没有它就无法通过反射找到方法,所以这也是必须有接口的原因。不知道大家明白否
public final class test12345 extends Proxy implements SayService {
//1、该类实现你传入的接口并实现方法
// 2、通过构造方法传入你定义的增强类对象
// 3、通过反射该接口,得到接口里的Method对象并传参给增强类,然后执行Invoke实现功能
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public test12345(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);//2.此处的super指向Proxy中的构造方法并赋值(下文的h就是此处的增强类对象),
}
//略去无关的hashcode和equals方法
public final void say(String paramString) {
try {
this.h.invoke(this, m3, new Object[]{paramString});//3.此处的h就是InvocationHandler对象,invoke就是你增强类里的方法,传入接口的方法和参数并执行你增强类里的invoke方法,即完成了整个操作!
} catch (Error | RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
static {//1.静态代码块给属性赋值,初始化接口中的方法对象(主要是下面的m3)
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.chenrui.core.jdk.SayService").getMethod("say", new Class[]{Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
来源:https://blog.csdn.net/a907691592/article/details/95354063


猜你喜欢
- BigDecimal 类一、 概述Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运
- finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常
- springboot配置文件中属性变量引用@@这种属性应用方式是field_name=@field_value@。两个@符号是springb
- 前言最近测试反馈一个问题,某个查询全量信息的接口,有时候返回全量数据,符合预期,但是偶尔又只返回1条数据,简直就是“见鬼
- 本文实例讲述了Java链表中元素删除的实现方法。分享给大家供大家参考,具体如下:该部分与上一节是息息相关的,关于如何在链表中删除元素,我们一
- 前言当同一类型的很多对象组成一个树结构的时候,可以考虑使用组合模式,组合模式涉及三个类:Component接口:定义树的各个节点的一些操作L
- 大家是不是平常都有好多文件需要定期备份?如歌曲、视频、文档,代码文件等等,如果经常增加删除修改文件,就需要定期备份,最早之前文件都不大的时候
- 什么是 Nacos Config在分布式系统中,由于服务数量巨多,为了方便服务 配置文件统一管理,实时更新,所以需要分布式配置中心组件。Sp
- 在观察者模式中有2个要素:一个是被观察对象,另一个是观察者。但被观察对象的状态发生改变会通知观察者。举例:把订阅报纸的人看作是观察者,把报纸
- 概述什么是 Spring WebFlux, 它是一种异步的, 非阻塞的, 支持背压(Back pressure)机制的Web 开发框架. 要
- 问题描述涛涛最近要负责图书馆的管理工作,需要记录下每天读者的到访情况。每位读者有一个编号,每条记录用读者的编号来表示。给出读者的来访记录,请
- 前言Java线程池中提交任务运行,通常使用execute()方法就足够了。那如果想要实现在主线程中阻塞获取线程池任务运行的结果,该怎么办呢?
- TCPTCP协议是面向连接、保证高可靠性(数据无丢失、数据无失序、数据无错误、数据无重复到达)传输层协议。TCP通过三次握手建立连接,通讯完
- 一. 什么是Spring SecuritySpring Security是Spring家族的一个安全管理框架, 相比于另一个安全框架Shir
- Android原生控件只有横向进度条一种,而且没法变换样式,比如原生rom的样子很丑是吧,当伟大的产品设计要求更换前背景,甚至纵向,甚至圆弧
- 如下所示: @Override public String getEmployeeBysup(String employeeI
- 1.创建阻塞的服务器当 ServerSocketChannel 与 SockelChannel 采用默认的阻塞模式时,为了同时处理多个客户的
- 什么是 MyBatis 缓存使⽤缓存可以减少 Java 应⽤与数据库的交互次数,从而提升程序的运行效率。⽐如查询出 id = 1 的对象,第
- 新建项目IDEA上方工具栏点击:文件->新建->模块此时的目录结构:需要在main文件夹下补全两个文件夹,点击main,右键-&
- 前言我们在实际项目中,除了会碰到一对一的情况,还有一对多的情况,比如一个用户可以有多辆车,而一辆车只能有一个用户等等,今天我们就来一起学习下