1. 关于阅读源码
工作中使用到的框架容易成为最熟悉的陌生代码,可以熟练使用,但是并不知道其中原理。阅读源码可以帮助我们知其然,亦知其所以然,既可以在工作中更好的使用框架特性,也可以借鉴其中优秀的设计思想。
但是面对框架庞大的代码量,往往不知如何下手。总结了一些方法,可以帮助 coder 战胜恐惧,顺利的开始阅读源码。
1.1. 屏蔽干扰
简单粗暴的减法,根据包名猜测作用,排除主流程外代码
如 MyBatis 的包结构如下
annotations注解类,流程实现无关- binding 代理 *
- builder 构造 *
cache缓存相关,后续独立阅读- datasource 数据源
exceptions异常封装- executor 具体的执行 **
io文件流jdbclogging日志,后续独立阅读- mapping 映射,sql 映射,参数映射,结果映射
- parsing sql 解析
plugin插件reflection反射- scripting 脚本
- session 会话请求 *
transaction事务管理,后续独立阅读type类型转换,后续独立阅读
需要阅读的包列表如下
- binding 代理 *
- builder 构造 *
- datasource 数据源
- executor 具体的执行 **
- mapping 映射,sql 映射,参数映射,结果映射
- parsing sql 解析
- scripting 脚本
- session 会话请求 *
1.2. 找到入口
代码是逻辑的实现,找到入口,根据功能逻辑逐步阅读。入口一般为
- 启动类
- 根据具体的使用方法 debug 跟踪
1.3. 不纠结细节,不扩散思维
根据方法名明确其作用,熟悉整体流程时,不过分纠结具体实现细节(如:锁),明确作用主线,不要扩散到其他实现中
2. 基础知识点
阅读源码前先补充两个知识点
2.1. 代理
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能. 不破坏开闭原则的前提下,实现了功能的修改。AOP 也是采用了这种设计模式。
2.1.1. 静态代理
需求(issue)需要编码(coding)实现,程序员(Coder)负责编码,但是 issue 不应该直接找 coder,产品经理(PM)负责在 coding 前后进行需求梳理、需求验收。但是 PM 不应该直接修改程序员的 coding 过程,所以 PM 就是一层代理,在 coder 无感知的情况下增加了功能与访问控制。
public class StaticProxy {
public static void main(String[] args) {
Issue client = new PM();
client.coding();
}
static interface Issue {
void coding();
}
static class Coder implements Issue {
@Override
public void coding() {
System.out.println("coding");
}
}
static class PM implements Issue {
private static Issue issue = new Coder();
@Override
public void coding() {
this.beforeDoing();
issue.coding();
this.afterDoing();
}
private void beforeDoing() {
System.out.println("需求梳理");
}
private void afterDoing() {
System.out.println("需求验收");
}
}
}
2.1.2. 动态代理
上面的代码有两个 bug,
- 如果我有多个程序员,如 JavaCoder、PythonCoder,那么是不是也需要多个 PM 吗?
- PM 不会 coding。。。
为了解决这两个问题,需要用到动态代理
public class DynamicProxy {
public static void main(String[] args) {
PM pmWithPython = new PM(new PythonCoder());
PM pmWithJava = new PM(new JavaCoder());
Issue pythonIssue = (Issue) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Issue.class},
pmWithPython);
pythonIssue.coding();
Issue javaIssue = (Issue) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Issue.class},
pmWithJava);
javaIssue.coding();
}
interface Issue {
void coding();
}
static class JavaCoder implements Issue {
@Override
public void coding() {
System.out.println("coding by java");
}
}
static class PythonCoder implements Issue {
@Override
public void coding() {
System.out.println("coding by python");
}
}
static class PM implements InvocationHandler {
private Issue issue;
public PM(Issue issue) {
this.issue = issue;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeDoing();
proxy = method.invoke(issue, args);
afterDoing();
return proxy;
}
private void beforeDoing() {
System.out.println("需求梳理");
}
private void afterDoing() {
System.out.println("需求验收");
}
}
}
很多人会纠结,代理模式和装饰模式有什么区别,网上大多数的文章说装饰模式是为了增强功能,代理模式是为了控制访问,这是不能赞同的一种说法。我认为区别在于思想,装饰模式是你明确知道要把某个对象做哪些扩展,而代理则是你不和对象直接交互,而是通过第三者沟通。 动态代理,则是可以选择无关的第三者,不需要有共同的接口或父类。
2.2. JDBC
简单的实现代码
import java.sql.*;
public class JdbcApp {
private static String url = "jdbc:mysql://localhost:3306/mysql";
private static String username = "root";
private static String password = "";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from user");
if (resultSet.next()) {
String Host = resultSet.getString("Host");
String User = resultSet.getString("User");
System.out.println("Host: " + Host + ", User: " + User);
}
}
}
2.2.1 JDBC 做了哪些事
2.2.2 Class.forName 做了什么?
通过反射将驱动注册进 DriverManager 中,用于创建 connection 时使用。
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
public class DriverManager {
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
}
反射运行静态块,但是不会生成实例。
2.2.3 可以不写 Class.forName 吗?
可以,因为 DriverManager 中通过 spi 初始化了驱动。
public class DriverManager {
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
}
对 spi 做一个简单的介绍
SPI 简介 SPI 全称为(Service Provider Interface) ,是 JDK 内置的一种服务提供发现机制。
简单来说,我们定义了一个服务 interface,jar 包可以编写实现,并通过文件声明实现类,让服务发现并动态替换。
public interface SpiDemoApi {
void print();
}
public class SpiDemoImplA implements SpiDemoApi {
@Override
public void print() {
System.out.println("spi A impl");
}
}
public class SpiDemoImplB implements SpiDemoApi {
@Override
public void print() {
System.out.println("spi B impl");
}
}
在 META-INF/services 目录下以接口全路径名命名的文件,并在其中声明实现类的全路径名。声明的方法可以通过 ServiceLoader 发现
import java.util.ServiceLoader;
public class SpiApp {
public static void main(String[] args) {
ServiceLoader<SpiDemoApi> serviceLoader = ServiceLoader.load(SpiDemoApi.class);
for (SpiDemoApi service : serviceLoader) {
service.print();
}
}
}
dubbo、log、jdbc 都使用了 spi 相关技术。
spi
dubbo log jdbc
SpringFactoriesLoader
2.3. 反射
2.3.1 类
3. MyBatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。
3.1. 入口
因为 springboot 给我们带来开发便利的同时,隐藏了太多细节,已经忘记了 MyBatis 的启动方式。
3.2. driver + connection
在实际应用的过程中,每次都创建一次 connection 会极大的影响性能,所以我们会选择连接池技术,简单来说就是维护一个 connection 的 list,每次请求从 list 中取出一个 connection。Mybatis 提供了 3 中方式实现数据源初始化。
每次获取 connection 都会判断 driver 是否已注册
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
if (driverClassLoader != null) {
driverType = Class.forName(driver, true, driverClassLoader);
} else {
driverType = Resources.classForName(driver);
}
// DriverManager requires the driver to be loaded via the system ClassLoader.
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Driver driverInstance = (Driver)driverType.newInstance();
DriverManager.registerDriver(new DriverProxy(driverInstance));
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
PooledConnection 使用了动态代理,在不改变 Connection 的前提下,使关闭的 connection 加入到闲置连接池内。
class PooledConnection implements InvocationHandler {
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw
ExceptionUtil.unwrapThrowable(t);
}
}
}
...
}
3.3. statement + query
mybatis 项目中有 statement 这个包,直接看其中的类。
StatementHandler 定义了获取 Statement 的接口
public interface StatementHandler {
...
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
Statement prepare(Connection connection)
throws SQLException;
...
}
BaseStatementHandler 实现了 prepare 接口,并留了一个钩子函数 instantiateStatement
public abstract class BaseStatementHandler implements StatementHandler {
...
@Override
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
...
}
SimpleStatementHandler 继承 BaseStatementHandler 并重写 instantiateStatement
public class SimpleStatementHandler extends BaseStatementHandler {
...
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() != null) {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.createStatement();
}
}
...
}
这里有一个两个成员变量,我们先 mark 一下,稍后再去阅读。
- MappedStatement 现在知道它可以获取 resultSetType。
- BoundSql 获取 sql
SimpleStatementHandler 实现了 StatementHandler 提供的 query 接口方法,通过 statement.execute 执行。
3.4. ResultSet
MyBatis 被称为半个 ORM 系统,就是因为它对 ResultSet 的映射。我们来看一下实现,同样有一个包名叫 resultset。。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于