Mybatis技术内幕-设计模式与应用场景总结

Mybatis技术内幕-设计模式与应用场景总结

1、适配器模式

主要解决由于接口不能兼容而导致类无法使用的问题,适配器模式会将需要适配的类转换成调用者能够使用的目标接口。主要涉及以下几个角色:
目标接口Target:调用者能够直接使用的接口;
需要适配的类Adaptee:Adaptee中有真正的业务逻辑,但是不能被调用者直接使用;
适配器:Adapter实现了Target接口,并包装一个Adaptee对象,Adapter在实现Target接口中的方法时,会将调用委托给Adaptee对象的相关对象,由Adaptee中的方法完成具体的业务。
适配器模式UML
说明代码:

/**
 * 被适配者
 */
public class Adaptee {
  public void adapteeMethod(String str) {
    System.out.println(str);
  }
}

/**
 * 调用者能够直接使用的接口
 */
public interface Target {
  void printListContent(List<String> content);
}

/**
 * 由于Adaptee仅仅处理String,现在扩展为处理List<String>,调用方可以直接调用
 */
public class Adapter implements Target {
  Adaptee adaptee;
  @Override
  public void printListContent(List<String> content) {
    content.stream().forEach(str -> adaptee.adapteeMethod(str));
  }
}

对应类图
在这里插入图片描述

ATT:1、增加适配器之后,原有的调用是不会改变的,即项目之前对Adaptee的调用,是不需要修改的,这符合开放-闭合原则;

Mybatis场景
Mybatis内部调用其日志模块时,使用了其内部接口(org.apache.ibatis.logging.Log),但是Log4j、Log4j2等第三方日志组件对外提供的接口各不相同,Mybatis为了集成和复用这些第三方日志组件(这里可以理解成,集成第三方组件,提供该功能,但是其实是为了对Log进行复用),在其日志模块中提供了多种Adapter,将这些第三方日志组件对外的接口适配成了org.apache.ibatis.logging.Log接口,这样Mybatis内部就可以统一通过org.apache.ibatis.logging.Log接口调用第三方日志组件的功能了。
在这里插入图片描述

mybatis适配器源码,这里的Log既是Target类也是Adaptee类,Log是外部调用的接口,Jdk14LoggingImpl等复用了Log

package org.apache.ibatis.logging;

/**
 * @author Clinton Begin
 */
public interface Log {

  boolean isDebugEnabled();

  boolean isTraceEnabled();

  void error(String s, Throwable e);

  void error(String s);

  void debug(String s);

  void trace(String s);

  void warn(String s);

}


package org.apache.ibatis.logging.jdk14;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.ibatis.logging.Log;

/**
 * @author Clinton Begin
 */
public class Jdk14LoggingImpl implements Log {

  private final Logger log;

  public Jdk14LoggingImpl(String clazz) {
    log = Logger.getLogger(clazz);
  }

  @Override
  public boolean isDebugEnabled() {
    return log.isLoggable(Level.FINE);
  }

  @Override
  public boolean isTraceEnabled() {
    return log.isLoggable(Level.FINER);
  }

  @Override
  public void error(String s, Throwable e) {
    log.log(Level.SEVERE, s, e);
  }

  @Override
  public void error(String s) {
    log.log(Level.SEVERE, s);
  }

  @Override
  public void debug(String s) {
    log.log(Level.FINE, s);
  }

  @Override
  public void trace(String s) {
    log.log(Level.FINER, s);
  }

  @Override
  public void warn(String s) {
    log.log(Level.WARNING, s);
  }

}

2、代理模式和JDK动态代理

静态代理模式

静态代理模式中涉及到的角色:
Suject:是程序中的业务逻辑接口;
RealSubject:是实现了Subject接口的真正业务类;
Proxy:是实现了Subject接口的代理类,其中封装了RealSubject对象。在程序中不会直接调动RealSubject对象的方法,而是使用Proxy对象实现相关功能。
代理模式:Proxy.operation()方法的实现会调用RealSubject对象的operation()方法执行真正的逻辑,但是处理完业务逻辑。Proxy.operation()会在RealSubject.operation()方法调用前后进行预处理和相关的后置处理。
作用:可以控制程序对RealSubject对象的访问,或是在执行业务处理的前后进行预处理和后置处理。
应用:延迟加载;
缺点:在编译阶段就要为每个RealSuject类创建一个对应的Proxy类,当需要代理的类很多时,这就出现大量的Proxy类。
演示代码:

