静态代理、动态代理与Mybatis的理解

这里的代理与设计模式中的代理模式密切相关,代理模式的主要作用是为其他对象提供一种控制对这个对象的访问方法,即在一个对象不适合或者不能直接引用另一个对象时,代理对象充当中介的作用。

现实生活中比较贴切的例子比如租房,被代理对象就是房东,代理对象就是中介,使用者就是租客,租客通过中介向房东租赁房屋,即使用者通过代理对象访问被代理对象。

一、直接调用

  • 一般我们通过new关键字初始化对象来调用类中的方法

  • 如下代码,创建Human接口,Student类实现了Human接口,在main函数中,通过new关键字来初始化Student对象来实现对Student类中say()方法的调用

interface Human{
public void say();
} class Student implements Human{
@Override
public void say() {
System.out.println("I'm a Student");
}
} public class ProxyTest {
public static void main(String[] args) {
Human human = new Student();
human.say();
}
} //输出
//I'm a Student

二、静态代理

实现静态代理有以下三个步骤:

  • 创建接口,通过接口来实现对象的代理

  • 创建该接口的实现类

  • 创建Proxy代理类来调用我们需要的方法

interface Human{
public void say();
} class Student implements Human{ @Override
public void say() {
System.out.println("I'm a Student");
}
} class StudentProxy implements Human{
private Student student; public StudentProxy(){} public StudentProxy(Student student){
this.student = student;
} private void begin(){
System.out.println("Begin");
} private void end(){
System.out.println("End");
} @Override
public void say() {
begin();
student.say();
end();
}
} public class ProxyTest {
public static void main(String[] args) {
Student student = new Student();
StudentProxy studentProxy = new StudentProxy(student);
studentProxy.say();
}
} //输出
//Begin
//I'm a Student
//End

在上述代码中,我们在没有修改Student类中say()方法的情况下,实现了在原来的say()方法前后分别执行sayHello()和sayBye()方法。由此引出代理模式的主要作用:

  • 在不修改被代理对象的情况下,实现对被代理对象功能的增强

同时,静态代理也存在一些比较致命的缺点。想象这样一个场景:若新增一个Worker类实现了Human接口,我们应该如何去代理这个Worker类?比较容易想到的方法是扩大StudentProxy的代理范围,然后将Worker当作参数传入StudentProxy,然后继续使用StudentProxy类代理Worker对象。这样实现功能是没有问题的,但会存在如下问题:

  • 当Human接口的实例中方法增加时,代理类中代码会变得非常冗长
  • 当有其他不属于Human类的子类需要被代理时,需要新增一个新的代理类

由此引出动态代理

三、动态代理

使用动态代理时,我们不需要编写实现类,而是通过JDK提供的Proxy.newProxyInstance()创建一个Human接口的对象。

生成动态代理有以下几个步骤:

  • 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  • 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    • 使用的ClassLoader,通常是接口类的ClassLoader;
    • 需要实现的接口数组,至少需要传入一个接口进去;
    • 用来处理接口方法调用的InvocationHandler实例。
  • 将返回的Object强制转型为接口。
interface Human{
public void say();
} class Student implements Human{ @Override
public void say() {
System.out.println("I'm a Student");
} @Override
public void eat() {
System.out.println("I eat something");
}
} class MyInvocationHandler implements InvocationHandler {
private Object object; public MyInvocationHandler(){} public MyInvocationHandler(Object object){
this.object = object;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Begin");
Object invoke = method.invoke(object, args);
System.out.println("End");
return invoke;
}
} public class ProxyTest {
public static void main(String[] args) {
MyInvocationHandler handler = new MyInvocationHandler(new Student());
Human human = (Human) Proxy.newProxyInstance(
Human.class.getClassLoader(),
new Class[] {Human.class},
handler);
human.say();
human.eat();
}
}

当Human接口的实例中方法增加时,如新增eat()方法时,只需要在Student类中直接实例化该方法即可。

当有其他不属于Human类的子类需要被代理时,只需要将传入MyInvocationHandler()中的new Student()替换为需要被代理的子类即可。

综上所述,通过动态代理基本可以解决静态代理的痛点。

四、Mybatis中的动态代理

在Springboot项目中配置Mybatis时,我们仅编写了Mapper接口,并未编写Mapper接口的实现类,那么当我们调用Mapper接口中方法时,是如何生成方法体的呢?

首先,项目在启动时生成MapperFactoryBean对象,通过factory.getObject()方法获取mapper的代理对象









将上述过程与动态代理的步骤进行对比,我们最终获取的是一个类似于动态代理例子中Human的代理对象,这里是MapperProxy的代理对象。至此,一个Mapper代理对象就生成完毕。

然后,当我们完成项目中Mybatis的相关配置后,使用我们Mapper接口中的数据库相关方法时,将调用之前生成的MapperProxy代理对象中invoke()方法。类比动态代理的例子,即调用MyInvocationHandler类中的invoke()方法。

//83行代码含义:如果method为Object中定义的方法(toString()、hash()...)则直接执行,这里我们要执行的是Mapper接口中定义的方法,显然返回为false
Object.class.equals(method.getDeclaringClass())

于是执行cachedInvoker(method)的invoke()方法

进入execute()方法,我们看到之前我们配置的mapper.xml在MapperMethod初始化时,被解析成了59行的command。在此处通过sqlSession对象实现了对数据库的操作。

至此,我们对Mybatis的数据库操作流程已经有了大致了解。回到开头的问题:为什么仅编写了Mapper接口,并未编写Mapper接口的实现类,仍然可以实现我们的功能?这与我们之前的动态代理例子有什么区别呢?

