一、源码分析概述
目录
2.1、基础支撑层源码分析—日志模块,使用的设计模式:适配器模式、代理模式
3.1、基础支撑层源码分析 数据源模块需求。使用的设计模式:工厂模式
4.1、基础支撑层源码分析 缓存模块需。使用的设计模式:装饰器模式
mybatis架构分析
包分析 详见脑图:java学习/享学架构师课程/03.mybaits/mybatis源码结构.xmind
谈谈设计模式的原则:
- 单一职责原则:一个类或者一个接口只负责唯一项职责,尽量设计出功能单一的接口。
- 依赖倒转原则:高层模块不应该依赖底层模块具体实现,接口高层与底层。既面向接口编程,当实现发生变化时,只需提供新的实现类,不需要修改高层模块代码。
- 开放-封闭原则:程序对外扩展开放,对修改关闭,换句话说,当需求发生变化时,我们可以通过添加新模块来满足新需求,而不是通过修改原来的是现代码来满足新需求。
高级开发:面向接口编程
二、日志模块分析
2.1、基础支撑层源码分析—日志模块,使用的设计模式:适配器模式、代理模式
-
- Mybatis没有提供日志的实现类,需要接入第三方日志组件,但第三方日志组件都有各自的log级别,且各不相同,mybatis统一提供了trace、debug、warn、error四个级别(适配器模式解决)
mybatis中,对应日志组件的实现类,如log4jImpl,就是log4j的适配器。在logging包中。
-
- 自动扫描日志实现,并且第三方日志插件加载优先级如下,slf4j->commonsLogin->Log4J2->Log4j->JdkLog
- 日志的使用要优雅的嵌入到主体功能中(代理模式,动态代理解决)
2.2、适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
Target:目标角色,期待得到的接口
Adaptee:适配者角色,被适配的接口
adapter:适配器角色,将源接口转换成目标接口
适用场景:当调用双方都不太容易修改的时候,为了复用现有组件可以使用适配器模式,在系统中接入第三方组件的时候经常被用到。
注意:如果系统中存在过多的适配器,会增加系统的复杂性,设计人员应考虑对系统进行重构。
mybatis中适配器的使用
代理模式那些事:
定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
目的:
1、通过因考入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性;
2、通过代理对象对原有的业务增强;
为什么使用代理模式?
代理模式给我们带来的便利:
作为中介解耦客户端和真实对象,保护真实对象安全;
防止直接访问目标对象给系统带来不必要的复杂性;
对业务进行增强,增强点多样化;(AOP)
Connection代理对象(connectionLogger)能够返回一个prepareStatement的代理对象(prepareStatementLogger),让prepareStatement也具备日志能力打印参数;
prepareStatement的代理对象(prepareStatementLogger)能够返回一个resultSet的代理对象(ResultSetLogger),让resultSet也具备日志打印能力;
simpleExcutor类才是真正访问数据库的类
org.apache.ibatis.executor.SimpleExecutor的doQuery方法
org.apache.ibatis.executor.SimpleExecutor#prepareStatement方法生成Statement
我们可以看到,getConnection(statementLog)方法里是用connection的代理对象connectionLogger进行连接初始化的
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = this.transaction.getConnection();
return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}
三、数据源模块分析
3.1、基础支撑层源码分析 数据源模块需求。使用的设计模式:工厂模式
- 常见的数据源组件都实现了javax.sql.DataSource接口;
- mybatis不但要能集成第三方的数据源组件,自身也提供了数据源的实现;
- 一般情况下,数据源的初始化过程参数较多,比较复杂;
3.2、数据源模块类图
连接池的数据结构
PooledConnection:使用动态代理封装了真正的数据库连接对象;
PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别管理空闲状态的连接资源和活跃状态的连接资源;
PooledDataSource:一个简单、同步的、线程安全的数据库连接池;
UnpooledDataSource:不使用数据源连接池;
pooledConnection对connection的增强,增加操作前的连接有效性检查、拦截等
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("close".hashCode() == methodName.hashCode() && "close".equals(methodName)) {
this.dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
this.checkConnection();
}
return method.invoke(this.realConnection, args);
} catch (Throwable var6) {
throw ExceptionUtil.unwrapThrowable(var6);
}
}
}
private void checkConnection() throws SQLException {
if (!this.valid) {
throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
}
}
poolconnection参数意义
视频问题1:为什么在使用jdbc连接数据库的时候,使用Class.forName("com.mysql.jdbc.Driver")时,就能够使用数据库的驱动了?
因为在jdbc的驱动类com.mysql.jdbc.Driver有这么一段静态代码块,注释就是将自己注册到驱动管理器DriverManager中
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
为什么调用registerDriver方法new Driver()就能将驱动注册到DriverManager中呢?
在UnpooledDataSource类中有这么段静态代码,这里会将所有的驱动扫描出来,放到缓存中
static {
Enumeration drivers = DriverManager.getDrivers();
while(drivers.hasMoreElements()) {
Driver driver = (Driver)drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
PoolState各个参数的意义
List<PooledConnection> idleConnections 空闲连接资源
List<PooledConnection> activeConnections 活跃连接资源
正在上传…重
PooledDataSource获取和归还连接过程
获取连接 getConnection()
源码位置:org.apache.ibatis.datasource.pooled.PooledDataSource#popConnection
流程图
归还连接 pushConnection()
源码位置:org.apache.ibatis.datasource.pooled.PooledDataSource#pushConnection
3.3、工厂模式
- 简单工厂模式
优点:客户端免除了直接创建产品对象的责任,而仅仅负责调用,对象创建和对象使用的职责解耦
缺点:不符合设计原则之单一原则和开闭原则,对于需求的扩展需要修改代码
使用场景:对象比较单一,需求不复杂的场景
使用:需要一个工厂接口,一个工厂类实现工厂接口,目标类。工厂类通过简单判断创建目标类。
- 工厂模式
工厂模式属于创建型模式,它提供了一种创建对象的最佳方式。定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行
工厂接口(Factory):工厂接口是工厂方法模式的核心接口,调用者会直接和工厂接口交互用于偶去具体的产品实现类;
具体工厂类(ConcreteFactory):是工厂接口的实现类,用于实例化产品对象,不同的具体工厂会根据需求实例化不同的产品实现类;
产品接口(Product):产品接口用于定义产品类的功能,具体工厂类产生的所有产品都必须事先这个接口。调用者与产品接口直接交互,这是调用者最关心的接口;
具体产品类(ConcreteProduct):实现产品接口的实现类,具体产品中定义了具体的业务逻辑;
使用逻辑:
定义一个工厂接口
定义一个产品接口
具体工厂实现工厂接口,完成生产工作,生产出实现产品接口的具体产品类
- 抽象工厂模式
概念:
所谓抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。它允许客户端使用抽象的接口来创建一组相关的产品,而不需要关心实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。它的优点是隔离了具体类的生成,使得客户端不需要知道什么被创建了,而缺点就在于新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更改接口及其下的所有子类。
特点:
可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
当增加一个新的产品族时不需要修改原代码,满足开闭原则。
其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
抽象工厂模式的主要角色 :
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 new Product(),可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要功能和特性,抽象工厂模式有多个抽象产品。
- 具体产品(Concrete Product):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
为什么要使用工厂模式?
四、缓存模块分析
装饰器模式、缓存模块分析
4.1、基础支撑层源码分析 缓存模块需。使用的设计模式:装饰器模式
- mybatis缓存的实现是基于Map的,从缓存里面读写数据是缓存模块的核心基础功能
- 除核心功能之外,有很多额外的附加功能,如:防止缓存击穿,添加缓存清空策略(FIFO、LRU least recently use)、序列化功能、日志能力、定时清空能力等
- 附加功能可以以任意的组合附加到核心基础功能之上
缓存的读写速度远高于硬盘,优先读取缓存。
怎样优雅的为核心功能添加附加能力?
使用继承的办法扩展附加功能?
A:继承的方式是静态的,用户不能控制增加行为的方式和时机,另外,新功能存在多种组合,使用继承可能导致大量子类存在;
装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。适用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
4.2、装饰器模式:
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀;
组件(Component):组件接口定义了全部组件类和装饰器实现的行为;
组件实现类(ConcreteComponent):实现Component接口,组件实现类就是被装饰器装饰的原始对象,新功能或者附加功能都是通过装饰器添加到该类的对象上的;
装饰器抽象类(Decorator):实现Component接口的抽象类,在其中封装了一个Component对象,也就是被装饰的对象;
具体装饰器类(ConcreteDecorator):该实现类要向被装饰的对象添加某些功能;
装饰器使用图示
优点:
相对于继承,装饰器模式灵活性更强,扩展性更强;
灵活性:装饰器模式将功能切分成一个个独立的装饰器,在运行期可以根据需要动态的添加功能,甚至对添加的新功能进行自由组合;
扩展性:当有新功能要添加的时候,只需要添加新的装饰器实现类,然后通过组合方式添加这个新的装饰器,无需修改已有代码,符合开闭原则;
4.3、装饰器模式使用举例
- IO中输入流和输出流的设计
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("xxx")))
- Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper类增强了request对象的功能。
- Mybatis的缓存组件:
Cache:Cache接口是缓存模块的核心接口,定义了缓存的基本操作;
PrepetualCache:在缓存模块中扮演ConcreteComponent角色,使用HashMap来实现Cache的相关操作;
BlockingCache:阻塞版本的缓存装饰器,保证只有一个线程到数据库去查找指定的key对应的数据。
缓存为什么要用锁?
缓存是数据库的一道防火墙,mysql自己拥有一个连接池,java的连接池是基于mysql的连接池实现的,如:mysql数据库连接池只有100,java配置空闲连接池101,这时java应用总是会抛出异常
粗粒度锁数据库是绝对安全的,但是性能低,细粒度锁性能较高,但是有击穿mysql缓存的风险
细粒度锁是对象锁,底层是concurrentHashMap
当所有线程访问缓存都没有数据时,开始竞争锁,保证在某一个瞬间只会有一个线程能拿到锁,访问数据库,并且把数据加载到缓存中,然后释放锁,后面进来的请求就可能能从缓存中拿到数据,以此来保护数据库的安全,不会被突然的大并发搞崩。
缺点:性能较低,有可能不同的请求所需要的数据是不相同的,但是只有一个锁,只能一个一个来取数据。
缓存装饰器解读
CaheKey解读
如何判断key值一致
Mybatis中涉及到动态sql的原因,缓存项的key不能仅仅通过一个String来表示,所以通过CacheKey来封装缓存的key值,CacheKey可以封装多个影响缓存的因素,判断两个CacheKey是否相同关键是比较两个对象的hash值是否一致;
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
private int multiplier;//参与hash计算的乘数
private int hashcode;//cacheKey的hash值,在update函数中实时运算出来的
private long checksum;//校验和,hash值的和
private int count;//updateList中的元素个数
private List<Object> updateList;
构成Cachekey的对象
mappedStatment的id(命名空间与id)
指定查询结果集的范围(分页信息)
查询所使用的sql语句
用户传递给sql语句的实际参数值
重点解读方法
update(Object obj)
equals(Object obj)
baseExcutor如何更新缓存
org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
org.apache.ibatis.executor.BaseExecutor#createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
Iterator i$ = parameterMappings.iterator();
while(i$.hasNext()) {
ParameterMapping parameterMapping = (ParameterMapping)i$.next();
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (this.configuration.getEnvironment() != null) {
cacheKey.update(this.configuration.getEnvironment().getId());
}
return cacheKey;
}
}
真正提供缓存的是PerpetualCache这个类
重点解读baseExcutor类
五、反射模块分析
反射模块主要解决后面两步,创建对象和属性赋值
反射过程分析、反射核心类
反射的核心类:
ObjectFactory:mybatis每次创建结果对象的新实例时,它都会使用对象工厂(ObjectFactory)去构建POJO
ReflectorFactory:创建Reflector的工厂类,Reflector是mybatis反射模块的基础,每个Reflector对象都对应一个类,在其中又缓存了反射操作所需要的类原信息
主要操作类是defaultReflectFactory,生产Reflector对象,Reflector包装了反射所需要的所有信息
Reflector属性意义
objectWrapper:对对象的包装,抽象了对象的属性信息,定义了一系列查询对象属性信息的方法,以及更新属性的方法
objectWrapper给属性赋值
ObjectaWrapperFactory:ObjectWrapper的工厂类,用于创建ObjectWrapper
metaObject:封装了对象元信息,包装了mybatis中五个核心的反射类,也是听给外不适用的隐藏数据,可以利用它读取或者修改对象属性信息。