目录
1.简单动态代理
2.动态代理为什么不能对类进行代理
3.无实现类完成动态代理
4.mybatis根据接口生成代理类
5.jdk动态代理能不能对类或者抽象类代理
6.总结
7.捋一下源码过程
Mybatis 底层封装使用的 JDK 动态代理,一般来说定义的JDK 动态代理分为三个步骤
定义代理接口
定义代理接口实现类
定义动态代理调用处理器
生成代理类调用
public interface Subject { // 定义代理接口
String sayHello();
}
public class SubjectImpl implements Subject { // 定义代理接口实现类
@Override
public String sayHello() {
System.out.println(" Hello World");
return "success";
}
}
public class ProxyInvocationHandler implements InvocationHandler { // 定义动态代理调用处理器
private Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(" ???? ???? ???? 进入代理调用处理器 ");
return method.invoke(target, args);
}
}
public class ProxyTest {
public static void main(String[] args) {
Subject subject = new SubjectImpl();
Subject proxy = (Subject) Proxy
.newProxyInstance(
subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(),
new ProxyInvocationHandler(subject));
proxy.sayHello();
/**
* 打印输出如下
* 调用处理器:???? ???? ???? 进入代理调用处理器
* 被代理实现类:Hello World
*/
}
}
之前我们有分析过动态代理的源码
生成的代理类继承proxy,又实现了Subject,如果对类进行代理的话,不可能同时继承两个类
我们demo中被代理的接口是有实现类的,而Mybatis中的Mapper接口是没有实现类的,那mybatis是如何实现的呢
我们先来看下普通动态代理有没有可能不用实现类,仅靠接口完成
public interface Subject {
String sayHello();
}
public class ProxyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(" ???? ???? ???? 进入代理调用处理器 ");
return "success";
}
}
根据代码可以看到,我们并没有实现接口 Subject,继续看一下怎么实现动态代理
public class ProxyTest {
public static void main(String[] args) {
Subject proxy = (Subject) Proxy
.newProxyInstance(
Subject.getClass().getClassLoader(),
new Class[]{Subject.class},
new ProxyInvocationHandler());
proxy.sayHello();
/**
* 打印输出如下
* 调用处理器:???? ???? ???? 进入代理调用处理器
*/
}
}
原本我们是通过实现类来获得类加载器,以及接口本身数组,和把实现类当做参数传入到调用处理器中
有实现类接口和无实现类接口产生的动态代理类区别:
InvocationHandler#invoke
方法调用,然后在invoke方法里面对实现类进行反射调用InvocationHandler#invoke
产生调用,这个invoke方法里返回啥,就返回啥。InvocationHandler#invoke 方法返回值是 success 字符串,定义个字符串变量,是否能成功返回
重点就是:Mapper 没有实现类,所有调用 JDBC 等操作都是在 Mybatis InvocationHandler 实现的
demo中接口固定,mapper可不固定
看看 Mybatis 底层它怎么实现的动态接口代理,小伙伴只需要关注标记处的代码即可
和我们的 Demo 代码很像,核心点在于 mapperInterface 它是怎么赋值的
先来说一下 Mybatis 代理工厂中具体生成动态代理类具体逻辑:
Class#forName
反射的方式返回 Class 对象(不止 .xml namespace 一种方式)重点:Mybatis 使用接口全限定名通过
Class#forName
生成 Class 对象,这个 Class 对象类型就是接口
代码举例:
假设已有接口 AutoConstructorMapper 以及对应的 .xml 如下
首先第一步获取 .xml 上 mapper 标签 namespace 属性,得到 mapper 接口全限定信息
根据 mapper 全限定信息获取 Class 对象
添加到对应的映射器容器中,等待生成动态代理对象
如果此时调用生成动态代理对象,代理工厂 newInstance 方法如下:
public abstract class AbstractProxy {
abstract void sayHello();
}
AbstractProxy proxyInterface = (AbstractProxy) Proxy
.newProxyInstance(
ProxyTest.class.getClassLoader(),
new Class[]{AbstractProxy.class},
new ProxyInvocationHandler());
proxyInterface.sayHello();
毫无疑问,报错是必然的,JDK 是不能对类进行代理的
带着小疑惑我们看一下 Proxy 源码报错位置,JDK 动态代理在生成代理类的过程代码中,会有是否接口验证
底层就判断了是不是接口,是类的话就不行
Q:JDK 动态代理能否对类代理?
因为 JDK 动态代理生成的代理类,会继承 Proxy 类,由于 Java 无法多继承,所以无法对类进行代理
Q:抽象类是否可以 JDK 动态代理?
不可以,抽象类本质上也是类,Proxy 生成代理类过程中,会校验传入 Class 是否接口
Q:Mybatis Mapper 接口没有实现类,怎么实现的动态代理?
Mybatis 会通过
Class#forname
得到 Mapper 接口 Class 对象,生成对应的动态代理对象,核心业务处理都会在InvocationHandler#invoke
进行处理,应该是在invoke里面获取的sql语句,然后进行jdbc的相关操作得到最后的结果返回过来