研究代码我们发现,我们并没有直接使用method.invoke()方法来调用实现类中的方法,而是调用了cachedInvoker(method)的invoke()方法解析我们配置的Mapper.xml,并通过sqlSession实现了数据库操作,这个invoke()方法相当于Mybatis自定义的方法。因此,这里的invoke()方法具体执行的逻辑是根据Mapper.xml配置来生成的,这个Mapper.xml配置可以理解为Mapper接口的实现类。

静态代理、动态代理与Mybatis的理解的更多相关文章

  1. 轻松理解 Java 静态代理/动态代理

    目录 什么是代理模式 定义 代理模式的主要角色 优点 缺点 静态代理 动态代理 JDK原生动态代理 例子 分析 小结 CGLIB动态代理 例子 分析 final类型 其他方案 尾声 理解Java动态代 ...

  2. 8、Spring教程之静态代理/动态代理

    为什么要学习代理模式,因为AOP的底层机制就是动态代理! 代理模式: 静态代理 动态代理 学习aop之前 , 我们要先了解一下代理模式! 静态代理 静态代理角色分析 抽象角色 : 一般使用接口或者抽象 ...

  3. java静态和动态代理原理

    一.代理概念 为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代.代理类负责请求的预处理.过滤.将请求分派给委托类 ...

  4. 【SSH系列】静态代理&&动态代理

    从设计模式说起 代理模式是二十三中设计模式中的一种,代理模式就是指由一个代理主题来操作真实的主题,真实的主题执行具体的业务操作,而代理主题负责其她相关业务,简而言之,代理模式可以由以下三个部分组成: ...

  5. 静态代理,动态代理,Cglib代理详解

    一.静态代理 新建一个接口 定义一个玩家方法: package com."".proxy.staticc; public interface Iplayer { public vo ...

  6. Java代理模式/静态代理/动态代理

    代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...

  7. 啰里吧嗦式讲解java静态代理动态代理模式

    一.为啥写这个 文章写的比较啰嗦,有些东西可以不看,因为想看懂框架, 想了解SSH或者SSM框架的设计原理和设计思路, 又去重新看了一遍反射和注解, 然后看别人的博客说想要看懂框架得先看懂设计模式,于 ...

  8. 静态代理&动态代理

    原文地址:http://blog.csdn.net/partner4java/article/details/7048879 静态AOP和动态AOP. 静态代理: 代理对象与被代理对象必须实现同一个接 ...

  9. Java 的静态代理 动态代理(JDK和cglib)

    转载:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html JAVA的动态代理 代理模式 代理模式是常用的java设计模式,他的特征是 ...

  10. spring aop,静态及动态代理例子

    @Aspect@Componentpublic class AopText { @Pointcut("execution(public * com.llf.service.*Service. ...

随机推荐

  1. 使用 Jenkins 进行持续集成与发布流程图

    应用构建和发布流程说明: 用户向 Gitlab 提交代码,代码中必须包含 Dockerfile 将代码提交到远程仓库 用户在发布应用时需要填写 git 仓库地址和分支.服务类型.服务名称.资源数量.实 ...

  2. el-menu菜单 -- unique-opened 子菜单唯一性失效

    总结: 点击的是  el-sub-menu . 所以  el-sub-menu 的唯一性是必须的.否则 unique-opened 属性不生效

  3. 整理display:none;和visibility:hidden;和overflow:hidden;的区别

    1.display:none;  这个属性隐藏元素,不占网页任何空间,彻底隐藏,消失 2.visibility:hidden;  占据空间,但是无法点击.隐藏了这个层,看不到,却能摸得着 3.over ...

  4. Redis HyperLogLog 是什么?这些场景使用它,让我枪出如龙,一笑破苍穹

    在移动互联网的业务场景中,数据量很大,我们需要保存这样的信息:一个 key 关联了一个数据集合,同时对这个数据集合做统计. 比如: 统计一个 APP 的日活.月活数: 统计一个页面的每天被多少个不同账 ...

  5. v74.01 鸿蒙内核源码分析(编码方式篇) | 机器指令是如何编码的 | 百篇博客分析OpenHarmony源码

    本篇关键词:指令格式.条件域.类型域.操作域.数据指令.访存指令.跳转指令.SVC(软件中断) 内核汇编相关篇为: v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的 v75.03 ...

  6. 数据库基础知识详解五:MySQL中的索引和其两种引擎、主从复制以及关系型/非关系型数据库

    1.MySQL中的索引 在MySQL,索引是由B+树实现的,B+是一种与B树十分类似的数据结构. 形如下面这种: 其结构特点: (1)有n课子树的结点中含有n个关键码. (2)非根节点子节点数: ce ...

  7. k8s入门之Ingress(七)

    Ingress 的功能其实很容易理解:所谓 Ingress,就是 Service 的"Service",代理不同后端 Service 而设置的负载均衡服务. 一.安装ingress ...

  8. 《手把手教你》系列基础篇(九十五)-java+ selenium自动化测试-框架之设计篇-java实现自定义日志输出(详解教程)

    1.简介 前面宏哥一连几篇介绍如何通过开源jar包Log4j.jar.log4j2.jar和logback实现日志文件输出,Log4j和logback确实很强大,能生成三种日志文件,一种是保存到磁盘的 ...

  9. Linux中的RCU机制

    什么是RCU? RCU(Read-Copy Update),顾名思义就是读-拷贝-修改,它是基于其原理命名的.对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝 ...

  10. 二叉查找树速通攻略 图文代码精心编写(Java实现)

    说在前面 如题目所言 这篇文章为了给下一篇二叉查找数做铺垫和前期知识准备,以便大家有良好的阅读体验,本来想合在一起的,但觉得有些长,所以就拆开了哈哈哈,还是新手向,两篇文章有些长,但如果能认真看下去, ...