public interface Subject {
  void operation();
}

public class RealSubject implements Subject {
    @Override
    public void operation() {
        System.out.println("realSubject operation");
    }
}


public class Proxy implements Subject {
    RealSubject realSubject;

    @Override
    public void operation() {
        System.out.println("before.....");
        realSubject.operation();
        System.out.println("after.....");
    }
}

动态代理模式

解决静态中代理类过多的问题,通过动态创建代理类并且通过指定类加载器加载,然后在创建代理对象时将InvokerHandler.invoke()方法,并最终调用真正业务对象的相应方法。

代码演示:

/**
 * 对于需要相同代理行为的业务类,只需要提供一个InvocationHandler实现即可。
 * 在程序运行时,JDK会为每一个RealSubject类动态生成代理类并加载到JVM中,之后创建对应的代理对象
 */
public class TestInvokerHandler implements InvocationHandler {
    //真正的业务对象,也就是RealSubject对象
    private Object target;

    public TestInvokerHandler(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;
    }

    public Object getProxy() {
        /**
         * 创建代理对象
         * 1、加载动态生成的代理类的类加载器
         * 2、业务类实现的接口
         * 3、InvokerHandler对象(JDK动态代理的核心)
         */
        return java.lang.reflect.Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    public static void main(String[] args) {
        Subject subject = new RealSubject();
        TestInvokerHandler testInvokerHandler = new TestInvokerHandler(subject);
        //获取代理对象
        Subject proxy = (Subject) testInvokerHandler.getProxy();
        //调用代理对象的方法,它会调用TestInvokeHandler.invoke()方法
        //invoke中会调用目标类的真正业务执行方法
        proxy.operation();
    }
}

Mybatis场景

JDBC调试:在Mybatis的日志模块中有一个JDBC包,它并不是将日志信息通过JDBC保存到数据库中,而是通过JDK动态代理的方式,将JDBC操作通过指定的日志框架打印出来,这个功能通常在开发阶段使用,它可以输出SQL语句,用户传入的绑定参数,SQL语句影响行数等等信息,对调试程序来说是很重要的。

在这里插入图片描述
ConnectionLogger继承了BaseJdbcLogger抽象类,其中封装了Connection对象并且同时实现了InvocationHandler接口。ConnectionLogger.newInstance()方法会为其封装的Connection对象创建相应的代理对象。
invoke()方法是代理对象的核心方法,它为preparedStatement(),preparedCall(),createStatement()等等方法了代理。
源码:


package org.apache.ibatis.logging.jdbc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.reflection.ExceptionUtil;

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {

    private final Connection connection;

    private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.connection = conn;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        try {
            //如果调用的是从Object继承的方法,则直接调用,不做任何处理
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, params);
            }
            //调用指定方法的时候,创建相应的Statement对象,并为该Statement创建代理对象并返回该代理对象
            if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
                if (isDebugEnabled()) {
                    debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
                }
                //调用Connection封装的preparedStatement()方法,得到PreparedStatement对象
                PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
                //为该PreparedStatement对象创建代理对象
                stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
                return stmt;
            } else if ("createStatement".equals(method.getName())) {
                Statement stmt = (Statement) method.invoke(connection, params);
                stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
                return stmt;
            } else {
               //其他方法则直接调用底层Connection对象的相应方法
                return method.invoke(connection, params);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }


    public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
        InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
        ClassLoader cl = Connection.class.getClassLoader();
        return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
    }

    public Connection getConnection() {
        return connection;
    }

}

3、单例模式

在整个系统中,单例类只能有一个实例对象,并且向外提供获取该实例对象的方法;
mybatis中应用场景:VFS虚拟文件系统;

4、工厂方法模式

5、装饰器模式

Cache接口及其实现

6、建造者模式

BaseBuilder
XMLBaseBuilder

7、组合模式

8、模板方法模式

9、策略模式

10、责任链模式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值