Merge remote-tracking branch 'remotes/other_master/master'

pull/34/head
huifer 6 years ago
commit 3e45f233c4

@ -22,18 +22,18 @@
- [Spring AOP 如何生效(Spring AOP标签解析)](/docs/Spring/AOP/Spring-Aop如何生效.md)
### SpringMVC
- [温习一下servlet](/docs/Spring/SpringMVC/温习一下servlet.md)
- [IoC 容器在 Web 环境中的启动](/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md)
- [温习一下 servlet](/docs/Spring/SpringMVC/温习一下servlet.md)
- [IoC容器 在 Web环境 中的启动](/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md)
- [SpringMVC 的设计与实现](/docs/Spring/SpringMVC/SpringMVC的设计与实现.md)
- [SpringMVC 跨域解析](/docs/Spring/SpringMVC/SpringMVC-CROS.md)
### SpringJDBC
- 努力编写中...
### Spring事务
- [Spring与事务处理](/docs/Spring/SpringTransaction/Spring与事务处理.md)
- [Spring声明式事务处理](/docs/Spring/SpringTransaction/Spring声明式事务处理.md)
- [Spring事务处理的设计与实现](/docs/Spring/SpringTransaction/Spring事务处理的设计与实现.md)
- [Spring事务处理器的设计与实现](/docs/Spring/SpringTransaction/Spring事务处理器的设计与实现.md)
- [Spring 与事务处理](/docs/Spring/SpringTransaction/Spring与事务处理.md)
- [Spring 声明式事务处理](/docs/Spring/SpringTransaction/Spring声明式事务处理.md)
- [Spring 事务处理的设计与实现](/docs/Spring/SpringTransaction/Spring事务处理的设计与实现.md)
- [Spring 事务管理器的设计与实现](/docs/Spring/SpringTransaction/Spring事务管理器的设计与实现.md)
### Spring源码故事瞎编版
- [面筋哥 IoC 容器的一天(上)](/docs/Spring/Spring源码故事瞎编版/面筋哥IoC容器的一天(上).md)
@ -54,17 +54,17 @@
## MyBatis
### 基础支持层
- [反射工具箱和TypeHandler系列](docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md)
- [DataSource及Transaction模块](docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md)
- [binding模块](docs/Mybatis/基础支持层/3、binding模块.md)
- [反射工具箱和 TypeHandler 系列](docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md)
- [DataSource Transaction 模块](docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md)
- [binding 模块](docs/Mybatis/基础支持层/3、binding模块.md)
- [缓存模块](docs/Mybatis/基础支持层/4、缓存模块.md)
### 核心处理层
- [MyBatis初始化](docs/Mybatis/核心处理层/1、MyBatis初始化.md)
- [SqlNode和SqlSource](docs/Mybatis/核心处理层/2、SqlNode和SqlSource.md)
- [MyBatis 初始化](docs/Mybatis/核心处理层/1、MyBatis初始化.md)
- [SqlNode SqlSource](docs/Mybatis/核心处理层/2、SqlNode和SqlSource.md)
- [ResultSetHandler](docs/Mybatis/核心处理层/3、ResultSetHandler.md)
- [StatementHandler](docs/Mybatis/核心处理层/4、StatementHandler.md)
- [Executor组件](docs/Mybatis/核心处理层/5、Executor组件.md)
- [SqlSession组件](docs/Mybatis/核心处理层/6、SqlSession组件.md)
- [Executor 组件](docs/Mybatis/核心处理层/5、Executor组件.md)
- [SqlSession 组件](docs/Mybatis/核心处理层/6、SqlSession组件.md)
### 类解析
- [Mybatis-Cache](/docs/Mybatis/基础支持层/Mybatis-Cache.md)
- [Mybatis-log](/docs/Mybatis/基础支持层/Mybatis-log.md)
@ -83,24 +83,59 @@
## Netty
### IO
- [把被说烂的BIO、NIO、AIO再从头到尾扯一遍](docs/Netty/IO/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md)
### 设计原理
### 网络 IO 技术基础
- [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md)
- [IO模型](docs/Netty/IOTechnologyBase/IO模型.md)
- [四种IO编程及对比](docs/Netty/IOTechnologyBase/四种IO编程及对比.md)
### Netty 粘拆包解决方案
- [TCP粘拆包问题及Netty中的解决方案](docs/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案.md)
### Netty 编解码
- [Java序列化缺点与主流编解码框架](docs/Netty/Netty编解码/Java序列化缺点与主流编解码框架.md)
### Netty 多协议开发
- [基于HTTP协议的Netty开发](docs/Netty/Netty多协议开发/基于HTTP协议的Netty开发.md)
- [基于WebSocket协议的Netty开发](docs/Netty/Netty多协议开发/基于WebSocket协议的Netty开发.md)
- [基于自定义协议的Netty开发](docs/Netty/Netty多协议开发/基于自定义协议的Netty开发.md)
### 基于Netty开发服务端及客户端
- [基于Netty的服务端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的服务端开发.md)
- [基于Netty的客户端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的客户端开发.md)
### Netty 主要组件的源码分析
- [ByteBuf组件]()
- [Channel组件 和 Unsafe组件]()
- [ChannelPipeline 和 ChannelHandler组件]()
- [EventLoop 和 EventLoopGroup组件]()
- [Future 和 Promise组件]()
### Netty 高级特性
- [Netty 架构设计](docs/Netty/AdvancedFeaturesOfNetty/Netty架构设计.md)
- [Netty 高性能之道](docs/Netty/AdvancedFeaturesOfNetty/Netty高性能之道.md)
- [Netty 高可靠性设计](docs/Netty/AdvancedFeaturesOfNetty/Netty高可靠性设计.md)
## Redis
- 努力编写中...
## Tomcat
- 努力编写中...
## 学习心得
### 个人经验
- [初级开发者应该从 spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md)
- [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md)
### 编码规范
- [由量变到质变写出高质量代码](docs/LearningExperience/EncodingSpecification/由量变到质变写出高质量代码.md)
- [一个程序员的自我修养](docs/LearningExperience/EncodingSpecification/一个程序员的自我修养.md)
### 设计模式
- [从框架源码中学习设计模式](docs/LearningExperience/DesignPattern/从框架源码中学习设计模式.md)
- [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md)
- [从 Spring 及 Mybatis 框架源码中学习设计模式(行为型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md)
- [从 Spring 及 Mybatis 框架源码中学习设计模式(结构型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md)
- [从框架源码中学习设计模式的感悟](docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md)
### 多线程
- [Java多线程编程在各主流框架中的应用]()
## 贡献者
感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。

@ -0,0 +1,834 @@
设计模式是解决问题的方案从大神的代码中学习对设计模式的使用可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码Spring系列、Mybatis及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。
本篇博文主要看一下创建型的几个设计模式,即,单例模式、各种工厂模式 及 建造者模式。
## 单例模式
### 个人理解
确保某个类只有一个实例并提供该实例的获取方法。实际应用很多不管是框架、JDK还是实际的项目开发但大都会使用“饿汉式”或“枚举”来实现单例。“懒汉式”也有一些应用但通过“双检锁机制”来保证单例的实现很少见。
### 实现方式
最简单的就是 使用一个私有构造函数、一个私有静态变量以及一个公共静态方法的方式来实现。懒汉式、饿汉式等简单实现就不多BB咯这里强调一下双检锁懒汉式实现的坑以及枚举方式的实现吧最后再结合spring源码 扩展一下单例bean的实现原理。
**1. 双检锁实现的坑**
```java
/**
* @author 云之君
* 双检锁 懒汉式,实现线程安全的单例
* 关键词JVM指令重排、volatile、反射攻击
*/
public class Singleton3 {
/**
* 对于我们初级开发来说这个volatile在实际开发中可能见过但很少会用到
* 这里加个volatile进行修饰也是本单例模式的精髓所在。
* 下面的 instance = new Singleton3(); 这行代码在JVM中其实是分三步执行的
* 1、分配内存空间
* 2、初始化对象
* 3、将instance指向分配的内存地址。
* 但JVM具有指令重排的特性实际的执行顺序可能会是1、3、2导致多线程情况下出问题
* 使用volatile修饰instance变量 可以 避免上述的指令重排
* tips不太理解的是 第一个线程在执行第2步之前就已经释放了锁吗导致其它线程进入synchronized代码块
* 执行 instance == null 的判断?
*/
private volatile static Singleton3 instance;
private Singleton3(){
}
public static Singleton3 getInstance(){
if(instance == null){
synchronized(Singleton3.class){
if(instance == null){
instance = new Singleton3();
}
}
}
return instance;
}
}
```
**2. 枚举实现**
其它的单例模式实现往往都会面临序列化 和 反射攻击的问题比如上面的Singleton3如果实现了Serializable接口那么在每次序列化时都会创建一个新对象若要保证单例必须声明所有字段都是transient的并且提供一个readResolve()方法。反射攻击可以通过setAccessible()方法将私有的构造方法公共化,进而实例化。若要防止这种攻击,就需要在构造方法中添加 防止实例化第二个对象的代码。
枚举实现的单例在面对 复杂的序列化及反射攻击时依然能够保持自己的单例状态所以被认为是单例的最佳实践。比如mybatis在定义SQL命令类型时就使用到了枚举。
```java
package org.apache.ibatis.mapping;
/**
* @author Clinton Begin
*/
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
```
### JDK中的范例
**1. java.lang.Runtime**
```java
/**
* 每个Java应用程序都有一个单例的Runtime对象通过getRuntime()方法获得
* @author unascribed
* @see java.lang.Runtime#getRuntime()
* @since JDK1.0
*/
public class Runtime {
/** 很明显,这里用的是饿汉式 实现单例 */
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
```
**2. java.awt.Desktop**
```java
public class Desktop {
/**
* Suppresses default constructor for noninstantiability.
*/
private Desktop() {
peer = Toolkit.getDefaultToolkit().createDesktopPeer(this);
}
/**
* 由于对象较大,这里使用了懒汉式延迟加载,方式比较简单,直接把锁加在方法上。
* 使用双检锁方式实现的单例 还没怎么碰到过,有经验的小伙伴 欢迎留言补充
*/
public static synchronized Desktop getDesktop(){
if (GraphicsEnvironment.isHeadless()) throw new HeadlessException();
if (!Desktop.isDesktopSupported()) {
throw new UnsupportedOperationException("Desktop API is not " +
"supported on the current platform");
}
sun.awt.AppContext context = sun.awt.AppContext.getAppContext();
Desktop desktop = (Desktop)context.get(Desktop.class);
if (desktop == null) {
desktop = new Desktop();
context.put(Desktop.class, desktop);
}
return desktop;
}
}
```
### Spring的单例bean是如何实现的
Spring实现单例bean是使用map注册表和synchronized同步机制实现的通过分析spring的 AbstractBeanFactory 中的 doGetBean 方法和DefaultSingletonBeanRegistry的getSingleton()方法,可以理解其实现原理。
```java
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
......
/**
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 真正实现向IOC容器获取Bean的功能也是触发依赖注入(DI)功能的地方
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args,
boolean typeCheckOnly) throws BeansException {
......
//创建单例模式bean的实例对象
if (mbd.isSingleton()) {
//这里使用了一个匿名内部类创建Bean实例对象并且注册给所依赖的对象
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
/**
*
* 创建一个指定的Bean实例对象如果有父级继承则合并子类和父类的定义
* 走子类中的实现
*
*/
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
//获取给定Bean的实例对象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
......
}
}
/**
* 默认的单例bean注册器
*/
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/** 单例的bean实例的缓存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
/**
* 返回给定beanName的 已经注册的 单例bean如果没有注册则注册并返回
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
// 加锁保证单例bean在多线程环境下不会创建多个
synchronized (this.singletonObjects) {
// 先从缓存中取有就直接返回没有就创建、注册到singletonObjects、返回
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while the singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
beforeSingletonCreation(beanName);
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<Exception>();
}
try {
singletonObject = singletonFactory.getObject();
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
// 注册到单例bean的缓存
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
}
```
## 简单工厂模式
### 个人理解
把同一系列类的实例化交由一个工厂类进行集中管控。与其说它是一种设计模式,倒不如把它看成一种编程习惯,因为它不符合“开闭原则”,增加新的产品类需要修改工厂类的代码。
### 简单实现
```java
public interface Hero {
void speak();
}
public class DaJi implements Hero {
@Override
public void speak() {
System.out.println("妲己,陪你玩 ~");
}
}
public class LiBai implements Hero{
@Override
public void speak() {
System.out.println("今朝有酒 今朝醉 ~");
}
}
/** 对各种英雄进行集中管理 */
public class HeroFactory {
public static Hero getShibing(String name){
if("LiBai".equals(name))
return new LiBai();
else if("DaJi".equals(name))
return new DaJi();
else
return null;
}
}
```
这种设计方式只在我们产品的“FBM资金管理”模块有看到过其中对100+个按钮类进行了集中管控,不过其设计结构比上面这种要复杂的多。
## 工厂方法模式
### 个人理解
在顶级工厂(接口/抽象类)中定义 产品类的获取方法,由具体的子工厂实例化对应的产品,一般是一个子工厂对应一个特定的产品,实现对产品的集中管控,并且符合“开闭原则”。
### Mybatis中的范例
mybatis中数据源DataSource的获取使用到了该设计模式。接口DataSourceFactory定义了获取DataSource对象的方法各实现类 完成了获取对应类型的DataSource对象的实现。(mybatis的源码都是缩进两个空格难道国外的编码规范有独门派系)
```java
public interface DataSourceFactory {
// 设置DataSource的属性一般紧跟在DataSource初始化之后
void setProperties(Properties props);
// 获取DataSource对象
DataSource getDataSource();
}
public class JndiDataSourceFactory implements DataSourceFactory {
private DataSource dataSource;
@Override
public DataSource getDataSource() {
return dataSource;
}
@Override
public void setProperties(Properties properties) {
try {
InitialContext initCtx;
Properties env = getEnvProperties(properties);
if (env == null) {
initCtx = new InitialContext();
} else {
initCtx = new InitialContext(env);
}
if (properties.containsKey(INITIAL_CONTEXT)
&& properties.containsKey(DATA_SOURCE)) {
Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT));
dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE));
} else if (properties.containsKey(DATA_SOURCE)) {
dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));
}
} catch (NamingException e) {
throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
}
}
}
public class UnpooledDataSourceFactory implements DataSourceFactory {
protected DataSource dataSource;
// 在实例化该工厂时就完成了DataSource的实例化
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
public DataSource getDataSource() {
return dataSource;
}
}
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
// 与UnpooledDataSourceFactory的不同之处是其初始化的DataSource为PooledDataSource
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
```
DataSource最主要的几个实现类内容都比较多代码就不贴出来咯感兴趣的同学可以到我的源码分析专题中看到详细解析。
**tips什么时候该用简单工厂模式什么时候该用工厂方法模式呢**
个人认为,工厂方法模式符合“开闭原则”,增加新的产品类不用修改代码,应当优先考虑使用这种模式。如果产品类结构简单且数量庞大时,还是使用简单工厂模式更容易维护些,如:上百个按钮类。
## 抽象工厂模式
### 个人理解
设计结构上与“工厂方法”模式很像,最主要的区别是,工厂方法模式中 一个子工厂只对应**一个**具体的产品,而抽象工厂模式中,一个子工厂对应**一组**具有相关性的产品,即,存在多个获取不同产品的方法。这种设计模式也很少见人用,倒是“工厂方法”模式见的最多。
### 简单实现
```java
public abstract class AbstractFactory {
abstract protected AbstractProductA createProductA();
abstract protected AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory {
@Override
protected AbstractProductA createProductA() {
return new ProductA1();
}
@Override
protected AbstractProductB createProductB() {
return new ProductB1();
}
}
public class ConcreteFactory2 extends AbstractFactory {
@Override
protected AbstractProductA createProductA() {
return new ProductA2();
}
@Override
protected AbstractProductB createProductB() {
return new ProductB2();
}
}
public class Client {
public static void main(String[] args) {
AbstractFactory factory = new ConcreteFactory1();
AbstractProductA productA = factory.createProductA();
AbstractProductB productB = factory.createProductB();
...
// 结合使用productA和productB进行后续操作
...
}
}
```
### JDK中的范例
JDK的javax.xml.transform.TransformerFactory组件使用了类似“抽象工厂”模式的设计抽象类TransformerFactory定义了两个抽象方法newTransformer()和newTemplates()分别用于生成Transformer对象 和 Templates对象其两个子类进行了不同的实现源码如下版本1.8)。
```java
public abstract class TransformerFactory {
public abstract Transformer newTransformer(Source source)
throws TransformerConfigurationException;
public abstract Templates newTemplates(Source source)
throws TransformerConfigurationException;
}
/**
* SAXTransformerFactory 继承了 TransformerFactory
*/
public class TransformerFactoryImpl
extends SAXTransformerFactory implements SourceLoader, ErrorListener {
@Override
public Transformer newTransformer(Source source) throws TransformerConfigurationException {
final Templates templates = newTemplates(source);
final Transformer transformer = templates.newTransformer();
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
return(transformer);
}
@Override
public Templates newTemplates(Source source) throws TransformerConfigurationException {
......
return new TemplatesImpl(bytecodes, transletName,
xsltc.getOutputProperties(), _indentNumber, this);
}
}
public class SmartTransformerFactoryImpl extends SAXTransformerFactory {
public Transformer newTransformer(Source source) throws TransformerConfigurationException {
if (_xalanFactory == null) {
createXalanTransformerFactory();
}
if (_errorlistener != null) {
_xalanFactory.setErrorListener(_errorlistener);
}
if (_uriresolver != null) {
_xalanFactory.setURIResolver(_uriresolver);
}
_currFactory = _xalanFactory;
return _currFactory.newTransformer(source);
}
public Templates newTemplates(Source source) throws TransformerConfigurationException {
if (_xsltcFactory == null) {
createXSLTCTransformerFactory();
}
if (_errorlistener != null) {
_xsltcFactory.setErrorListener(_errorlistener);
}
if (_uriresolver != null) {
_xsltcFactory.setURIResolver(_uriresolver);
}
_currFactory = _xsltcFactory;
return _currFactory.newTemplates(source);
}
}
```
## 建造者模式
### 个人理解
该模式主要用于将复杂对象的构建过程分解成一个个简单的步骤,或者分摊到多个类中进行构建,保证构建过程层次清晰,代码不会过分臃肿,屏蔽掉了复杂对象内部的具体构建细节,其类图结构如下所示。
![avatar](/images/DesignPattern/建造者模式类图.png)
该模式的主要角色如下:
- 建造者接口Builder用于定义建造者构建产品对象的各种公共行为主要分为 建造方法 和 获取构建好的产品对象;
- 具体建造者ConcreteBuilder实现上述接口方法
- 导演Director通过调用具体建造者创建需要的产品对象
- 产品Product被建造的复杂对象。
其中的导演角色不必了解产品类的内部细节,只提供需要的信息给建造者,由具体建造者处理这些信息(这个处理过程可能会比较复杂)并完成产品构造,使产品对象的上层代码与产品对象的创建过程解耦。建造者模式将复杂产品的创建过程分散到不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,也会使创建过程更加清晰。每个具体建造者都可以创建出完整的产品对象,而且具体建造者之间是相互独立的, 因此系统就可以通过不同的具体建造者,得到不同的产品对象。当有新产品出现时,无须修改原有的代码,只需要添加新的具体建造者即可完成扩展,这符合“开放一封闭” 原则。
### 典型的范例 StringBuilder和StringBuffer
相信在拼SQL语句时大家一定经常用到StringBuffer和StringBuilder这两个类它们就用到了建造者设计模式源码如下版本1.8
```java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
// 这里完成了对复杂String的构造将str拼接到当前对象后面
str.getChars(0, len, value, count);
count += len;
return this;
}
}
/**
* @since JDK 1.5
*/
public final class StringBuilder extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
public StringBuilder() {
super(16);
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
}
/**
* @since JDK 1.0
*/
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
/**
* toString返回的最后一个值的缓存。在修改StringBuffer时清除。
*/
private transient char[] toStringCache;
public StringBuffer() {
super(16);
}
/**
* 与StringBuilder建造者最大的不同就是增加了线程安全机制
*/
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
}
```
### Mybatis中的范例
MyBatis 的初始化过程使用了建造者模式,抽象类 BaseBuilder 扮演了“建造者接口”的角色对一些公用方法进行了实现并定义了公共属性。XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 等实现类扮演了“具体建造者”的角色分别用于解析mybatis-config.xml配置文件、映射配置文件 以及 SQL节点。Configuration 和 SqlSessionFactoryBuilder 则分别扮演了“产品” 和 “导演”的角色。**即SqlSessionFactoryBuilder 使用了 BaseBuilder建造者组件 对复杂对象 Configuration 进行了构建。**
BaseBuilder组件的设计与上面标准的建造者模式是有很大不同的BaseBuilder的建造者模式主要是为了将复杂对象Configuration的构建过程分解的层次更清晰将整个构建过程分解到多个“具体构造者”类中需要这些“具体构造者”共同配合才能完成Configuration的构造单个“具体构造者”不具有单独构造产品的能力这与StringBuilder及StringBuffer是不同的。
个人理解的构建者模式 其核心就是用来构建复杂对象的,比如 mybatis 对 Configuration 对象的构建。当然,我们也可以把 对这个对象的构建过程 写在一个类中,来满足我们的需求,但这样做的话,这个类就会变得及其臃肿,难以维护。所以把整个构建过程合理地拆分到多个类中,分别构建,整个代码就显得非常规整,且思路清晰,而且 建造者模式符合 开闭原则。其源码实现如下。
```java
public abstract class BaseBuilder {
/**
* Configuration 是 MyBatis 初始化过程的核心对象并且全局唯一,
* MyBatis 中几乎全部的配置信息会保存到Configuration 对象中。
* 也有人称它是一个“All-In-One”配置对象
*/
protected final Configuration configuration;
/**
* 在 mybatis-config.xml 配置文件中可以使用<typeAliases>标签定义别名,
* 这些定义的别名都会记录在该 TypeAliasRegistry 对象中
*/
protected final TypeAliasRegistry typeAliasRegistry;
/**
* 在 mybatis-config.xml 配置文件中可以使用<typeHandlers>标签添加自定义
* TypeHandler完成指定数据库类型与 Java 类型的转换,这些 TypeHandler
* 都会记录在 TypeHandlerRegistry 中
*/
protected final TypeHandlerRegistry typeHandlerRegistry;
/**
* BaseBuilder 中记录的 TypeAliasRegistry 对象和 TypeHandlerRegistry 对象,
* 其实是全局唯一的,它们都是在 Configuration 对象初始化时创建的
*/
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
}
public class XMLConfigBuilder extends BaseBuilder {
/** 标识是否已经解析过 mybatis-config.xml 配置文件 */
private boolean parsed;
/** 用于解析 mybatis-config.xml 配置文件 */
private final XPathParser parser;
/** 标识 <environment> 配置的名称,默认读取 <environment> 标签的 default 属性 */
private String environment;
/** 负责创建和缓存 Reflector 对象 */
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在 mybatis-config.xml 配置文件中查找<configuration>节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析<properties>节点
propertiesElement(root.evalNode("properties"));
// 解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
// 解析<plugins>节点
pluginElement(root.evalNode("plugins"));
// 解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
// 解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析<reflectorFactory>节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析<environments>节点
environmentsElement(root.evalNode("environments"));
// 解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析<mappers>节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private final Map<String, XNode> sqlFragments;
private final String resource;
public void parse() {
// 判断是否已经加载过该映射文件
if (!configuration.isResourceLoaded(resource)) {
// 处理<mapper>节点
configurationElement(parser.evalNode("/mapper"));
// 将 resource 添加到 Configuration.loadedResources 集合中保存,
// 它是 HashSet<String> 类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
// 注册 Mapper 接口
bindMapperForNamespace();
}
// 处理 configurationElement() 方法中解析失败的<resultMap>节点
parsePendingResultMaps();
// 处理 configurationElement() 方法中解析失败的<cache-ref>节点
parsePendingCacheRefs();
// 处理 configurationElement() 方法中解析失败的 SQL 语句节点
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取<mapper>节点的 namespace 属性,若 namespace 属性为空,则抛出异常
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置 MapperBuilderAssistant 的 currentNamespace 字段,记录当前命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>节点
cacheElement(context.evalNode("cache"));
// 解析<parameterMap>节点,(该节点 已废弃,不再推荐使用)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析<resultMap>节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析<sql>节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析<select><insert><update><delete>等SQL节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
}
public class XMLStatementBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
private final XNode context;
private final String requiredDatabaseId;
public void parseStatementNode() {
// 获取 SQL 节点的 id 以及 databaseId 属性,若其 databaseId属性值与当前使用的数据库不匹配
// 则不加载该 SQL 节点;若存在相同 id 且 databaseId 不为空的 SQL 节点,则不再加载该 SQL 节点
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 根据 SQL 节点的名称决定其 SqlCommandType
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 在解析 SQL 语句之前,先处理其中的<include>节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 处理<selectKey>节点
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析配置文件得到 Configuration 对象,然后用其创建 DefaultSqlSessionFactory 对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
```

@ -1,6 +0,0 @@
本文用于总结阅读过的框架中,所使用过的设计模式,结合实际生成中的源码,重新理解设计模式。
努力编写中...

@ -1,13 +0,0 @@
## 六大原则
1. 单一职责:一个类只负责唯一一项职责
2. 依赖倒置:即面向接口编程,系统的高层模块(顶层接口、顶层抽象类等)不应该依赖底层模块(具体实现类),当需求发生变化时,对外接口不变,只要提供新的实现类即可。
3. 接口隔离:尽量设计出功能单一的接口,避免实现类实现很多不必要的接口方法
4. **开放-封闭:对扩展开放,对修改关闭**,本原则是设计模式的终极目标
5. 迪米特法则:尽量减少类之间的耦合性
6. 里氏替换:继承体系的设计要合理
## 装饰器模式

@ -0,0 +1,847 @@
本文用于总结《阿里Java开发手册》、《用友技术review手册》及个人Java开发工作经验并结合这半年来的源码阅读经验进行编写。回顾那些写过的和读过的代码回顾自己。
## 第一章 基础编码规范
### 1.1 命名规范
- 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
tipsJDK动态代理生成的代理类 类名使用了\$符号开头,如\$Proxy1。
- 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
tips正确的英文拼写和语法可以让阅读者易于理解避免歧义。注意即使纯拼音命名方式也要避免采用。alibabayonyouBeijing等国际通用的名称可视同英文。
在我们的财务相关模块的工程代码及数据库表设计中可以看到一些拼音首字母缩写的命名方式arap_djzbarap是“应收应付”的英文缩写djzb是“单据主表”的汉语拼音首字母zdr、shr、lrr都是汉语拼音首字母缩写。当然这些都是历史包袱经历了这么多年的代码积累很难从底层去修正咯但在自己的实际编码中要以史为鉴让自己的代码更加优雅规范这也是同事是否尊重你的重要考量之一。
- 类名使用UpperCamelCase——大驼峰风格但以下情形例外: DO / BO / DTO/ VO / AO /
PO / UID等。
tips合理的类名后缀能够让我们在开发中快速地找到自己想要的代码想看某个业务层就ctrl + shift + T搜索“XXXBO”想看某展示层代码 就搜索“XXXVO”。
- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
例如Spring框架的AbstractApplicationContext和Mybatis框架的BaseExecutor都是抽象类。
- 方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase——小驼峰风格。
- 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
tips实际编码中有时确实会嫌某常量名太长不便于使用。以后应该在语义完整清楚的情况下再考虑尽量缩短名称长度。
- 类型与中括号紧挨相连来表示数组。
正例:定义整形数组 int[] arrayDemo
反例:在 main 参数中,使用 String args[]来定义。
- POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted()RPC框架在反向解析的时候“误以为”对应的属性名称是 deleted导致属性获取不到进而抛出异常。
tips我们的VO类中有很多is开头的Boolean类型变量DJZBHeaderVO中的isjszxzf是否结算中心支付字段。
- 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils此规则参考 spring 的框架结构)
- 杜绝完全不规范的缩写,避免望文不知义。
反例AbstractClass“缩写”命名成 AbsClasscondition“缩写”命名成 condi此类随 意缩写严重降低了代码的可阅读性。
- 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词 组合来表达其意。
正例:在 JDK 中表达原子更新的类名为AtomicReferenceFieldUpdater。
反例:变量 int a 的随意命名方式。
- 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
tips将设计模式体现在名字中有利于阅读者快速理解架构设计理念。 如Spring框架的BeanFactory工厂模式、JdkDynamicAopProxyJDK动态代理模式
- 接口类中的方法和属性不要加任何修饰符号public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
正例:接口方法签名 void commit();
接口基础常量 String COMPANY = "alibaba";
反例:接口方法定义 public abstract void f();
说明JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默认实现。
- 接口和实现类的命名有两套规则:
【强制】对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部 的实现类用 Impl 的后缀与接口区别。
正例CacheServiceImpl 实现 CacheService 接口。
【推荐】 如果是形容能力的接口名称取对应的形容词为接口名通常是able 的形式)。
正例AbstractTranslator 实现 Translatable 接口。
- 枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。
正例:枚举名字为 ProcessStatusEnum 的成员名称SUCCESS / UNKNOWN_REASON。
- 各层命名规约:
A) Service/DAO 层方法命名规约
1 获取单个对象的方法用 get 做前缀。
2 获取多个对象的方法用 list 做前缀复数形式结尾如listObjects。
3 获取统计值的方法用 count 做前缀。
4 插入的方法用 save/insert 做前缀。
5 删除的方法用 remove/delete 做前缀。
6 修改的方法用 update 做前缀。
B) 领域模型命名规约
1 数据对象xxxDOxxx 即为数据表名。
2 数据传输对象xxxDTOxxx 为业务领域相关的名称。
3 展示对象xxxVOxxx 一般为网页名称。
4 POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
### 1.2 常量定义
- 不允许任何魔法值(意义不明的变量 / 常量)直接出现在代码中。
反例:
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
- 在 long 或者 Long 赋值时,数值后使用大写的 L不能是小写的 l小写容易跟数字 1 混淆,造成误解。
说明Long a = 2l; 写的是数字的 21还是 Long 型的 2?
- 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。
- 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。
1跨应用共享常量放置在二方库中通常是 client.jar 中的 constant 目录下。
2应用内共享常量放置在一方库中通常是子模块中的 constant 目录下。
反例:易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示 “是” 的变量。
类 A 中public static final String YES = "yes"
类 B 中public static final String YES = "y"
A.YES.equals(B.YES),预期是 true但实际返回为 false导致线上问题。
3子工程内部共享常量即在当前子工程的 constant 目录下。
4包内共享常量即在当前包下单独的 constant 目录下。
5类内共享常量直接在类内部 private static final 定义。
- 如果变量值仅在一个固定范围内变化用 enum 类型来定义。
说明:如果存在名称之外的延伸属性应使用 enum 类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。
正例:
```java
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int seq;
SeasonEnum(int seq){
this.seq = seq;
}
}
```
### 1.3 代码格式
代码格式无非就是一些空格、换行、缩进的问题没必要死记直接用开发工具eclipse、IDEAformat一下即可省时省力。
### 1.4 OOP 规约
- 避免通过一个类的对象引用访问此类的静态变量或方法,增加编译器解析成本。
- 所有的覆写方法,必须加@Override 注解。
说明getObject()与 get0bject()的问题。一个是字母的 O一个是数字的 0加@Override 可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名(由方法名、参数的类型及**顺序** 确定唯一的方法签名)进行修改,其实现类会马上编译报错。
- 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。
说明:可变参数必须放置在参数列表的最后。(能用数组的就不要使用可变参数编程,可变参数在编译时会被编译成数组类型。可变参数能兼容数组类参数,但是数组类参数却无法兼容可变参数。可变参数类型必须作为参数列表的最后一项,且不能放在定长参数的前面。)
正例public List<User> listUsers(String type, Long... ids) {...}
- 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
tips
一方库:本工程范围内,各个模块和包之间的相互依赖。
二方库:引入的同一个公司内部的其他工程。
三方库公司以外的其他依赖比如apachegoogle等。
- 不能使用过时的类或方法。
说明java.net.URLDecoder 中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数 decode(String source, String encode)。接口提供方既然明确是过时接口, 那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。
- Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
正例:"test".equals(object);
反例object.equals("test");
说明:推荐使用 java.util.Objects#equalsJDK7 引入的工具类)。**个人认为,当要比较两个不确定的对象时,可以考虑使用这个类,如果只是想确定某个对象是否为目标值,使用上面的方法并不差**。
- 所有的相同类型的包装类对象之间值的比较,全部使用 equals() 方法比较。
说明:对于 Integer var = ? 在-128 至 127 范围内的赋值Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑, 推荐使用 equals() 方法进行判断。
- 关于基本数据类型与包装数据类型的使用标准如下:
1【强制】所有的 POJO 类属性必须使用包装数据类型。
2【强制】RPC 方法的返回值和参数必须使用包装数据类型。
3【推荐】所有的局部变量使用基本数据类型。
说明POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
正例:数据库的查询结果可能是 null因为自动拆箱用基本数据类型接收有 NPE 风险。(不是很理解这个结论ResultSet.getInt()等方法获得的是基本数据类型ORM映射时怎么就拆箱咯)
反例:比如显示成交总额涨跌情况,即正负 x%x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
- 定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
反例POJO 类的 gmtCreate 默认值为 new Date(),但是这个属性在数据提取时并没有置入具 体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。
- 序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列化失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。
- **构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init() 方法中。**
在很多client端的代码中有看到这种编码方式。
- POJO 类必须写 toString() 方法。使用 IDE 中的工具source -> generate toString() 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString()。
说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。
- 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读。
- 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。
说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类 关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个 黑盒实现;因为承载的信息价值较低,所有 Service 和 DAO 的 getter/setter 方法放在类体 最后。
- setter 方法中参数名称与类成员变量名称一致this.成员名 = 参数名。在 getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。
- 循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
- final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:
1 不允许被继承的类String 类。
2 不允许修改引用的域对象。
3 不允许被重写的方法POJO 类的 setter 方法。
4 **不允许运行过程中重新赋值的局部变量**可以看到有些方法的形参中使用了final修饰。
5 避免上下文重复使用一个变量,使用 final 描述可以强制重新定义一个变量,方便更好 地进行重构。
- 慎用 Object 的 clone 方法来拷贝对象。
说明:对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现域对象的 深度遍历式拷贝。
- **类成员与方法访问控制从严**合理使用Java的访问修饰符
1 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2 工具类不允许有 public 或 default 构造方法。
3 类非 static 成员变量并且与子类共享,必须是 protected。
4 类非 static 成员变量并且仅在本类使用,必须是 private。
5 类 static 成员变量如果仅在本类使用,必须是 private。
6 若是 static 成员变量,考虑是否为 final。
7 类成员方法只供类内部调用,必须是 private。
8 类成员方法只对继承类公开,那么限制为 protected。 说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。
思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作 用域太大,无限制的到处跑,那么你会担心的。
### 1.5 集合处理
- 关于 hashCode 和 equals 的处理,遵循如下规则:
1 只要重写 equals就必须重写 hashCode。
2 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的 对象必须重写这两个方法。
3 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
说明String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象 作为 key 来使用。
- ArrayList的subList结果不可强转成ArrayList否则会抛出ClassCastException 异常即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
说明subList 返回的是 ArrayList 的内部类 SubList并不是 ArrayList 而是 ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
- 在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。
- 使用集合转数组的方法,必须使用集合的 toArray(T[] array)方法,传入的是类型完全一样的数组,大小就是 list.size()。
说明:使用 toArray 带参方法入参分配的数组空间不够大时toArray 方法内部将重新分配 内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ] 的数组元素将被置为 null其它数组元素保持原值因此最好将方法入参数组大小定义与集 合元素个数一致。
```java
// 正例:
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
// 反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,
// 若强转其它 类型数组将出现 ClassCastException 错误。
// 这是我平时的写法初始化一个list.size()大小的数组似乎效率更好一些,如果数组的容量
// 比list小原来的数组对象不会被使用浪费系统资源
String[] strs = list.toArray(new String[0]);
```
- 使用工具类 Arrays.asList()把数组转换成集合时不能使用其修改集合相关的方法它的add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
说明asList() 的返回对象是一个 Arrays 的内部类ArrayList而不是java.util.ArrayList该内部类 并没有实现集合的修改/删除等方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。
```java
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
第一种情况list.add("yangguanbao"); 运行时异常。
第二种情况str[0] = "gujin"; 那么 list.get(0)也会随之修改。
```
- 泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add() 方 法,而<? super T>不能使用 get() 方法,作为接口调用赋值时易出错。
```java
// <? extends T>:上界通配符(Upper Bounds Wildcards
// <? super T>:下界通配符(Lower Bounds Wildcards)
List<? extends C> list1; // list1 的元素的类型只能是 C 和 C 的子类。
List<? super C> list2; // list2 的元素的类型只能是 C 和 C 的父类。
// 简单来说就是 <? extends C> 上界为 C 类型范围粗略理解为 [C,+∞)
// 不允许添加除 null 的元素,获取的元素类型是 C
// <? super C> 下界为 C 类型范围粗略理解为 (-∞,C],允许添加 C 以及 C 的子类类型元素,
// 获取的元素类型是 Object
// 扩展说一下 PECS(Producer Extends Consumer Super)原则。
// 第一、频繁往外读取内容的,适合用<? extends T>。
// 第二、经常往里插入的,适合用<? super T>。
```
- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
```java
// 正例:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
// 反例对比ArrayList的remove()和Iterator的remove()方法,可以找到其中的坑。
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
```
- 在 JDK7 版本及以上Comparator 实现类要满足如下三个条件,不然 Arrays.sort() Collections.sort() 会报 IllegalArgumentException 异常。
说明:三个条件如下 1 xy 的比较结果和 yx 的比较结果相反。 2 x>yy>z则 x>z。 3 x=y则 xz 比较结果和 yz 比较结果相同。
```java
// 反例:下例中没有处理相等的情况,实际使用中可能会出现异常:
new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};
```
- 集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。
说明:菱形泛型,即 diamond直接使用<>来指代前边已经指定的类型。
```java
// 正例:
// <> diamond 方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);
```
- 集合初始化时,指定集合初始值大小。
说明HashMap 使用 HashMap(int initialCapacity) 初始化。
正例initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子即loader factor默认为 0.75,如果暂时无法确定初始值大小,请设置为 16即默认值
反例HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量 7 次被迫扩大resize() 需要重建 hash 表,严重影响性能。
- 使用 entrySet 遍历 Map 类集合 K-V而不是 keySet 方式进行遍历。
说明keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8使用 Map.foreach() 方法。
正例values()返回的是 V 值集合,是一个 list 集合对象keySet()返回的是 K 值集合,是 一个 Set 集合对象entrySet()返回的是 K-V 值组合集合。
```java
// 创建一个Map
Map<String, Object> infoMap = new HashMap<>();
infoMap.put("name", "Zebe");
infoMap.put("site", "www.zebe.me");
infoMap.put("email", "zebe@vip.qq.com");
// 传统的Map迭代方式
for (Map.Entry<String, Object> entry : infoMap.entrySet()) {
System.out.println(entry.getKey() + "" + entry.getValue());
}
// JDK8的迭代方式
infoMap.forEach((key, value) -> {
System.out.println(key + "" + value);
});
```
- 高度注意 Map 类集合 K-V 能不能存储 null 值的情况,如下表格:
集合类 | Key | Value | Super | 说明
-|-|-|-|-
HashMap | **允许为 null** | **允许为 null** | AbstractMap | 线程不安全
ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap| 锁分段技术JDK8:CAS
Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全
TreeMap | 不允许为 null | **允许为 null** | AbstractMap | 线程不安全
反例: 由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上, 存储 null 值时会抛出 NPE 异常。
- 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
说明有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是一定的。如ArrayList 是 order/unsortHashMap 是 unorder/unsortTreeSet 是 order/sort。
- 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作。
### 1.6 并发处理
- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明:资源驱动类、工具类、单例工厂类都需要注意。
- 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
```java
// 正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
}
```
- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
tips:我们的代码中的很多线程都是自行显式创建的,很少见到通过线程池进行统一管理的。
- 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明Executors 返回的线程池对象的弊端如下:
1FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE可能会堆积大量的请求从而导致 OOM。
2CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE可能会创建大量的线程从而导致 OOM。
- SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static必须加锁或者使用 DateUtils 工具类。
```java
// 正例:注意线程安全,使用 DateUtils。亦推荐如下处理
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
// 说明:如果是 JDK8 的应用,可以使用 Instant 代替 DateLocalDateTime 代替 Calendar
// DateTimeFormatter 代替 SimpleDateFormat
// 官方给出的解释simple beautiful strong immutable thread-safe。
```
- 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,**避免在锁代码块中调用 RPC 方法**。
- **对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序**,否则可能会造成死锁。
说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C否则可能出现死锁。
- 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。
- 多线程并行处理定时任务时Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
- 使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。
说明:注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。
- 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。
说明Random 实例包括 java.util.Random 的实例或者 Math.random()的方式。
正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom而在 JDK7 之前,需要编码保证每个线程持有一个实例。
- 在并发场景下通过双重检查锁double-checked locking实现延迟初始化的优化问题隐患(指令重排会导致 双检锁失效,产生隐患)(可参考 The "Double-Checked Locking is Broken" Declaration),推荐解决方案中较为简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型。
```java
// 反例:
class LazyInitDemo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other methods and fields...
}
```
- volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。
```java
// 如果是 count++操作,使用如下类实现:
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
// 如果是 JDK8推 荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
```
- HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在开发过程中可以使用其它数据结构或加锁来规避此风险。
- ThreadLocal 无法解决共享对象的更新问题ThreadLocal 对象建议使用 static 修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。
### 1.7 控制语句
- 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使空代码。
- 在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码。
- 在高并发场景中,避免使用“等于”判断作为中断或退出的条件。
说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。
- 表达异常的分支时,少用 if-else 方式,这种方式可以改写成:
```java
if (condition) {
...
return obj;
}
// 接着写 else 的业务逻辑代码;
// 说明:如果非得使用 if()...else if()...else...方式表达逻辑,【强制】避免后续代码维护困难,
// 请勿超过 3 层。
// 正例:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现,其中卫语句示例如下:
public void today() {
if (isBusy()) {
System.out.println(“change time.”);
return;
}
if (isFree()) {
System.out.println(“go to travel.”);
return;
}
System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”);
return;
}
```
- 除常用方法(如 getXxx/isXxx等外不要在条件判断中执行其它复杂的语句将复杂逻辑判断的结果赋值给一个有意义的布尔变量名以提高可读性。
说明:很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?
```java
// 正例:
// 伪代码如下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
// 反例在我们的代码中可以看到很多这种把复杂冗长的逻辑判断写在if语句中的
if ((file.open(fileName, "w") != null) && (...) || (...)) {
...
}
```
- **循环体中的语句要考量性能**,以下操作尽量移至循环体外处理,如:定义对象、变量、 获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。
- 避免采用取反逻辑运算符。
说明:取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。
正例:使用 if (x < 628) x 628
反例:使用 if (!(x >= 628)) 来表达 x 小于 628。
- 接口入参保护(即,参数校验),这种场景常见的是用作批量操作的接口。
- 下列情形,需要进行参数校验:
1 调用频次低的方法。
2 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
3 需要极高稳定性和可用性的方法。
4 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
5 敏感权限入口。
- 下列情形,不需要进行参数校验:
1 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。
2 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。
3 被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
### 1.8 注释规约
注释感觉是我们代码规范的重灾区咯,也是大家最容易忽略的地方。
- 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/** 内容 */格式,不得使用 // xxx 方式。 说明:在 IDE 编辑窗口中Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注 释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。
- 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。
说明:对子类的实现要求,或者调用注意事项,请一并说明。
- 所有的类都必须添加创建者和创建日期。
- 方法内部单行注释,在被注释语句上方另起一行,使用 // 注释。方法内部多行注释 使用 /* */ 注释,注意与代码对齐。
- 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
- 与其用“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。
反例“TCP 连接超时” 解释成 “传输控制协议连接超时”,理解反而费脑筋。
- 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后, 就失去了导航的意义。
- 谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。
说明:代码被注释掉有两种可能性。
1后续会恢复此段代码逻辑。
2永久不用。前者如果没有备注信息难以知晓注释动机。后者建议直接删掉代码仓库保存了历史代码
- 对于注释的要求:
第一、能够准确反应设计思想和代码逻辑;
第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。
- 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
```java
// 反例:
// put elephant into fridge
put(elephant, fridge);
// 方法名 put加上两个有意义的变量名 elephant 和 fridge已经说明了这是在干什么
// 语义清晰的代码不需要额外的注释。
```
- 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
1 待办事宜TODO: 标记人,标记时间,[预计处理时间]
表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc 还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。
2 错误不能工作FIXME: 标记人,标记时间,[预计处理时间]
在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。
### 1.9 其它
- 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
说明不要在方法体内定义Pattern pattern = Pattern.compile(“规则”);
- velocity 调用 POJO 类的属性时,建议直接使用属性名取值即可,模板引擎会自动按规范调用 POJO 的 getXxx(),如果是 boolean 基本数据类型变量boolean 命名不需要加 is 前缀会自动调用isXxx()方法。
说明:注意如果是 Boolean 包装类对象,优先调用 getXxx()的方法。
- 注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1 x 10 使 Random nextInt nextLong
- 获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();
说明:如果想获取更加精确的纳秒级时间值,使用 System.nanoTime()的方式。在 JDK8 中, 针对统计时间等场景,推荐使用 Instant 类。
- 不要在视图模板中加入任何复杂的逻辑。
说明:根据 MVC 理论,视图的职责是展示,不要抢模型和控制器的活。
- 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
- 及时清理不再使用的代码段或配置信息。
说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。
正例:对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三个斜杠(///)来说明注释掉代码的理由。
## 第二章 异常与日志规范
### 2.1 异常处理
- Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理比如NullPointerExceptionIndexOutOfBoundsException 等等。
说明:无法通过预检查的异常除外,比如,**在解析字符串形式的数字时,不得不通过 catch NumberFormatException 来实现**。
正例if (obj != null) {...}
反例try { obj.method(); } catch (NullPointerException e) {…}
- 异常不要用来做流程控制,条件控制。
说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
- catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。 对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
说明:对大段代码进行 try-catch使程序无法根据不同的异常做出正确的应激反应也不利于定位问题这是一种不负责任的表现。
正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。
- 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
- 有 try 块放到了事务代码中catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。
- finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。
- 不要在 finally 块中使用 return。
说明finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
- 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
- 方法的返回值可以为 null不强制返回空集合或者空对象等必须添加注释充分说明什么情况下会返回 null 值。
说明:本手册明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。
- 防止 NPE是程序员的基本修养注意 NPE 产生的场景:
1返回类型为基本数据类型return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例public int f() { return Integer 对象} 如果为 null自动解箱抛 NPE。
2 数据库的查询结果可能为 null。
3 集合里的元素即使 isNotEmpty取出的数据元素也可能为 null。
4 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
6 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
正例:使用 JDK8 的 Optional 类来防止 NPE 问题。
- 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException() 更不允许抛出 Exception 或者 Throwable应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常DAOException / ServiceException 等。
- 对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出; 跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简短信息”。
说明:关于 RPC 方法返回方式使用 Result 方式的理由。
1使用抛异常返回方式调用方如果没有捕获到就会产生运行时错误。
2如果不加栈信息只是 new 自定义异常,加入自己的理解的 error message对于调用端解决问题的帮助不会太多。如果加了栈信息在频繁调用出错的情况下数据序列化和传输的性能损耗也是问题。
- 避免出现重复的代码Dont Repeat Yourself即 DRY 原则。
说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。
```java
// 正例:
// 一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
private boolean checkParam(DTO dto) {...}
```
### 2.2 日志规约
- 应用中不可直接使用日志系统Log4j、Logback中的 API而应依赖使用日志框架 SLF4J 中的API使用门面模式的日志框架有利于维护和各个类的日志处理方式统一。
```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
```
- 日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
- 应用中的扩展日志如打点、临时监控、访问日志等命名方式appName_logType_logName.log。 logType:日志类型,如 stats/monitor/access 等logName:日志描述。这种命名的好处: 通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
正例mppserver 应用中单独监控时区转换异常mppserver_monitor_timeZoneConvert.log
说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
- 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 如果日志级别是 warn上述日志不会打印但是会执行字符串拼接操作如果 symbol 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
```java
// 正例:
//(条件)建议采用如下方式
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
// 正例:(占位符)
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
```
- 避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
```xml
<!-- 正例: -->
<logger name="com.taobao.dubbo.config" additivity="false">
```
- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
正例logger.error(各类参数或者对象 toString() + "_" + e.getMessage(), e);
- 谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
- 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
说明注意日志输出的级别error 级别只记录系统逻辑出错、异常或者重要的错误信息。
- 尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话 使用中文描述即可,否则容易产生歧义。国际化团队或海外部署的服务器由于字符集问题,【强制】 使用全英文来注释和描述日志错误信息。
## 第三章 数据库规范
我们 to B 的业务主要使用的是Oracle和SQL server去年参与适配了国产的华为GaussDB及达梦DM数据库。
### 3.1 建表规约
- 临时库、表名必须以tmp为前缀如果是按照日期生成的以日期为后缀
- 备份库、表必须以bak为前缀如果是按照日期生成的以日期为后缀
- 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint 1 表示是0 表示否)。
说明:任何字段如果为非负数,必须是 unsigned。
注意POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在&lt;resultMap&gt;设置从is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。 正例:表达逻辑删除的字段名 is_deleted1 表示删除0 表示未删除。
tips我们使用的是dr字段代表逻辑删除且POJO和布尔字段也未使用上述规范这也与我们的JDBC框架有关我们的JDBC框架是自己设计的与mybatis等主流框架有很大不同。
- 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:**MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。**
正例aliyun_adminrdc_configlevel3_name
反例AliyunAdminrdcConfiglevel_3_name
- 表名不使用复数名词。
说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。
- 禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
- 主键索引名为 pk_字段名唯一索引名为 uk_字段名普通索引名则为 idx_字段名。
说明pk_ 即 primary keyuk_ 即 unique keyidx_ 即 index 的简称。
- 小数类型为 decimal禁止使用 float 和 double。
说明float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到错误的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
- 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
tips公司这一点倒是做的比较规范。
- varchar 是不定长字符串,不预先分配存储空间,长度不要超过 5000如果存储长度大于此值定义字段类型为 text独立出来一张表用主键来对应避免影响其它字段索引效率。
tipsOracle的varchar最大长度为4000SQL server 8000这是之前适配数据库时踩过的坑。
- 表必备三字段id, gmt_create, gmt_modified。
说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。gmt_create、gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被动更新。
- **表的命名最好是加上“业务名称_表的作用”**。 正例alipay_task / force_project / trade_config
- 库名与应用名称尽量一致。
- 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
- 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
1不是频繁修改的字段。
2不是 varchar 超长字段,更不能是 text 字段。
正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。
- 单表行数超过 500 万行或者单表容量超过 2GB才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
- 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。
### 3.2 SQL语句
- 不要使用 count(列名)或 count(常量)来替代 count(*)count(*)是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:**count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行**。
- **count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL那么即使另一列有不同的值也返回为 0**
- 当某一列的值全是 NULL 时count(col)的返回结果为 0但 sum(col)的返回结果为 NULL因此使用 sum()时需注意 NPE 问题。
正例:可以使用如下方式来避免 sum 的 NPE 问题SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
- 使用 ISNULL()来判断是否为 NULL 值。
说明NULL 与任何值的直接比较都为 NULL。
1 NULL<>NULL 的返回结果是 NULL而不是 false。
2 NULL=NULL 的返回结果是 NULL而不是 true。
3 NULL<>1 的返回结果是 NULL而不是 true。
- 在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
- 不得使用外键与级联,一切外键概念必须在应用层解决。
说明: 以学生和成绩的关系为例学生表中的student_id是主键那么成绩表中的student_id 则为外键。如果更新学生表中的 student_id同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
- 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
- 数据订正(特别是删除、修改记录操作)时,要先 select避免出现误删除确认无误才能执行更新语句。
- in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控 制在 1000 个之内。
- 如果有国际化需要,所有的字符存储与表示,均以 utf-8 编码,注意字符统计函数 的区别。
说明:
SELECT LENGTH("轻松工作") 返回为 12
SELECT CHARACTER_LENGTH("轻松工作") 返回为 4
如果需要存储表情,那么选择 utf8mb4 来进行存储,注意它与 utf-8 编码的区别。
- TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger有可能造成事故故不建议在开发代码中使用此语句。
说明TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
### 3.3 ORM映射
我们的JDBC框架是自己研发的之前也有看过Mybatis的源码两者的设计及使用还是差别挺大的。
- 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:
1增加查询分析器解析成本。
2增减字段容易与 resultMap 配置不一致。
3无用字 段增加网络消耗,尤其是 text 类型的字段。
- POJO 类的布尔属性不能加 is而数据库字段必须加 is_要求在 resultMap 中进行字段与属性之间的映射。
说明:参见定义 POJO 类以及数据库字段定义规定,在&lt;resultMap&gt;中增加映射,是必须的。 在 MyBatis Generator 生成的代码中,需要进行对应的修改。
- 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个 POJO 类与之对应。
说明:配置映射关系,使字段与 DO 类解耦,方便维护。
- sql.xml 配置参数使用:#{} #param# 不要使用${} 此种方式容易出现 SQL 注入。
- iBATIS 自带的 queryForList(String statementName,int start,int size)不推 荐使用。
说明其实现方式是在数据库取到statementName对应的SQL语句的所有记录再通过subList 取 start,size 的子集合。
```java
// 正例:
Map<String, Object> map = new HashMap<>();
map.put("start", start);
map.put("size", size);
```
- 不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
说明resultClass=”Hashtable”会置入字段名和属性值但是值的类型不可控。
- 更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。
- 不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。
- @Transactional 事务不要滥用。事务会影响数据库的 QPS另外使用事务的地方需要考虑各方面的回滚方案包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
- &lt;isEqual&gt;中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;&lt;isNotEmpty&gt;表示不为空且不为 null 时执行;&lt;isNotNull&gt;表示不为 null 值时 执行。
### 3.4 索引规范
- 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律(只要有这个可能性 就一定会发生),必然有脏数据产生。
- 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
说明:即使双表 join 也要注意表索引、SQL 性能。
- 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。
- 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
- 如果有 order by 的场景请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
正例where a=? and b=? order by c; 索引a_b_c
反例索引中有范围查找那么索引有序性无法利用WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
- 利用覆盖索引来进行查询操作,避免回表。
说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。
正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain 的结果extra 列会出现using index。
- 利用延迟关联或者子查询优化超多分页场景。
说明MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
正例:先快速定位需要获取的 id 段,然后再关联:
SELECT a.* FROM 表1 a, ( select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
- SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
说明:
1consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2ref 指的是使用普通的索引normal index
3range 对索引进行范围检索。
反例explain 表的结果type=index索引物理文件全扫描速度非常慢这个 index 级别比较range 还低,与全表扫描是小巫见大巫。
- 建组合索引的时候,区分度最高的在最左边。
正例:如果 where a=? and b=? ,如果 a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。
说明存在非等号和等号混合时在建索引时请把等号条件的列前置。如where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。
- 防止因字段类型不同造成的隐式转换,导致索引失效。
- 创建索引时避免有如下极端误解:
1宁滥勿缺。认为一个查询就需要建一个索引。
2宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
3抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。
## 安全规约
- 隶属于用户个人的页面或者功能必须进行权限控制校验。
说明:防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信 内容、修改他人的订单。
- 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。
说明:中国大陆个人手机号码显示为:158****9119隐藏中间 4 位,防止隐私泄露。
- 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库。
- 用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
1page size 过大导致内存溢出;
2恶意 order by 导致数据库慢查询;
3任意重定向
4SQL 注入;
5反序列化注入
6正则输入源串拒绝服务 ReDoS
说明Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
- 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
- 表单、AJAX 提交必须执行 CSRF 安全验证。
说明CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL只要受害者用户一访问后台便在用户不知情的情况下对数据库中用户参数进行相应修改。
- 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。
说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
- 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。

@ -1,51 +1,51 @@
在Mybatis的基础支持层主要看一下支撑ORM实现的底层代码。
Mybatis 的基础支持层主要看一下支撑 ORM实现 的底层代码。
## 1 反射工具包
### 1.1Reflector
Reflector类主要实现了对JavaBean的元数据属性的封装比如可读属性列表可写属性列表及反射操作的封装属性对应的setter方法getter方法的反射调用。源码实现如下
Reflector类 主要实现了对 JavaBean 的元数据属性的封装,比如:可读属性列表,可写属性列表;及反射操作的封装,如:属性对应的 setter方法getter方法 的反射调用。源码实现如下:
```java
public class Reflector {
/** JavaBean的Class类型在调用Reflector的构造方法时初始化该值 */
/** JavaBean Class类型在调用 Reflector 的构造方法时初始化该值 */
private final Class<?> type;
/** 可读的属性列表 */
private final String[] readablePropertyNames;
private final String[] writablePropertyNames;
/** key属性名value该属性名对应的setter方法调用器 */
/** key 属性名value 该属性名对应的 setter方法调用器 */
private final Map<String, Invoker> setMethods = new HashMap<>();
private final Map<String, Invoker> getMethods = new HashMap<>();
/** key属性名称value该属性setter方法的返回值类型 */
/** key 属性名称value 该属性 setter方法的返回值类型 */
private final Map<String, Class<?>> setTypes = new HashMap<>();
private final Map<String, Class<?>> getTypes = new HashMap<>();
/** type的默认构造方法 */
/** type 的默认构造方法 */
private Constructor<?> defaultConstructor;
/** 所有属性名称的集合 */
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
/**
* 里面的大部分方法都是通过简单的JDK反射操作实现的
* 里面的大部分方法都是通过简单的 JDK反射操作 实现的
* @param clazz
*/
public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
// 处理clazz中的所有getter方法填充getMethods集合和getTypes集合
// 处理 clazz 中的 所有getter方法填充 getMethods集合 getTypes集合
addGetMethods(clazz);
addSetMethods(clazz);
// 处理没有getter、setter方法的字段
// 处理没有 getter、setter方法 的字段
addFields(clazz);
// 根据getMethods、setMethods集合初始化可读、可写的属性
// 根据 getMethods、setMethods集合 初始化可读、可写的属性
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
// 初始化caseInsensitivePropertyMap集合key属性名的大写value属性名
// 初始化 caseInsensitivePropertyMap集合key 属性名的大写value 属性名
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
@ -56,7 +56,7 @@ public class Reflector {
}
```
### 1.2 ReflectorFactory
顾名思义Reflector的工厂模式跟大部分工厂类一样里面肯定有通过标识获取对象的方法。类的设计也遵照了 接口,实现类的模式,虽然本接口只有一个默认实现。
顾名思义Reflector 的工厂模式,跟大部分工厂类一样,里面肯定有通过标识获取对象的方法。类的设计也遵照了 接口,实现类的模式,虽然本接口只有一个默认实现。
```java
public interface ReflectorFactory {
@ -65,7 +65,7 @@ public interface ReflectorFactory {
void setClassCacheEnabled(boolean classCacheEnabled);
/**
* 主要看一下这个方法通过JavaBean的clazz获取该JavaBean对应的Reflector
* 主要看一下这个方法,通过 JavaBean clazz 获取该 JavaBean 对应的 Reflector
*/
Reflector findForClass(Class<?> type);
}
@ -77,7 +77,7 @@ public class DefaultReflectorFactory implements ReflectorFactory {
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
/**
* 实例化一个ConcurrentMap全局变量然后暴露一个方法从map中获取目标对象这种设计是很多框架都会用的
* 实例化一个 ConcurrentMap全局变量然后暴露一个方法从 map 中获取目标对象,这种设计是很多框架都会用的
*/
@Override
public Reflector findForClass(Class<?> type) {
@ -104,14 +104,14 @@ public class DefaultReflectorFactory implements ReflectorFactory {
}
/**
* 支持定制化ReflectorFactory
* 支持定制化 ReflectorFactory
*/
public class CustomReflectorFactory extends DefaultReflectorFactory {
}
```
### 1.3 ObjectFactory
该类也是接口+一个默认实现类并且支持自定义扩展mybatis中有很多这样的设计方式。
该类也是接口加一个默认实现类并且支持自定义扩展Mybatis 中有很多这样的设计方式。
```java
/**
* MyBatis uses an ObjectFactory to create all needed new Objects.
@ -144,8 +144,8 @@ public interface ObjectFactory {
}
/**
* ObjectFactory接口的唯一直接实现反射工厂根据传入的参数列表选择
* 合适的构造函数实例化对象,不传参数,则直接调用其午餐构造方法
* ObjectFactory接口 的唯一直接实现,反射工厂,根据传入的参数列表,选择
* 合适的构造函数实例化对象,不传参数,则直接调用其无参构造方法
*/
public class DefaultObjectFactory implements ObjectFactory, Serializable {
@ -165,7 +165,7 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable {
}
/**
* 通过反射来实例化给定的类如果调用无参构造方法则直接constructor.newInstance()
* 通过反射来实例化给定的类,如果调用无参构造方法,则直接 constructor.newInstance()
* 如果有参,则根据参数类型和参数值进行调用
*/
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
@ -206,12 +206,12 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable {
}
```
## 2 类型转换
类型转换是实现ORM的重要一环由于 数据库中的数据类型与Java语言的数据类型并不对等所以在PrepareStatement为sql语句绑定参数时需要从Java类型转换成JDBC类型而从结果集获取数据时又要将JDBC类型转换成Java类型mybatis使用TypeHandler完成了上述的双向转换。
类型转换是实现 ORM 的重要一环,由于数据库中的数据类型与 Java语言 的数据类型并不对等,所以在 PrepareStatement sql语句 绑定参数时,需要从 Java类型 转换成 JDBC类型而从结果集获取数据时又要将 JDBC类型 转换成 Java类型Mybatis 使用 TypeHandler 完成了上述的双向转换。
### 2.1 JdbcType
mybatis通过JdbcType这个枚举类型代表了JDBC中的数据类型
Mybatis 通过 JdbcType 这个枚举类型代表了 JDBC 中的数据类型。
```java
/**
* 该枚举类描述了JDBC中的数据类型
* 该枚举类描述了 JDBC 中的数据类型
*/
public enum JdbcType {
/*
@ -262,7 +262,7 @@ public enum JdbcType {
public final int TYPE_CODE;
/** 该静态集合维护了 常量编码 与 JdbcType之间的关系 */
/** 该静态集合维护了 常量编码 与 JdbcType 之间的关系 */
private static Map<Integer,JdbcType> codeLookup = new HashMap<>();
static {
@ -282,14 +282,14 @@ public enum JdbcType {
}
```
### 2.2 TypeHandler
TypeHandler是mybatis中所有类型转换器的顶层接口,主要用于实现 数据从Java类型到JdbcType类型的相互转换。
TypeHandler 是 Mybatis 中所有类型转换器的顶层接口,主要用于实现数据从 Java类型 JdbcType类型 的相互转换。
```java
public interface TypeHandler<T> {
/** 通过PreparedStatement为SQL语句绑定参数时将数据从Java类型转换为JDBC类型 */
/** 通过 PreparedStatement SQL语句 绑定参数时,将数据从 Java类型 转换为 JDBC类型 */
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/** 从结果集获取数据时将数据由JDBC类型转换成Java类型 */
/** 从结果集获取数据时,将数据由 JDBC类型 转换成 Java类型 */
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
@ -299,7 +299,7 @@ public interface TypeHandler<T> {
}
/**
* 可用于实现自定义的TypeHandler
* 可用于实现自定义的 TypeHandler
*/
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
@ -344,13 +344,13 @@ public abstract class BaseTypeHandler<T> extends TypeReference<T> implements Typ
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
/**
1. NonNull就是NoneNull非空的意思
* NonNull 就是 NoneNull非空的意思
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
// IntegerTypeHandler就调用PreparedStatement的setInt方法
// BooleanTypeHandler就调用PreparedStatement的setBoolean方法
// IntegerTypeHandler 就调用 PreparedStatement setInt()方法
// BooleanTypeHandler 就调用 PreparedStatement setBoolean()方法
// 其它的基本数据类型,以此类推
ps.setInt(i, parameter);
}
@ -377,29 +377,29 @@ public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
}
}
```
TypeHandler主要用于单个参数的类型转换如果要将多个列的值转换成一个Java对象可以在映射文件中定义合适的映射规则&lt;resultMap&gt; 完成映射。
TypeHandler 主要用于单个参数的类型转换,如果要将多个列的值转换成一个 Java对象可以在映射文件中定义合适的映射规则 &lt;resultMap&gt; 完成映射。
### 2.3 TypeHandlerRegistry
TypeHandlerRegistry主要负责管理所有已知的TypeHandlermybatis在初始化过程中会为所有已知的TypeHandler创建对象并注册到TypeHandlerRegistry。
TypeHandlerRegistry 主要负责管理所有已知的 TypeHandlerMybatis 在初始化过程中会为所有已知的 TypeHandler 创建对象,并注册到 TypeHandlerRegistry。
```java
//TypeHandlerRegistry中的核心字段
// TypeHandlerRegistry 中的核心字段如下
/** 该集合主要用于从结果集读取数据时将数据从JDBC类型转换成Java类型 */
/** 该集合主要用于从结果集读取数据时,将数据从 JDBC类型 转换成 Java类型 */
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
/**
* 记录了Java类型向指定JdbcType转换时需要使用的TypeHandler对象。
* 如String可能转换成数据库的char、varchar等多种类型所以存在一对多的关系
* 记录了 Java类型 向指定 JdbcType 转换时,需要使用的 TypeHandler对象。
* 如String 可能转换成数据库的 char、varchar 等多种类型,所以存在一对多的关系
*/
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
/** key TypeHandler的类型value 该TypeHandler类型对应的TypeHandler对象 */
/** keyTypeHandler 的类型value该 TypeHandler类型 对应的 TypeHandler对象 */
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
```
1. **注册TypeHandler对象**
TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功能该方法存在多种重载但大多数register()方法最终都会走register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)的处理逻辑该重载方法中分别指定了TypeHandler能够处理的Java类型、JDBC类型、TypeHandler对象。
**1、注册TypeHandler对象**
TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象 的功能,该方法存在多种重载,但大多数 register()方法 最终都会走 register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) 的处理逻辑,该重载方法中分别指定了 TypeHandler 能够处理的 Java类型、JDBC类型、TypeHandler对象。
```java
/**
* TypeHandlerRegistry中对register方法实现了多种重载本register方法
* TypeHandlerRegistry 中对 register()方法 实现了多种重载,本 register()方法
* 被很多重载方法调用,用来完成注册功能。
*/
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
@ -414,14 +414,14 @@ TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功
allTypeHandlersMap.put(handler.getClass(), handler);
}
```
另外TypeHandlerRegistry还提供了扫描并注册指定包目录下TypeHandler实现类 的register()方法重载。
另外TypeHandlerRegistry 还提供了扫描并注册指定包目录下 TypeHandler实现类 的 register()方法 重载。
```java
/**
* 从指定包名packageName中获取自定义的TypeHandler实现类
* 从指定 包名packageName 中获取自定义的 TypeHandler实现类
*/
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 查找指定包下的TypeHandler接口实现类
// 查找指定包下的 TypeHandler接口实现类
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
for (Class<?> type : handlerSet) {
@ -432,14 +432,14 @@ TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功
}
}
```
最后看一下TypeHandlerRegistry的构造方法其通过多种register()方法重载完成了所有已知的TypeHandler的重载。
最后看一下 TypeHandlerRegistry 的构造方法,其通过多种 register()方法 重载,完成了所有已知的 TypeHandler 的重载。
```java
/**
* 进行Java及JDBC基本数据类型的TypeHandler注册
* 除了注册mybatis提供的基本TypeHandler外我们也可以添加自定义的TypeHandler
* 接口实现在mybatis-config.xml配置文件中<typeHandlers>节点 添加相应的
* <typeHandlers>节点配置,并指定自定义的TypeHandler实现类。mybatis在初始化时
* 会解析该节点,并将TypeHandler类型的对象注册到TypeHandlerRegistry中 供mybatis后续使用
* 进行 Java JDBC基本数据类型 TypeHandler 注册
* 除了注册 Mybatis 提供的 基本TypeHandler 外,我们也可以添加自定义的 TypeHandler
* 接口实现,在 mybatis-config.xml配置文件 <typeHandlers>节点 添加相应的
* <typeHandlers>节点配置,并指定自定义的 TypeHandler实现类。Mybatis 在初始化时
* 会解析该节点,并将 TypeHandler类型 的对象注册到 TypeHandlerRegistry 中供 Mybatis 后续使用
*/
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
@ -515,20 +515,20 @@ TypeHandlerRegistry中的register()方法实现了注册TypeHandler对象的功
register(JapaneseDate.class, new JapaneseDateTypeHandler());
}
```
2. **查找TypeHandler**
TypeHandlerRegistry其实就是一个容器前面注册了一堆东西也就是为了方便获取其对应的方法为getTypeHandler(),也存在多种重载其中最重要的一个重载为getTypeHandler(Type type, JdbcType jdbcType)它会根据指定的Java类型和JdbcType类型查找相应的TypeHandler对象。
**2、查找TypeHandler**
TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也就是为了方便获取,其对应的方法为 getTypeHandler()该方法也存在多种重载,其中最重要的一个重载为 getTypeHandler(Type type, JdbcType jdbcType),它会根据指定的 Java类型 JdbcType类型 查找相应的 TypeHandler对象。
```java
/**
* 获取TypeHandler对象
* getTypeHandler方法亦存在多种重载而本重载方法被其它多个重载方法调用
* 获取 TypeHandler对象
* getTypeHandler()方法 亦存在多种重载,而本重载方法被其它多个重载方法调用
*/
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
// Java数据类型与JDBC数据类型的关系往往是一对多
// 所以一般会先根据Java数据类型获取Map<JdbcType, TypeHandler<?>>
// 再根据JDBC数据类型获取对应的TypeHandler
// Java数据类型 JDBC数据类型 的关系往往是一对多,
// 所以一般会先根据 Java数据类型 获取 Map<JdbcType, TypeHandler<?>>对象
// 再根据 JDBC数据类型 获取对应的 TypeHandler对象
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
@ -545,4 +545,4 @@ TypeHandlerRegistry其实就是一个容器前面注册了一堆东西
return (TypeHandler<T>) handler;
}
```
除了mabatis本身自带的TypeHandler实现我们还可以添加自定义的TypeHandler实现类在配置文件mybatis-config.xml中的&lt;typeHandler&gt;标签下配置好自定义TypeHandlermybatis就会在初始化时解析该标签内容完成自定义TypeHandler的注册。
除了 Mabatis 本身自带的 TypeHandler实现我们还可以添加自定义的 TypeHandler实现类在配置文件 mybatis-config.xml 中的 &lt;typeHandler&gt; 标签下配置好 自定义TypeHandlerMybatis 就会在初始化时解析该标签内容,完成 自定义TypeHandler 的注册。

@ -1,15 +1,15 @@
在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用mybatis集成第三方数据源组件c3p0、Druid另外mybatis也提供了自己的数据库连接池实现本文会通过mybatis的源码实现来了解数据库连接池的设计。而事务方面一般使用spring进行事务的管理这里不做详细分析。下面我们看一下mybatis是如何对这两部分进行封装的。
在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用 Mybatis 集成第三方数据源组件c3p0、Druid另外Mybatis 也提供了自己的数据库连接池实现,本文会通过 Mybatis 的源码实现来了解数据库连接池的设计。而事务方面,一般使用 Spring 进行事务的管理,这里不做详细分析。下面我们看一下 Mybatis 是如何对这两部分进行封装的。
## 1 DataSource
常见的数据源都会实现javax.sql.DataSource接口mybatis中提供了两个该接口的实现类分别是PooledDataSource和UnpooledDataSource并使用不同的工厂类分别管理这两个类的对象。
常见的数据源都会实现 javax.sql.DataSource接口Mybatis 中提供了两个该接口的实现类分别是PooledDataSource UnpooledDataSource并使用不同的工厂类分别管理这两个类的对象。
### 1.1 DataSourceFactory
DataSourceFactory系列类的设计比较简单DataSourceFactory作为顶级接口UnpooledDataSourceFactory实现了该接口PooledDataSourceFactory又继承了UnpooledDataSourceFactory。
DataSourceFactory系列类 的设计比较简单DataSourceFactory 作为顶级接口UnpooledDataSourceFactory 实现了该接口PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。
```java
public interface DataSourceFactory {
// 设置DataSource的属性一般紧跟在DataSource初始化之后
// 设置 DataSource 的属性,一般紧跟在 DataSource 初始化之后
void setProperties(Properties props);
// 获取DataSource对象
// 获取 DataSource对象
DataSource getDataSource();
}
@ -21,7 +21,7 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
protected DataSource dataSource;
// 在实例化该工厂时就完成了DataSource的实例化
// 在实例化该工厂时,就完成了 DataSource 的实例化
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@ -29,13 +29,13 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
// 创建dataSource对应的MetaObject
// 创建 dataSource 对应的 MetaObject
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
// 处理properties中配置的数据源信息
// 处理 properties 中配置的数据源信息
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
// 以"driver."开头的配置项是对DataSource的配置将其记录到driverProperties中
// 以 "driver." 开头的配置项是对 DataSource 的配置,将其记录到 driverProperties
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
@ -47,8 +47,8 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
}
}
if (driverProperties.size() > 0) {
// 设置数据源UnpooledDataSource的driverProperties属性
// PooledDataSource中持有UnpooledDataSource对象
// 设置数据源 UnpooledDataSource driverProperties属性
// PooledDataSource 中持有 UnpooledDataSource对象
metaDataSource.setValue("driverProperties", driverProperties);
}
}
@ -62,7 +62,7 @@ public class UnpooledDataSourceFactory implements DataSourceFactory {
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
// 与UnpooledDataSourceFactory的不同之处是其初始化的DataSource为PooledDataSource
// 与 UnpooledDataSourceFactory 的不同之处是,其初始化的 DataSource PooledDataSource
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
@ -70,17 +70,17 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
```
### 1.2 UnpooledDataSource
本实现类实现了DataSource接口中的getConnection()及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:
本实现类实现了 DataSource接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:
```java
public class UnpooledDataSource implements DataSource {
// 加载Driver驱动类 类加载器
// 加载 Driver驱动类 类加载器
private ClassLoader driverClassLoader;
// 数据库连接驱动的相关配置通过UnpooledDataSourceFactory的setProperties()方法设置进来的
// 数据库连接驱动的相关配置,通过 UnpooledDataSourceFactory setProperties()方法 设置进来的
private Properties driverProperties;
// 缓存所有已注册的数据库连接驱动Driver
// 缓存所有已注册的 数据库连接驱动Driver
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
// 数据库连接驱动名称
@ -100,8 +100,8 @@ public class UnpooledDataSource implements DataSource {
private Integer defaultNetworkTimeout;
/**
* UnpooledDataSource被加载时会通过该静态代码块将已经在DriverManager
* 中注册JDBC Driver复制一份到registeredDrivers
* UnpooledDataSource 被加载时,会通过该静态代码块将已经在 DriverManager
* 中注册的 JDBC Driver 注册到 registeredDrivers 中
*/
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
@ -111,11 +111,11 @@ public class UnpooledDataSource implements DataSource {
}
}
// getConnection()及其重载方法、doGetConnection(String username, String password)方法
// getConnection() 及其重载方法、doGetConnection(String username, String password)方法
// 最终都会调用本方法
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化数据库驱动该方法会创建配置中指定的Driver对象
// 并将其注册到DriverManager和registeredDrivers中
// 初始化数据库驱动,该方法会创建配置中指定的 Driver对象
// 并将其注册到 DriverManager registeredDrivers
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
// 配置数据库连接属性,如:连接超时时间、是否自动提交事务、事务隔离级别
@ -134,12 +134,12 @@ public class UnpooledDataSource implements DataSource {
} else {
driverType = Resources.classForName(driver);
}
// 通过反射 获取Driver实例对象
// 通过反射获取 Driver实例对象
Driver driverInstance = (Driver)driverType.newInstance();
// 注册驱动到DriverManagerDriverProxy是UnpooledDataSource的内部类
// 也是Driver的静态代理类
// 注册驱动到 DriverManagerDriverProxy UnpooledDataSource 的内部类
// 也是 Driver 的静态代理类
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 将driver缓存到registeredDrivers
// 将 driver 缓存到 registeredDrivers
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
@ -171,14 +171,14 @@ public class UnpooledDataSource implements DataSource {
3. 连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会进入阻塞队列等待,直到有连接可用;
4. 如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。
PooledDataSource实现了简易的数据库连接池功能其创建数据库连接的功能依赖了上面的UnpooledDataSource。
PooledDataSource 实现了简易的数据库连接池功能,其创建数据库连接的功能依赖了上面的 UnpooledDataSource。
#### 1.3.1 PooledConnection
PooledDataSource通过管理PooledConnection来实现对java.sql.Connection的管理。PooledConnection封装了java.sql.Connection数据库连接对象及其代理对象JDK动态代理生成的。PooledConnection继承了JDK动态代理的InvocationHandler接口。
PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection数据库连接对象 及其代理对象JDK动态代理生成的。PooledConnection 继承了 JDK动态代理 InvocationHandler接口。
```java
class PooledConnection implements InvocationHandler {
// 记录当前PooledConnection对象所属的PooledDataSource对象
// 当调用close()方法时会将PooledConnection放回该PooledDataSource
// 记录当前 PooledConnection对象 所属的 PooledDataSource对象
// 当调用 close()方法 时会将 PooledConnection 放回该 PooledDataSource
private final PooledDataSource dataSource;
// 真正的数据库连接对象
private final Connection realConnection;
@ -190,30 +190,30 @@ class PooledConnection implements InvocationHandler {
private long createdTimestamp;
// 最后一次使用的 时间戳
private long lastUsedTimestamp;
// 由 数据库URL、用户名、密码 计算出来的hash值可用于标识该连接所在的连接池
// 由 数据库URL、用户名、密码 计算出来的 hash值可用于标识该连接所在的连接池
private int connectionTypeCode;
// 检测当前PooledConnection连接池连接对象是否有效主要用于 防止程序通过close()方法将
// 检测当前 PooledConnection连接池连接对象 是否有效,主要用于 防止程序通过 close()方法
// 连接还给连接池之后,依然通过该连接操作数据库
private boolean valid;
/**
* invoke()方法是本类的重点实现也是proxyConnection代理连接对象的代理逻辑实现
* 它会对close()方法的调用进行处理并在调用realConnection的方法之前进行校验
* invoke()方法 是本类的重点实现,也是 proxyConnection代理连接对象 的代理逻辑实现
* 它会对 close()方法 的调用进行处理,并在调用 realConnection对象 的方法之前进行校验
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 如果调用的是close()方法,则将其放进连接池,而不是真的关闭连接
// 如果调用的是 close()方法,则将其放进连接池,而不是真的关闭连接
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// 通过上面的valid字段 校验连接是否有效
// 通过上面的 valid字段 校验连接是否有效
checkConnection();
}
// 调用realConnection的对应方法
// 调用 realConnection对象 的对应方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
@ -229,7 +229,7 @@ class PooledConnection implements InvocationHandler {
}
```
#### 1.3.2 PoolState
PoolState主要用于管理PooledConnection对象状态其通过持有两个List&lt;PooledConnection&gt;集合分别管理空闲状态的连接 和 活跃状态的连接。另外PoolState还定义了一系列用于统计的字段。
PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List&lt;PooledConnection&gt;集合 分别管理空闲状态的连接 和 活跃状态的连接。另外PoolState 还定义了一系列用于统计的字段。
```java
public class PoolState {
@ -336,8 +336,8 @@ public class PoolState {
}
```
#### 1.3.3 PooledDataSource
PooledDataSource管理的数据库连接对象 是由其持有的UnpooledDataSource对象创建的并由PoolState管理所有连接的状态。
PooledDataSource的getConnection()方法会首先调用popConnection()方法获取PooledConnection对象然后通过PooledConnection的getProxyConnection()方法获取数据库连接的代理对象。popConnection()方法是PooledDataSource的核心逻辑之一其整体的逻辑关系如下图
PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource对象 创建的,并由 PoolState 管理所有连接的状态。
PooledDataSource getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection对象然后通过 PooledConnection getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图:
![avatar](/images/mybatis/数据库连接池流程图.png)
@ -375,8 +375,8 @@ public class PooledDataSource implements DataSource {
private int expectedConnectionTypeCode;
/**
* 下面的两个getConnection()方法都会调用popConnection()
* 获取PooledConnection对象然后调用该对象的getProxyConnection()方法
* 下面的两个 getConnection()方法 都会调用 popConnection()
* 获取 PooledConnection对象然后调用该对象的 getProxyConnection()方法
* 获取数据库连接的代理对象
*/
@Override
@ -390,7 +390,7 @@ public class PooledDataSource implements DataSource {
}
/**
* 本方法实现了连接池获取连接对象的具体逻辑是PooledDataSource的核心逻辑之一
* 本方法实现了连接池获取连接对象的具体逻辑,是 PooledDataSource 的核心逻辑之一
*/
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
@ -421,7 +421,7 @@ public class PooledDataSource implements DataSource {
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// 如果最老的连接超时了就在PoolState中记录一下相关信息然后将该连接对象释放掉
// 如果最老的连接超时了,就在 PoolState 中记录一下相关信息,然后将该连接对象释放掉
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
@ -442,7 +442,7 @@ public class PooledDataSource implements DataSource {
log.debug("Bad connection. Could not roll back");
}
}
// 从最老连接中取出真正的 数据库连接对象及相关信息用来构建新的PooledConnection对象
// 从最老连接中取出真正的 数据库连接对象及相关信息,用来构建新的 PooledConnection对象
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
@ -453,7 +453,7 @@ public class PooledDataSource implements DataSource {
}
} else {
// 如果最老的连接对象也没超时,则进入阻塞等待,
// 等待时间poolTimeToWait可自行设置
// 等待时间 poolTimeToWait 可自行设置
try {
if (!countedWait) {
// 等待次数加一
@ -464,7 +464,7 @@ public class PooledDataSource implements DataSource {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
// native方法使执行到这里的线程阻塞等待poolTimeToWait毫秒
// native方法使执行到这里的线程阻塞等待 poolTimeToWait毫秒
state.wait(poolTimeToWait);
// 统计累计等待的时间
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
@ -529,9 +529,9 @@ public class PooledDataSource implements DataSource {
}
/**
* 看一下之前讲过的PooledConnection中的动态代理方法invoke(),可以发现
* 当调用数据库连接代理对象的close()方法时,并未关闭真正的数据库连接,
* 而是调用了本方法将连接对象归还给连接池方便后续使用本方法也是PooledDataSource的核心逻辑之一
* 看一下之前讲过的 PooledConnection 中的 动态代理方法invoke(),可以发现
* 当调用数据库连接代理对象的 close()方法 时,并未关闭真正的数据库连接,
* 而是调用了本方法,将连接对象归还给连接池,方便后续使用,本方法也是 PooledDataSource 的核心逻辑之一
*/
protected void pushConnection(PooledConnection conn) throws SQLException {
// 国际惯例,操作公共资源先上个锁
@ -540,7 +540,8 @@ public class PooledDataSource implements DataSource {
state.activeConnections.remove(conn);
// 如果该连接有效
if (conn.isValid()) {
// 如果连接池中的空闲连接数未达到阈值 且 该连接确实属于本连接池通过之前获取的expectedConnectionTypeCode进行校验
// 如果连接池中的空闲连接数未达到阈值 且 该连接确实属于
// 本连接池(通过之前获取的 expectedConnectionTypeCode 进行校验)
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
// CheckoutTime = 应用从连接池取出连接到归还连接的时长
// accumulatedCheckoutTime = 所有连接累计的CheckoutTime
@ -549,14 +550,14 @@ public class PooledDataSource implements DataSource {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 从conn中取出真正的 数据库连接对象重新封装成PooledConnection
// 从 conn 中取出真正的 数据库连接对象,重新封装成 PooledConnection
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
// 将newConn放进空闲连接对象列表
// 将 newConn 放进空闲连接对象列表
state.idleConnections.add(newConn);
// 设置newConn的相关属性
// 设置 newConn 的相关属性
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
// 将原本的conn作废
// 将原本的 conn 作废
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
@ -565,7 +566,7 @@ public class PooledDataSource implements DataSource {
state.notifyAll();
} else {
// 如果空闲连接已达阈值 或 该连接对象不属于本连接池,则做好统计数据
// 回滚连接的事务关闭真正的连接最后作废该conn
// 回滚连接的事务,关闭真正的连接,最后作废 该conn
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
@ -632,11 +633,11 @@ public class PooledDataSource implements DataSource {
}
}
```
最后我们来看一下popConnection()和pushConnection()都调用了的isValid()方法该方法除了检测PooledConnection中的valid字段外 还还会调用PooledDataSource中的pingConnection()方法,让数据库连接对象 执行指定的sql语句检测连接是否正常。
最后,我们来看一下 popConnection() pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql语句检测连接是否正常。
```java
class PooledConnection implements InvocationHandler {
/**
* 检测PooledConnection对象的有效性
* 检测 PooledConnection对象 的有效性
*/
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
@ -646,7 +647,7 @@ class PooledConnection implements InvocationHandler {
public class PooledDataSource implements DataSource {
/**
* ping一下数据库检测数据库连接是否正常
* ping 一下数据库,检测数据库连接是否正常
*/
protected boolean pingConnection(PooledConnection conn) {
boolean result = true;
@ -661,10 +662,10 @@ public class PooledDataSource implements DataSource {
}
if (result) {
// 是否允许发送检测语句检测数据库连接是否正常poolPingEnabled可自行配置
// 是否允许发送检测语句检测数据库连接是否正常poolPingEnabled 可自行配置
// 该检测会牺牲一定的系统资源,以提高安全性
if (poolPingEnabled) {
// 超过poolPingConnectionsNotUsedFor毫秒未使用的连接 才会检测其连接状态
// 超过 poolPingConnectionsNotUsedFor毫秒 未使用的连接 才会检测其连接状态
if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
if (log.isDebugEnabled()) {
@ -703,8 +704,8 @@ public class PooledDataSource implements DataSource {
}
```
## 2 Transaction
遵循接口-实现类的设计原则mybatis也是先使用Transaction接口对数据库事务做了抽象而实现类则只提供了两个JdbcTransaction和ManagedTransaction。这两种对象的获取使用了两个对应的工厂类JdbcTransactionFactory和ManagedTransactionFactory。
不过一般我们并不会使用mybatis管理事务而是将mybatis集成到spring由spring进行事务的管理。细节部分会在后面的文章中详细讲解。
遵循 “接口-实现类” 的设计原则Mybatis 也是先使用 Transaction接口 对数据库事务做了抽象而实现类则只提供了两个JdbcTransaction ManagedTransaction。这两种对象的获取使用了两个对应的工厂类 JdbcTransactionFactory ManagedTransactionFactory。
不过一般我们并不会使用 Mybatis 管理事务,而是将 Mybatis 集成到 Spring由 Spring 进行事务的管理。细节部分会在后面的文章中详细讲解。
```java
public interface Transaction {
@ -765,7 +766,7 @@ public class JdbcTransaction implements Transaction {
return connection;
}
// 提交、回滚、关闭等操作的代码都比较简单只对原生的JDBC操作做了简单封装
// 提交、回滚、关闭等操作的代码都比较简单,只对原生的 JDBC操作 做了简单封装
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
@ -863,10 +864,10 @@ public class ManagedTransaction implements Transaction {
private TransactionIsolationLevel level;
// 对应的数据库连接
private Connection connection;
// 控制是否关闭持有的连接在close()方法中用其判断是否真的关闭连接
// 控制是否关闭持有的连接,在 close()方法 中用其判断是否真的关闭连接
private final boolean closeConnection;
// 本类的实现也很简单commit、rollback方法都是空实现
// 本类的实现也很简单commit()、rollback()方法 都是空实现
public ManagedTransaction(Connection connection, boolean closeConnection) {
this.connection = connection;
this.closeConnection = closeConnection;
@ -926,7 +927,7 @@ public class ManagedTransaction implements Transaction {
public interface TransactionFactory {
/**
* 配置TransactionFactory对象一般会在完成TransactionFactory对象
* 配置 TransactionFactory对象一般会在完成 TransactionFactory对象
* 初始化之后 就进行自定义属性配置
*/
default void setProperties(Properties props) {
@ -934,12 +935,12 @@ public interface TransactionFactory {
}
/**
* 在指定的数据库连接上创建Transaction事务对象
* 在指定的数据库连接上创建 Transaction事务对象
*/
Transaction newTransaction(Connection conn);
/**
* 从指定数据源获取数据库连接并在此连接上创建Transaction对象
* 从指定数据源获取数据库连接,并在此连接上创建 Transaction对象
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

@ -1,29 +1,30 @@
binding模块主要为了解决一个历史遗留问题原先查询一个VO对象时需要调用SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法执行指定的sql语句第一个参数selectXXVOById指定了执行的sql语句id如果我们不小心写错了参数mybatis是无法在初始化时发现这个错误的只会在实际调用queryForObject(“selectXXVOById”, primaryKey)方法时才会抛出异常这对于工程师来说是非常难受的就像泛型出来之前很多类型转换不会在编译期发现错误一样。而binding模块就像Java的泛型机制一样将程序的错误提前暴露出来为开发人员省去不少排查问题的精力。
binding模块 主要为了解决一个历史遗留问题,原先查询一个 VO对象 时需要调用 SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法,执行指定的 sql语句第一个参数 selectXXVOById 指定了执行的 sql语句id如果我们不小心写错了参数Mybatis 是无法在初始化时发现这个错误的,只会在实际调用 queryForObject(“selectXXVOById”, primaryKey)方法 时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。而 binding模块 就像 Java的泛型机制 一样,将程序的错误提前暴露出来,为开发人员省去不少排查问题的精力。
binding模块的解决方案是定义一个Mapper接口在接口中定义sql语句对应的方法名(Id)及参数这些方法在mybatis的初始化过程中会与该Mapper接口对应的映射配置文件中的sql语句相关联如果存在无法关联的sql语句mybatis就会抛出异常,帮助我们及时发现问题。示例代码如下:
binding模块 的解决方案是,定义一个 Mapper接口在接口中定义 sql语句 对应的 方法名Id 及 参数,这些方法在 Mybatis 的初始化过程中,会与该 Mapper接口 对应的映射配置文件中的 sql语句 相关联,如果存在无法关联的 sql语句Mybatis 就会抛出异常,帮助我们及时发现问题。示例代码如下:
```java
public interface HeroMapper {
// 映射文件中会存在一个<select>节点id为“selectHeroVOById”
public HeroVO selectHeroVOById(int id);
// 映射文件中会存在一个 <select>节点id “selectHeroVOById”
public HeroVO selectHeroVOById(int id);
}
// 首先获取HeroMapper对应的代理对象
// 首先,获取 HeroMapper 对应的代理对象
HeroMapper heroMapper = session.getMapper(HeroMapper.class);
// 直接调用HeroMapper接口中的方法 获取结果集
// 直接调用 HeroMapper接口 中的方法,获取结果集
HeroVO heroVO = heroMapper.selectHeroVOById("23333");
```
## 1 MapperRegistry和MapperProxyFactory
MapperRegistry是Mapper接口及其对应的代理对象工厂的注册中心。Configuration是mybatis中全局性的配置对象根据mybatis的核心配置文件mybatis-config.xml解析而成。Configuration通过mapperRegistry属性持有该对象。
mybatis在初始化过程中会读取映射配置文件和Mapper接口中的注解信息并调用MapperRegistry的addMappers()方法填充knownMappers集合在需要执行某sql语句时会先调用getMapper()方法获取实现了Mapper接口的动态代理对象。
MapperRegistry 是 Mapper接口 及其对应的代理对象工厂的注册中心。Configuration 是 Mybatis 中全局性的配置对象,根据 Mybatis 的核心配置文件 mybatis-config.xml 解析而成。Configuration 通过 mapperRegistry属性 持有该对象。
Mybatis 在初始化过程中会读取映射配置文件和 Mapper接口 中的注解信息,并调用 MapperRegistry 的 addMappers()方法 填充 knownMappers集合在需要执行某 sql语句 时,会先调用 getMapper()方法 获取实现了 Mapper接口 的动态代理对象。
```java
public class MapperRegistry {
// mybatis全局唯一的配置对象,包含了几乎所有配置信息
// Mybatis 全局唯一的配置对象,包含了几乎所有配置信息
private final Configuration config;
// keyMapper接口valueMapperProxyFactory为Mapper接口创建代理对象的工厂
// keyMapper接口valueMapperProxyFactory Mapper接口 创建代理对象的工厂
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
// 下面的两个重载方法 通过扫描指定的包目录获取所有的Mapper接口
// 下面的两个重载方法 通过扫描指定的包目录,获取所有的 Mapper接口
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
@ -38,7 +39,7 @@ public class MapperRegistry {
}
public <T> void addMapper(Class<T> type) {
// 该type是不是接口
// 该 type 是不是接口
if (type.isInterface()) {
// 是否已经加载过
if (hasMapper(type)) {
@ -46,9 +47,9 @@ public class MapperRegistry {
}
boolean loadCompleted = false;
try {
// 将Mapper接口的Class对象 和 对应的MapperProxyFactory对象添加到knownMappers集合
// 将 Mapper接口 Class对象 和 对应的 MapperProxyFactory对象 添加到 knownMappers集合
knownMappers.put(type, new MapperProxyFactory<>(type));
// XML解析和注解的处理
// XML 解析和注解的处理
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
@ -62,45 +63,45 @@ public class MapperRegistry {
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取type对应的MapperProxyFactory对象
// 获取 type 对应的 MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 根据sqlSession创建 type接口的代理对象
// 根据 sqlSession 创建 type接口 的代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// 获取所有的MapperProxyFactory
// 获取所有的 MapperProxyFactory
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
// 初始化的时候会持有Configuration对象
// 初始化的时候会持有 Configuration对象
public MapperRegistry(Configuration config) {
this.config = config;
}
// 是否存在指定的MapperProxyFactory
// 是否存在指定的 MapperProxyFactory
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
}
```
MapperProxyFactory主要负责创建代理对象。
MapperProxyFactory 主要负责创建代理对象。
```java
public class MapperProxyFactory<T> {
// 要创建的动态代理对象 所实现的接口
private final Class<T> mapperInterface;
// 缓存mapperInterface接口中Method对象和其对应的MapperMethod对象
// 缓存 mapperInterface接口 Method对象 和其对应的 MapperMethod对象
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
// 初始化时为mapperInterface注入值
// 初始化时为 mapperInterface 注入值
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@ -114,15 +115,15 @@ public class MapperProxyFactory<T> {
}
public T newInstance(SqlSession sqlSession) {
// 每都会创建一个新的MapperProxy对象
// 每都会创建一个新的 MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
/**
* 非常眼熟的JDK动态代理 代码创建了实现mapperInterface接口的代理对象
* 根据国际惯例mapperProxy对应的类 肯定实现了InvocationHandler接口
* 为mapperInterface接口方法的调用织入统一处理逻辑
* 非常眼熟的 JDK动态代理 代码,创建了实现 mapperInterface接口 的代理对象
* 根据国际惯例mapperProxy对应的类 肯定实现了 InvocationHandler接口
* 为 mapperInterface接口方法的调用 织入统一处理逻辑
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
@ -130,17 +131,17 @@ public class MapperProxyFactory<T> {
}
```
## 2 MapperProxy
MapperProxy实现了InvocationHandler接口为Mapper接口的方法调用织入了统一处理。
MapperProxy 实现了 InvocationHandler接口 Mapper接口 的方法调用织入了统一处理。
```java
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
// 记录关联的sqlSession对象
// 记录关联的 sqlSession对象
private final SqlSession sqlSession;
// 对应的Mapper接口的Class对象
// 对应的 Mapper接口 Class对象
private final Class<T> mapperInterface;
// 用于缓存MapperMethod对象keyMapper接口中方法对应的Method对象
// valueMapperMethod对象该对象会完成参数转换 及 sql语句的执行功能
// 用于缓存 MapperMethod对象keyMapper接口 中方法对应的 Method对象
// valueMapperMethod对象该对象会完成参数转换 及 sql语句 的执行功能)
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
@ -152,7 +153,7 @@ public class MapperProxy<T> implements InvocationHandler, Serializable {
// 为被代理对象的方法 织入统一处理
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果目标方法继承自Object则直接调用目标方法
// 如果目标方法继承自 Object则直接调用目标方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
@ -161,30 +162,31 @@ public class MapperProxy<T> implements InvocationHandler, Serializable {
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取mapperMethod对象如果没有就创建新的
// 从缓存中获取 mapperMethod对象如果没有就创建新的
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行sql语句返回结果集
// 执行 sql语句返回结果集
return mapperMethod.execute(sqlSession, args);
}
// 主要负责维护methodCache 缓存
// 主要负责维护 methodCache 缓存
private MapperMethod cachedMapperMethod(Method method) {
// 这里用到了Java8的新特性computeIfAbsent()是Java8的方法Lambda表达式也是Java8中最重要的新特性之一
// computeIfAbsent()方法表示 当前map中若key对应的value为空则执行传入的Lambda表达式将key和表达式的value
// 存入当前map并返回value值
// 在这段代码中的意思是若methodCache中没有method对应的value就执行右侧的Lambda表达式并将表达式的结果
// 存入methodCache 并 返回
// 这里用到了 Java8 的新特性computeIfAbsent() Java8 新增的方法Lambda表达式 也是 Java8中 最重要的新特性之一
// computeIfAbsent()方法 表示 当前map中 key 对应的 value 为空,则执行传入的 Lambda表达式 key 和表达式的 value
// 存入 当前map并返回 value值
// 在这段代码中的意思是:若 methodCache 中没有 method 对应的 value就执行右侧的 Lambda表达式并将表达式的结果
// 存入 methodCache 并返回
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
```
## 3 MapperMethod
MapperMethod中封装了Mapper接口中对应方法的信息和对应sql语句的信息是连接Mapper接口及映射配置文件中定义的sql语句的桥梁。
MapperMethod中持有两个非常重要的属性这两个属性对应的类 都是MapperMethod中的静态内部类。另外MapperMethod在被实例化时就对这两个属性进行了初始化。
MapperMethod 中封装了 Mapper接口 中对应方法的信息,和对应 sql语句 的信息,是连接 Mapper接口 及映射配置文件中定义的 sql语句 的桥梁。
MapperMethod 中持有两个非常重要的属性,这两个属性对应的类 都是 MapperMethod 中的静态内部类。另外MapperMethod 在被实例化时就对这两个属性进行了初始化。
```java
public class MapperMethod {
/** 下面这俩货都是内部类而且还是public static的 */
/** 下面这俩货都是内部类,而且还是 public static 的 */
private final SqlCommand command;
private final MethodSignature method;
@ -194,25 +196,25 @@ public class MapperMethod {
}
}
```
MapperMethod中的核心方法execute()就主要用到了这两个类所以我们先看一下SqlCommand和MethodSignature的源码。
MapperMethod 中的核心方法 execute() 就主要用到了这两个类,所以我们先看一下 SqlCommand MethodSignature 的源码。
### 3.1 SqlCommand
```java
public static class SqlCommand {
// sql语句的id
private final String name;
// sql语句的类型SqlCommandType是枚举类型持有常用的 增、删、改、查等操作类型
// sql语句的类型SqlCommandType 是枚举类型,持有常用的 增、删、改、查等操作类型
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 方法名
final String methodName = method.getName();
// 该方法对应的类的Class对象
// 该方法对应的类的 Class对象
final Class<?> declaringClass = method.getDeclaringClass();
// MappedStatement封装了sql语句相关的信息在mybatis初始化时创建
// MappedStatement 封装了 sql语句 相关的信息,在 Mybatis初始化 时创建
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
// 处理Flush注解
// 处理 Flush注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
@ -221,7 +223,7 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 初始化name 和 type
// 初始化 name 和 type
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
@ -232,17 +234,17 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// sql语句的名称默认是由Mapper接口方法的 包名.类名.方法名
// sql语句 的名称默认是由 Mapper接口方法 的 包名.类名.方法名
String statementId = mapperInterface.getName() + "." + methodName;
// 检测是否有该名称的sql语句
// 检测是否有该名称的 sql语句
if (configuration.hasStatement(statementId)) {
// 从configuration的mappedStatements容器中获取statementId对应的MappedStatement对象
// 从 configuration mappedStatements容器 中获取 statementId 对应的 MappedStatement对象
return configuration.getMappedStatement(statementId);
// 如果此方法不是mapperInterface接口定义的则返回空
// 如果此方法不是 mapperInterface接口 定义的,则返回空
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 对mapperInterface的父接口 进行递归处理
// 对 mapperInterface 的父接口 进行递归处理
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
@ -272,33 +274,33 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
private final boolean returnsMany;
// 返回值类型是否为 Map类型
private final boolean returnsMap;
// 返回值类型是否为void
// 返回值类型是否为 void
private final boolean returnsVoid;
// 返回值类型是否为Cursor
// 返回值类型是否为 Cursor
private final boolean returnsCursor;
// 返回值类型是否为Optional
// 返回值类型是否为 Optional
private final boolean returnsOptional;
// 返回值类型的Class对象
// 返回值类型的 Class对象
private final Class<?> returnType;
// 如果返回值类型为Map则用该字段记录了作为key的列名
// 如果返回值类型为 Map则用该字段记录了作为 key 的列名
private final String mapKey;
// 标记该方法参数列表中ResultHandler类型参数的位置
// 标记该方法参数列表中 ResultHandler类型参数 的位置
private final Integer resultHandlerIndex;
// 标记该方法参数列表中RowBounds类型参数的位置
// 标记该方法参数列表中 RowBounds类型参数 的位置
private final Integer rowBoundsIndex;
/**
* 顾名思义这是一个处理Mapper接口中 方法参数列表的解析器它使用了一个SortedMap<Integer, String>
* 类型的容器 记录了参数在参数列表中的位置索引 与 参数名之间的对应关系key参数在参数列表中的索引位置
* 顾名思义,这是一个处理 Mapper接口 中 方法参数列表的解析器,它使用了一个 SortedMap<Integer, String>
* 类型的容器记录了参数在参数列表中的位置索引 与 参数名之间的对应关系key参数 在参数列表中的索引位置,
* value参数名(参数名可用@Param注解指定默认使用参数索引作为其名称)
*/
private final ParamNameResolver paramNameResolver;
/**
* MethodSignature的构造方法会解析对应的method并初始化上述字段
* MethodSignature 的构造方法会解析对应的 method并初始化上述字段
*/
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 获取method方法的返回值类型
// 获取 method方法 的返回值类型
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
@ -307,7 +309,7 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
} else {
this.returnType = method.getReturnType();
}
// 对MethodSignature持有的各属性 进行初始化
// 对 MethodSignature 持有的各属性 进行初始化
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
@ -340,18 +342,18 @@ MapperMethod中的核心方法execute()就主要用到了这两个类,所以
}
```
### 3.3 execute()方法
execute()方法会根据sql语句的类型(CRUD)调用SqlSession对应的方法完成数据库操作SqlSession是mybatis的核心组件之一,后面会详细解读。
execute()方法 会根据 sql语句 的类型(CRUD)调用 SqlSession 对应的方法完成数据库操作SqlSession 是 Mybatis 的核心组件之一,后面会详细解读。
```java
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据sql语句的类型 调用sqlSession对应的方法
// 根据 sql语句 的类型 调用 sqlSession 对应的方法
switch (command.getType()) {
case INSERT: {
// 使用ParamNameResolver处理args实参列表将用户传入的实参与
// 使用 ParamNameResolver 处理 args实参列表将用户传入的实参与
// 指定参数名称关联起来
Object param = method.convertArgsToSqlCommandParam(args);
// 获取返回结果rowCountResult()方法 根据method属性中的returnType
// 获取返回结果rowCountResult()方法 根据 method属性 中的 returnType
// 对结果的类型进行转换
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
@ -367,24 +369,24 @@ public class MapperMethod {
break;
}
case SELECT:
// 处理返回值为void且ResultSet通过ResultHandler处理的方法
// 处理返回值为 void ResultSet 通过 ResultHandler 处理的方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 处理返回值为集合 或 数组的方法
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 处理返回值为Map的方法
// 处理返回值为 Map 的方法
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 处理返回值为Cursor的方法
// 处理返回值为 Cursor 的方法
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 处理返回值为单一对象的方法
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
// 处理返回值为Optional的方法
// 处理返回值为 Optional 的方法
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
@ -405,20 +407,20 @@ public class MapperMethod {
}
/**
* 当执行insert、update、delete类型的sql语句时其执行结果都要经过本方法处理
* 当执行 insert、update、delete 类型的 sql语句 时,其执行结果都要经过本方法处理
*/
private Object rowCountResult(int rowCount) {
final Object result;
// 方法的返回值为void时
// 方法的返回值为 void
if (method.returnsVoid()) {
result = null;
// 方法的返回值为Integer时
// 方法的返回值为 Integer
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
// 方法的返回值为Long时
// 方法的返回值为 Long
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
// 方法的返回值为Boolean时
// 方法的返回值为 Boolean
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
@ -428,12 +430,12 @@ public class MapperMethod {
}
/**
* 如果Mapper接口中定义的方法准备使用ResultHandler处理查询结果集则通过此方法处理
* 如果 Mapper接口 中定义的方法准备使用 ResultHandler 处理查询结果集,则通过此方法处理
*/
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
// 获取sql语句对应的MappedStatement对象该对象中记录了sql语句相关信息
// 获取 sql语句 对应的 MappedStatement对象该对象中记录了 sql语句 相关信息
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
// 当使用ResultHandler处理结果集时必须指定ResultMap或ResultType
// 当使用 ResultHandler 处理结果集时,必须指定 ResultMap ResultType
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
@ -442,11 +444,11 @@ public class MapperMethod {
}
// 转换实参列表
Object param = method.convertArgsToSqlCommandParam(args);
// 如果实参列表中有RowBounds类型参数
// 如果实参列表中有 RowBounds类型参数
if (method.hasRowBounds()) {
// 从args参数列表中获取RowBounds对象
// 从 args参数列表 中获取 RowBounds对象
RowBounds rowBounds = method.extractRowBounds(args);
// 执行查询并用指定的ResultHandler处理结果对象
// 执行查询,并用指定的 ResultHandler 处理结果对象
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
@ -454,22 +456,22 @@ public class MapperMethod {
}
/**
* 如果Mapper接口中对应方法的返回值为集合(Collection接口实现类) 或 数组,
* 如果 Mapper接口 中对应方法的返回值为集合(Collection接口实现类) 或 数组,
* 则调用本方法将结果集处理成 相应的集合或数组
*/
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 参数列表转换
Object param = method.convertArgsToSqlCommandParam(args);
// 参数列表中是否有RowBounds类型的参数
// 参数列表中是否有 RowBounds类型的参数
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 这里使用了selectList()方法进行查询所以返回的结果集就是List类型的
// 这里使用了 selectList()方法 进行查询,所以返回的结果集就是 List类型的
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// 将结果集转换为数组或Collection集合
// 将结果集转换为数组或 Collection集合
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
@ -481,27 +483,27 @@ public class MapperMethod {
}
/**
* 将结果集转换成Collection集合
* 将结果集转换成 Collection集合
*/
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
// 使用前面介绍的ObjectFactory通过反射方式创建集合对象
// 使用前面介绍的 ObjectFactory通过反射方式创建集合对象
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
// 实际上就是调用了Collection的addAll()方法
// 实际上就是调用了 Collection addAll()方法
metaObject.addAll(list);
return collection;
}
/**
* 本方法和上面的convertToDeclaredCollection()功能类似,主要负责将结果对象转换成数组
* 本方法和上面的 convertToDeclaredCollection()功能 类似,主要负责将结果对象转换成数组
*/
@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
// 获取数组中元素的类型Class
// 获取数组中元素的 类型Class
Class<?> arrayComponentType = method.getReturnType().getComponentType();
// 根据元素类型 和 元素数量 初始化数组
Object array = Array.newInstance(arrayComponentType, list.size());
// 将List转换成数组
// 将 List 转换成数组
if (arrayComponentType.isPrimitive()) {
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
@ -513,7 +515,7 @@ public class MapperMethod {
}
/**
* 如果Mapper接口中对应方法的返回值为类型为Map则调用此方法执行sql语句
* 如果 Mapper接口 中对应方法的返回值为类型为 Map则调用此方法执行 sql语句
*/
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
@ -521,7 +523,7 @@ public class MapperMethod {
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 注意这里调用的是sqlSession的selectMap方法使用返回的是一个Map类型结果集
// 注意这里调用的是 SqlSession 的 selectMap()方法,返回的是一个 Map类型结果集
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
@ -530,7 +532,7 @@ public class MapperMethod {
}
/**
* 本方法与上面的executeForMap()方法类似只不过sqlSession调用的是selectCursor()
* 本方法与上面的 executeForMap()方法 类似,只不过 sqlSession 调用的是 selectCursor()
*/
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;

@ -1,21 +1,21 @@
MyBatis中的缓存分为一级缓存、二级缓存但在本质上是相同的它们使用的都是Cache接口的实现。MyBatis缓存模块的设计 使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。
MyBatis 中的缓存分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是 Cache接口 的实现。MyBatis缓存模块 的设计,使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。
## 1 Cache组件
MyBatis中缓存模块相关的代码位于org.apache.ibatis.cache包下其中Cache接口是缓存模块中最核心的接口它定义了所有缓存的基本行为。
MyBatis 中缓存模块相关的代码位于 org.apache.ibatis.cache包 下,其中 Cache接口 是缓存模块中最核心的接口,它定义了所有缓存的基本行为。
```java
public interface Cache {
/**
* 获取当前缓存的Id
* 获取当前缓存的 Id
*/
String getId();
/**
* 存入缓存的key和valuekey一般为CacheKey对象
* 存入缓存的 key valuekey 一般为 CacheKey对象
*/
void putObject(Object key, Object value);
/**
* 根据key获取缓存值
* 根据 key 获取缓存值
*/
Object getObject(Object key);
@ -37,7 +37,7 @@ public interface Cache {
/**
*
* 获取读写锁,可以看到,这个接口方法提供了默认的实现!!
* 这是Java8的新特性只是平时开发时很少用到
* 这是 Java8 的新特性!!只是平时开发时很少用到!!!
*
*/
default ReadWriteLock getReadWriteLock() {
@ -45,18 +45,19 @@ public interface Cache {
}
}
```
如下图所示Cache接口的实现类有很多但大部分都是装饰器只有PerpetualCache提供了Cache 接口的基本实现。
如下图所示Cache接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口 的基本实现。
![avatar](/images/mybatis/Cache组件.png)
### 1.1 PerpetualCache
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用HashMap记录缓存项也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用 HashMap 记录缓存项,也是通过该 HashMap对象 的方法实现的 Cache接口 中定义的相应方法。
```java
public class PerpetualCache implements Cache {
// Cache对象的唯一标识
// Cache对象 的唯一标识
private final String id;
// 其所有的缓存功能实现都是基于JDK的HashMap提供的方法
// 其所有的缓存功能实现,都是基于 JDK HashMap 提供的方法
private Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
@ -94,7 +95,7 @@ public class PerpetualCache implements Cache {
}
/**
* 其重写了Object中的equals()和hashCode()方法两者都只关心id字段
* 其重写了 Object 中的 equals() hashCode()方法,两者都只关心 id字段
*/
@Override
public boolean equals(Object o) {
@ -121,9 +122,9 @@ public class PerpetualCache implements Cache {
}
}
```
下面来看一下cache.decorators包下提供的装饰器它们都直接实现了Cache接口扮演着装饰器的角色。这些装饰器会在PerpetualCache的基础上提供一些额外的功能通过多个组合后满足一个特定的需求。
下面来看一下 cache.decorators包 下提供的装饰器,它们都直接实现了 Cache接口扮演着装饰器的角色。这些装饰器会在 PerpetualCache 的基础上提供一些额外的功能,通过多个组合后满足一个特定的需求。
### 1.2 BlockingCache
BlockingCache是阻塞版本的缓存装饰器它会保证只有一个线程到数据库中查找指定key对应的数据。
BlockingCache 是阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据。
```java
public class BlockingCache implements Cache {
@ -131,7 +132,7 @@ public class BlockingCache implements Cache {
private long timeout;
// 持有的被装饰者
private final Cache delegate;
// 每个key都有其对应的ReentrantLock锁对象
// 每个 key 都有其对应的 ReentrantLock锁对象
private final ConcurrentHashMap<Object, ReentrantLock> locks;
// 初始化 持有的持有的被装饰者 和 锁集合
@ -141,11 +142,11 @@ public class BlockingCache implements Cache {
}
}
```
假设线程A在BlockingCache中未查找到keyA对应的缓存项时线程A会获取keyA对应的锁这样后续线程A在查找keyA时,其它线程会被阻塞。
假设 线程A 在 BlockingCache 中未查找到 keyA 对应的缓存项时线程A 会获取 keyA 对应的锁这样线程A 在后续查找 keyA 时,其它线程会被阻塞。
```java
// 根据key获取锁对象然后上锁
// 根据 key 获取锁对象,然后上锁
private void acquireLock(Object key) {
// 获取key对应的锁对象
// 获取 key 对应的锁对象
Lock lock = getLockForKey(key);
// 获取锁,带超时时长
if (timeout > 0) {
@ -165,18 +166,18 @@ public class BlockingCache implements Cache {
}
private ReentrantLock getLockForKey(Object key) {
// Java8新特性Map系列类中新增的方法
// Java8 新特性Map系列类 中新增的方法
// V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
// 表示若key对应的value为空则将第二个参数的返回值存入该Map集合并返回
// 表示,若 key 对应的 value 为空,则将第二个参数的返回值存入该 Map集合 并返回
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
```
假设线程A从数据库中查找到keyA对应的结果对象后将结果对象放入到BlockingCache中此时线程A会释放keyA对应的锁唤醒阻塞在该锁上的线程。其它线程即可从BlockingCache中获取keyA对应的数据而不是再次访问数据库。
假设 线程A 从数据库中查找到 keyA 对应的结果对象后,将结果对象放入到 BlockingCache 中,此时 线程A 会释放 keyA 对应的锁,唤醒阻塞在该锁上的线程。其它线程即可从 BlockingCache 中获取 keyA 对应的数据,而不是再次访问数据库。
```java
@Override
public void putObject(Object key, Object value) {
try {
// 存入key和其对应的缓存项
// 存入 key 和其对应的缓存项
delegate.putObject(key, value);
} finally {
// 最后释放锁
@ -194,18 +195,18 @@ public class BlockingCache implements Cache {
}
```
### 1.3 FifoCache和LruCache
在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache是先入先出版本的装饰器当向缓存添加数据时如果缓存项的个数已经达到上限则会将缓存中最老即最早进入缓存的缓存项删除。
在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache 是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。
```java
public class FifoCache implements Cache {
// 被装饰对象
private final Cache delegate;
// 用一个FIFO的队列记录key的顺序其具体实现为LinkedList
// 用一个 FIFO 的队列记录 key 的顺序,其具体实现为 LinkedList
private final Deque<Object> keyList;
// 决定了缓存的容量上限
private int size;
// 国际惯例通过构造方法初始化自己的属性缓存容量上限默认为1024个
// 国际惯例,通过构造方法初始化自己的属性,缓存容量上限默认为 1024个
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<>();
@ -228,16 +229,16 @@ public class FifoCache implements Cache {
@Override
public void putObject(Object key, Object value) {
// 存储缓存项之前先在keyList中注册
// 存储缓存项之前,先在 keyList 中注册
cycleKeyList(key);
// 存储缓存项
delegate.putObject(key, value);
}
private void cycleKeyList(Object key) {
// 在keyList队列中注册要添加的key
// 在 keyList队列 中注册要添加的 key
keyList.addLast(key);
// 如果注册这个key会超出容积上限则把最老的一个缓存项清除掉
// 如果注册这个 key 会超出容积上限,则把最老的一个缓存项清除掉
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
@ -254,7 +255,7 @@ public class FifoCache implements Cache {
return delegate.removeObject(key);
}
// 除了清理缓存项还要清理key的注册列表
// 除了清理缓存项,还要清理 key 的注册列表
@Override
public void clear() {
delegate.clear();
@ -263,36 +264,36 @@ public class FifoCache implements Cache {
}
```
LruCache是按照"近期最少使用算法"Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。
LruCache 是按照"近期最少使用算法"Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。
```java
public class LruCache implements Cache {
// 被装饰者
private final Cache delegate;
// 这里使用的是LinkedHashMap它继承了HashMap但它的元素是有序的
// 这里使用的是 LinkedHashMap它继承了 HashMap但它的元素是有序的
private Map<Object, Object> keyMap;
// 最近最少被使用的缓存项的key
// 最近最少被使用的缓存项的 key
private Object eldestKey;
// 国际惯例,构造方法中进行属性初始化
public LruCache(Cache delegate) {
this.delegate = delegate;
// 这里初始化了keyMap并定义了eldestKey的取值规则
// 这里初始化了 keyMap并定义了 eldestKey 的取值规则
setSize(1024);
}
public void setSize(final int size) {
// 初始化keyMap同时指定该Map的初始容积及加载因子第三个参数true表示该LinkedHashMap
// 记录的顺序是accessOrderLinkedHashMap.get()方法会改变其中元素的顺序
// 初始化 keyMap同时指定该 Map 的初始容积及加载因子第三个参数true 表示 该LinkedHashMap
// 记录的顺序是 accessOrderLinkedHashMap.get()方法 会改变其中元素的顺序
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
// 当调用LinkedHashMap.put()方法时,该方法会被调用
// 当调用 LinkedHashMap.put()方法 时,该方法会被调用
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
// 当已达到缓存上限更新eldestKey字段后面将其删除
// 当已达到缓存上限,更新 eldestKey字段后面将其删除
eldestKey = eldest.getKey();
}
return tooBig;
@ -304,13 +305,13 @@ public class LruCache implements Cache {
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
// 记录缓存项的key超出容量则清除最久未使用的缓存项
// 记录缓存项的 key超出容量则清除最久未使用的缓存项
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
// eldestKey不为空则表示已经达到缓存上限
// eldestKey 不为空,则表示已经达到缓存上限
if (eldestKey != null) {
// 清除最久未使用的缓存
delegate.removeObject(eldestKey);
@ -321,7 +322,7 @@ public class LruCache implements Cache {
@Override
public Object getObject(Object key) {
// 访问key元素 会改变该元素在LinkedHashMap中的顺序
// 访问 key元素 会改变该元素在 LinkedHashMap 中的顺序
keyMap.get(key); //touch
return delegate.getObject(key);
}
@ -350,33 +351,33 @@ public class LruCache implements Cache {
}
```
### 1.4 SoftCache和WeakCache
在分析SoftCache和WeakCache实现之前我们再温习一下Java提供的4种引用类型强引用StrongReference、软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
在分析 SoftCache WeakCache 实现之前,我们再温习一下 Java 提供的4种引用类型强引用StrongReference、软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
- 强引用
平时用的最多的如Object obj new Object()新建的Object对象就是被强引用的。如果一个对象被强引用即使是JVM内存空间不足要抛出OutOfMemoryError异常GC也绝不会回收该对象。
平时用的最多的,如 Object obj new Object(),新建的 Object对象 就是被强引用的。如果一个对象被强引用,即使是 JVM内存空间不足要抛出 OutOfMemoryError异常GC 也绝不会回收该对象。
- 软引用
仅次于强引用的一种引用它使用类SoftReference来表示。当JVM内存不足时GC会回收那些只被软引用指向的对象从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象例如 数据库缓存中的对象就可以从数据库中恢复所以软引用可以用来实现缓存下面要介绍的SoftCache就是通过软引用实现的。
另外由于在程序使用软引用之前的某个时刻其所指向的对象可能己经被GC回收掉了所以通过 Reference.get()方法来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。
仅次于强引用的一种引用,它使用类 SoftReference 来表示。当 JVM内存不足时GC 会回收那些只被软引用指向的对象,从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象,例如, 数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面要介绍的 SoftCache 就是通过软引用实现的。
另外,由于在程序使用软引用之前的某个时刻,其所指向的对象可能己经被 GC 回收掉了,所以通过 Reference.get()方法 来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。
- 弱引用
弱引用使用WeakReference表示它不会阻止所引用的对象被GC回收。在JVM进行垃圾回收时如果指向一个对象的所有引用都是弱引用那么该对象会被回收。
所以只被弱引用所指向的对象其生存周期是两次GC之间的这段时间而只被软引用所指向的对象可以经历多次GC直到出现内存紧张的情况才被回收。
弱引用使用 WeakReference表示它不会阻止所引用的对象被 GC回收。在 JVM 进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。
所以,只被弱引用所指向的对象,其生存周期是 两次GC之间 的这段时间,而只被软引用所指向的对象可以经历多次 GC直到出现内存紧张的情况才被回收。
- 虚引用
最弱的一种引用类型由类PhantomReference表示。虚引用可以用来实现比较精细的内存使用控制但很少使用。
最弱的一种引用类型,由类 PhantomReference 表示。虚引用可以用来实现比较精细的内存使用控制,但很少使用。
- 引用队列ReferenceQueue )
很多场景下我们的程序需要在一个对象被GC时得到通知引用队列就是用于收集这些信息的队列。在创建SoftReference对象时可以为其关联一个引用队列当SoftReference所引用的对象被GC时 JVM就会将该SoftReference对象添加到与之关联的引用队列中。当需要检测这些通知信息时就可以从引用队列中获取这些SoftReference对象。不仅是SoftReference弱引用和虚引用都可以关联相应的队列。
很多场景下,我们的程序需要在一个对象被 GC 时得到通知,引用队列就是用于收集这些信息的队列。在创建 SoftReference对象 时,可以为其关联一个引用队列,当 SoftReference 所引用的对象被 GC 时, JVM 就会将该 SoftReference对象 添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些 SoftReference对象。不仅是 SoftReference弱引用和虚引用都可以关联相应的队列。
现在来看一下SoftCache的具体实现。
现在来看一下 SoftCache 的具体实现。
```java
public class SoftCache implements Cache {
// 这里使用了LinkedList作为容器在SoftCache中最近使用的一部分缓存项不会被GC
// 这是通过将其value添加到hardLinksToAvoidGarbageCollection集合实现的有强引用指向其value
// 这里使用了 LinkedList 作为容器,在 SoftCache 中,最近使用的一部分缓存项不会被 GC
// 这是通过将其 value 添加到 hardLinksToAvoidGarbageCollection集合 实现的有强引用指向其value
private final Deque<Object> hardLinksToAvoidGarbageCollection;
// 引用队列用于记录已经被GC的缓存项所对应的SoftEntry对象
// 引用队列,用于记录已经被 GC 的缓存项所对应的 SoftEntry对象
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
// 持有的被装饰者
private final Cache delegate;
// 强连接的个数默认为256
// 强连接的个数,默认为 256
private int numberOfHardLinks;
// 构造方法进行属性的初始化
@ -391,7 +392,7 @@ public class SoftCache implements Cache {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
// 指向value的引用是软引用并且关联了 引用队列
// 指向 value 的引用是软引用,并且关联了 引用队列
super(value, garbageCollectionQueue);
// 强引用
this.key = key;
@ -400,7 +401,7 @@ public class SoftCache implements Cache {
@Override
public void putObject(Object key, Object value) {
// 清除已经被GC的缓存项
// 清除已经被 GC 的缓存项
removeGarbageCollectedItems();
// 添加缓存
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
@ -408,7 +409,7 @@ public class SoftCache implements Cache {
private void removeGarbageCollectedItems() {
SoftEntry sv;
// 遍历queueOfGarbageCollectedEntries集合清除已经被GC的缓存项value
// 遍历 queueOfGarbageCollectedEntries集合清除已经被 GC 的缓存项 value
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
@ -418,21 +419,21 @@ public class SoftCache implements Cache {
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
// 用一个软引用指向 key对应的缓存项
// 用一个软引用指向 key 对应的缓存项
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
// 检测缓存中是否有对应的缓存项
if (softReference != null) {
// 获取softReference引用的value
// 获取 softReference 引用的 value
result = softReference.get();
// 如果softReference引用的对象已经被GC则从缓存中清除对应的缓存项
// 如果 softReference 引用的对象已经被 GC则从缓存中清除对应的缓存项
if (result == null) {
delegate.removeObject(key);
} else {
synchronized (hardLinksToAvoidGarbageCollection) {
// 将缓存项的value添加到hardLinksToAvoidGarbageCollection集合中保存
// 将缓存项的 value 添加到 hardLinksToAvoidGarbageCollection集合 中保存
hardLinksToAvoidGarbageCollection.addFirst(result);
// 如果hardLinksToAvoidGarbageCollection的容积已经超过numberOfHardLinks
// 则将最老的缓存项从hardLinksToAvoidGarbageCollection中清除FIFO
// 如果 hardLinksToAvoidGarbageCollection 的容积已经超过 numberOfHardLinks
// 则将最老的缓存项从 hardLinksToAvoidGarbageCollection 中清除FIFO
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
@ -444,7 +445,7 @@ public class SoftCache implements Cache {
@Override
public Object removeObject(Object key) {
// 清除指定的缓存项之前也会先清理被GC的缓存项
// 清除指定的缓存项之前,也会先清理被 GC 的缓存项
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@ -456,7 +457,7 @@ public class SoftCache implements Cache {
// 清理强引用集合
hardLinksToAvoidGarbageCollection.clear();
}
// 清理被GC的缓存项
// 清理被 GC 的缓存项
removeGarbageCollectedItems();
// 清理最底层的缓存项
delegate.clear();
@ -479,17 +480,17 @@ public class SoftCache implements Cache {
}
```
WeakCache的实现与SoftCache基本类似唯一的区别在于其中使用WeakEntry继承了WeakReference封装真正的 value 对象,其他实现完全一样。
WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry继承了WeakReference封装真正的 value对象其他实现完全一样。
另外还有ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache等。ScheduledCache是周期性清理缓存的装饰器它的clearInterval字段记录了两次缓存清理之间的时间间隔默认是一小时lastClear字段记录了最近一次清理的时间戳。ScheduledCache 的getObject()、putObject()、removeObject()等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。
另外,还有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理缓存的装饰器,它的 clearInterval字段 记录了两次缓存清理之间的时间间隔默认是一小时lastClear字段 记录了最近一次清理的时间戳。 ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 字段和 request 字段记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit字段 和 request字段 记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法 中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。
SynchronizedCache通过在每个方法上添加 synchronized关键字为Cache添加了同步功能有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类。
SynchronizedCache 通过在每个方法上添加 synchronized关键字 Cache 添加了同步功能,有点类似于 JDK 中 Collections 的 SynchronizedCollection内部类。
SerializedCache 提供了将 value 对象序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象进行序列化,井将序列化后的 byte[] 数组作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[] 数组反序列化成 Java 对象。不使用 SerializedCache 装饰器进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而 使用SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java 原生序列化。
SerializedCache 提供了将 value对象 序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java对象 进行序列化,井将序列化后的 byte[]数组 作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[]数组 反序列化成 Java对象。不使用 SerializedCache装饰器 进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而使用 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java原生序列化。
## 2 CacheKey
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key进行比较MyBatis 中因为涉及动态 SQL 等 多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey 类来表示缓存项的 key在一个 CacheKey 对象中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象是否相同。
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key 进行比较MyBatis 中因为涉及 动态SQL 等多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey类 来表示缓存项的 key在一个 CacheKey对象 中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey对象 是否相同。
```java
public class CacheKey implements Cloneable, Serializable {
@ -548,16 +549,16 @@ public class CacheKey implements Cloneable, Serializable {
}
/**
* CacheKey重写了equals()和hashCode()方法,这两个方法使用上面介绍
* 的count、checksum、hashcode、updateList比较两个CacheKey对象是否相同
* CacheKey重写了 equals() hashCode()方法,这两个方法使用上面介绍
* 的 count、checksum、hashcode、updateList 比较两个 CacheKey对象 是否相同
*/
@Override
public boolean equals(Object object) {
// 如果为同一对象直接返回true
// 如果为同一对象,直接返回 true
if (this == object) {
return true;
}
// 如果object都不是CacheKey类型直接返回false
// 如果 object 都不是 CacheKey类型直接返回 false
if (!(object instanceof CacheKey)) {
return false;
}
@ -565,7 +566,7 @@ public class CacheKey implements Cloneable, Serializable {
// 类型转换一下
final CacheKey cacheKey = (CacheKey) object;
// 依次比较hashcode、checksum、count如果不等直接返回false
// 依次比较 hashcode、checksum、count如果不等直接返回 false
if (hashcode != cacheKey.hashcode) {
return false;
}
@ -576,7 +577,7 @@ public class CacheKey implements Cloneable, Serializable {
return false;
}
// 比较updateList中的元素是否相同不同直接返回false
// 比较 updateList 中的元素是否相同,不同直接返回 false
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
@ -612,6 +613,6 @@ public class CacheKey implements Cloneable, Serializable {
```
## 3 小结
至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java 反射机制的封装;然后分析了类型转换 TypeHandler 组件,了解了 MyBatis 如何实现数据在 Java 类型与 JDBC 类型之间的转换。
至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java反射机制的封装然后分析了类型转换 TypeHandler组件了解了 MyBatis 如何实现数据在 Java类型 与 JDBC类型 之间的转换。
之后分析了MyBatis 提供的 DataSource 模块的实现和原理,深入解析了 MyBatis 自带的连接池PooledDataSource 的详细实现;后面紧接着介绍了 Transaction 模块的功能。然后分析了 binding 模块如何将 Mapper 接口与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache 接口以及多个实现类的具体实现它们是Mybatis中一级缓存和二级缓存的基础。
之后分析了 MyBatis 提供的 DataSource模块 的实现和原理,深入解析了 MyBatis 自带的连接池 PooledDataSource 的详细实现;后面紧接着介绍了 Transaction模块 的功能。然后分析了 binding模块 如何将 Mapper接口 与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache接口 以及多个实现类的具体实现,它们是 Mybatis 中一级缓存和二级缓存的基础。

@ -1,6 +1,6 @@
spring框架的IoC容器初始化一样mybatis也会通过定位、解析相应的配置文件完成自己的初始化。mybatis的配置文件主要有mybatis-config.xml核心配置文件及一系列映射配置文件另外mybatis也会根据注解进行配置。
Spring框架 的 IoC容器初始化 一样Mybatis 也会通过定位、解析相应的配置文件完成自己的初始化。Mybatis 的配置文件主要有 mybatis-config.xml核心配置文件 及一系列映射配置文件另外Mybatis 也会根据注解进行配置。
## 1 BaseBuilder
mybatis初始化的主要内容是加载并解析mybatis-config.xml配置文件、映射配置文件以及相关的注解信息。mybatis的初始化入口是SqlSessionFactoryBuilder的build()方法。
Mybatis初始化 的主要内容是加载并解析 mybatis-config.xml配置文件、映射配置文件以及相关的注解信息。Mybatis 的初始化入口是 SqlSessionFactoryBuilder 的 build()方法。
```java
public class SqlSessionFactoryBuilder {
@ -17,15 +17,15 @@ public class SqlSessionFactoryBuilder {
}
/**
* build方法的主要实现
* build()方法 的主要实现
*/
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// SqlSessionFactory会创建XMLConfigBuilder对象来解析mybatis-config.xml配置文件
// XMLConfigBuilder继承自BaseBuilder抽象类顾名思义这一系的类使用了 建造者设计模式
// SqlSessionFactory 会创建 XMLConfigBuilder对象 来解析 mybatis-config.xml配置文件
// XMLConfigBuilder 继承自 BaseBuilder抽象类顾名思义这一系的类使用了 建造者设计模式
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件的内容 到Configuration对象根据到Configuration对象
// 创建DefaultSqlSessionFactory对象然后返回
// 解析配置文件的内容 到 Configuration对象根据 Configuration对象
// 创建 DefaultSqlSessionFactory对象然后返回
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
@ -44,17 +44,17 @@ public class SqlSessionFactoryBuilder {
return new DefaultSqlSessionFactory(config);
}
```
BaseBuilder中的核心字段如下
BaseBuilder 中的核心字段如下:
```java
public abstract class BaseBuilder {
// 保存了mybatis的几乎所以核心配置信息,全局唯一
// 保存了 Mybatis 的几乎所以核心配置信息,全局唯一
protected final Configuration configuration;
// 在mybatis-config.xml中可以通过<typeAliases>标签定义别名
// 在 mybatis-config.xml 中可以通过 <typeAliases>标签 定义别名
protected final TypeAliasRegistry typeAliasRegistry;
// 在mybatis-config.xml中可以通过<typeHandlers>标签添加自定义TypeHandler
// TypeHandler用于完成JDBC数据类型与Java类型的相互转换所有的TypeHandler
// 都保存在typeHandlerRegistry中
// 在 mybatis-config.xml 中可以通过 <typeHandlers>标签 添加 自定义TypeHandler
// TypeHandler 用于完成 JDBC数据类型 Java类型 的相互转换,所有的 TypeHandler
// 都保存在 typeHandlerRegistry
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
@ -64,38 +64,38 @@ public abstract class BaseBuilder {
}
}
```
BaseBuilder中的typeAliasRegistry和typeHandlerRegistry字段均来自于configuration通过BaseBuilder的构造方法可以看到详细内容。
BaseBuilder 中的 typeAliasRegistry typeHandlerRegistry字段 均来自于 configuration通过 BaseBuilder 的构造方法可以看到详细内容。
## 2 XMLConfigBuilder
XMLConfigBuilder是BaseBuilder的众多子类之一主要负责解析mybatis-config.xml配置文件。它通过调用parseConfiguration()方法实现整个解析过程其中mybatis-config.xml配置文件中的每个节点都被封装成了一个个相应的解析方法parseConfiguration()方法只是依次调用了这些解析方法而已。
XMLConfigBuilder BaseBuilder 的众多子类之一,主要负责解析 mybatis-config.xml配置文件。它通过调用 parseConfiguration()方法 实现整个解析过程其中mybatis-config.xml配置文件 中的每个节点都被封装成了一个个相应的解析方法parseConfiguration()方法 只是依次调用了这些解析方法而已。
```java
public class XMLConfigBuilder extends BaseBuilder {
// 标记是否解析过mybatis-config.xml文件
// 标记是否解析过 mybatis-config.xml文件
private boolean parsed;
// 用于解析mybatis-config.xml的解析器
// 用于解析 mybatis-config.xml 的解析器
private final XPathParser parser;
// 标识<environment>配置的名称,默认读取<environment>标签的default属性
// 标识 <environment>配置 的名称,默认读取 <environment>标签 default属性
private String environment;
// 创建并缓存Reflector对象
// 创建并缓存 Reflector对象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
/**
* 解析的入口调用了parseConfiguration()进行后续的解析
* 解析的入口,调用了 parseConfiguration() 进行后续的解析
*/
public Configuration parse() {
// parsed标志位的处理
// parsed标志位 的处理
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
// 在 mybatis-config.xml配置文件 中查找 <configuration>节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 根据root.evalNode("properties")中的值就可以知道具体是解析哪个标签的方法咯
// 根据 root.evalNode("properties") 中的值就可以知道具体是解析哪个标签的方法咯
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
@ -115,19 +115,19 @@ public class XMLConfigBuilder extends BaseBuilder {
}
}
```
mybatis中的标签很多,所以相对应的解析方法也很多,这里挑几个比较重要的标签进行分析。
Mybatis 中的标签很多,所以相对应的解析方法也很多,这里挑几个比较重要的标签进行分析。
### 2.1 解析&lt;typeHandlers&gt;标签
```java
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
// 处理<typeHandlers>下的所有子标签
// 处理 <typeHandlers> 下的所有子标签
for (XNode child : parent.getChildren()) {
// 处理<package>标签
// 处理 <package> 标签
if ("package".equals(child.getName())) {
// 获取指定的包名
String typeHandlerPackage = child.getStringAttribute("name");
// 通过typeHandlerRegistry的register(packageName)方法
// 扫描指定包中的所有TypeHandler类并进行注册
// 通过 typeHandlerRegistry register(packageName)方法
// 扫描指定包中的所有 TypeHandler类并进行注册
typeHandlerRegistry.register(typeHandlerPackage);
} else {
// Java数据类型
@ -156,29 +156,29 @@ mybatis中的标签很多所以相对应的解析方法也很多这里挑
### 2.2 解析&lt;environments&gt;标签
```java
/**
* mybatis可以配置多个<environment>环境,分别用于开发、测试及生产等,
* 但每个SqlSessionFactory实例只能选择其一
* Mybatis 可以配置多个 <environment>环境,分别用于开发、测试及生产等,
* 但每个 SqlSessionFactory实例 只能选择其一
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 如果未指定XMLConfigBuilder的environment字段则使用default属性指定的<environment>环境
// 如果未指定 XMLConfigBuilder environment字段则使用 default属性 指定的 <environment>环境
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 遍历<environment>节点
// 遍历 <environment>节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 实例化TransactionFactory
// 实例化 TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建DataSourceFactory和DataSource
// 创建 DataSourceFactory DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 创建的Environment对象中封装了上面的TransactionFactory对象和DataSource对象
// 创建的 Environment对象 中封装了上面的 TransactionFactory对象 DataSource对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 为configuration注入environment属性值
// 为 configuration 注入 environment属性值
configuration.setEnvironment(environmentBuilder.build());
}
}
@ -186,38 +186,38 @@ mybatis中的标签很多所以相对应的解析方法也很多这里挑
}
```
### 2.3 解析&lt;databaseIdProvider&gt;标签
mybatis不像hibernate那样通过hql的方式直接帮助开发人员屏蔽不同数据库产品在sql语法上的差异针对不同的数据库产品mybatis往往要编写不同的sql语句。但在mybatis-config.xml配置文件中可以通过&lt;databaseIdProvider&gt;定义所有支持的数据库产品的databaseId然后在映射配置文件中定义sql语句节点时通过databaseId指定该sql语句应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。
Mybatis 不像 Hibernate 那样,通过 HQL 的方式直接帮助开发人员屏蔽不同数据库产品在 sql语法 上的差异,针对不同的数据库产品, Mybatis 往往要编写不同的 sql语句。但在 mybatis-config.xml配置文件 中,可以通过 &lt;databaseIdProvider&gt; 定义所有支持的数据库产品的 databaseId然后在映射配置文件中定义 sql语句节点 时,通过 databaseId 指定该 sql语句 应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。
mybatis初始化时会根据前面解析到的DataSource来确认当前使用的数据库产品然后在解析映射文件时加载不带databaseId属性的sql语句 及 带有databaseId属性的sql语句其中带有databaseId属性的sql语句优先级更高会被优先选中。
Mybatis 初始化时,会根据前面解析到的 DataSource 来确认当前使用的数据库产品,然后在解析映射文件时,加载不带 databaseId属性 sql语句 及带有 databaseId属性 sql语句其中带有 databaseId属性 sql语句 优先级更高,会被优先选中。
```java
/**
* 解析<databaseIdProvider>节点并创建指定的DatabaseIdProvider对象
* 该对象会返回databaseId的值mybatis会根据databaseId选择对应的sql语句去执行
* 解析 <databaseIdProvider>节点,并创建指定的 DatabaseIdProvider对象
* 该对象会返回 databaseId的值Mybatis 会根据 databaseId 选择对应的 sql语句 去执行
*/
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// 为了保证兼容性修改type取值
// 为了保证兼容性,修改 type取值
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
// 解析相关配置信息
Properties properties = context.getChildrenAsProperties();
// 创建DatabaseIdProvider对象
// 创建 DatabaseIdProvider对象
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
// 配置DatabaseIdProvider完成初始化
// 配置 DatabaseIdProvider完成初始化
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
// 根据前面解析到的DataSource获取databaseId并记录到configuration的configuration属性上
// 根据前面解析到的 DataSource 获取 databaseId并记录到 configuration configuration属性
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
```
mybatis提供了DatabaseIdProvider接口该接口的核心方法为getDatabaseId(DataSource dataSource)主要根据dataSource查找对应的databaseId并返回。该接口的主要实现类为VendorDatabaseIdProvider。
Mybatis 提供了 DatabaseIdProvider接口该接口的核心方法为 getDatabaseId(DataSource dataSource),主要根据 dataSource 查找对应的 databaseId 并返回。该接口的主要实现类为 VendorDatabaseIdProvider。
```java
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
@ -247,20 +247,20 @@ public class VendorDatabaseIdProvider implements DatabaseIdProvider {
// 解析到数据库产品名
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
// 根据<databaseIdProvider>子节点配置的数据库产品 和 databaseId之间的对应关系
// 确定最终使用的databaseId
// 根据 <databaseIdProvider>子节点 配置的数据库产品和 databaseId 之间的对应关系,
// 确定最终使用的 databaseId
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// 没有合适的databaseId则返回null
// 没有合适的 databaseId则返回 null
return null;
}
return productName;
}
// 根据dataSource获取 数据库产品名的具体实现
// 根据 dataSource 获取 数据库产品名的具体实现
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
@ -280,31 +280,31 @@ public class VendorDatabaseIdProvider implements DatabaseIdProvider {
}
```
### 2.4 解析&lt;mappers&gt;标签
mybatis初始化时除了加载mybatis-config.xml文件还会加载全部的映射配置文件mybatis-config.xml文件的&lt;mapper&gt;节点会告诉mybatis去哪里查找映射配置文件,及使用了配置注解标识的接口。
Mybatis 初始化时,除了加载 mybatis-config.xml文件还会加载全部的映射配置文件mybatis-config.xml 文件的 &lt;mapper&gt;节点 会告诉 Mybatis 去哪里查找映射配置文件,及使用了配置注解标识的接口。
```java
/**
* 解析<mappers>节点本方法会创建XMLMapperBuilder对象加载映射文件如果映射配置文件存在
* 相应的Mapper接口也会加载相应的Mapper接口解析其中的注解 并完成向MapperRegistry的注册
* 解析 <mappers>节点,本方法会创建 XMLMapperBuilder对象 加载映射文件,如果映射配置文件存在
* 相应的 Mapper接口也会加载相应的 Mapper接口解析其中的注解 并完成向 MapperRegistry 的注册
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 处理<mappers>的子节点
// 处理 <mappers> 的子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 获取<package>子节点中的包名
// 获取 <package>子节点 中的包名
String mapperPackage = child.getStringAttribute("name");
// 扫描指定的包目录然后向MapperRegistry注册Mapper接口
// 扫描指定的包目录,然后向 MapperRegistry 注册 Mapper接口
configuration.addMappers(mapperPackage);
} else {
// 获取<mapper>节点的resource、url、mapperClass属性这三个属性互斥只能有一个不为空
// mybatis提供了通过包名、映射文件路径、类全名、URL四种方式引入映射器。
// 映射器由一个接口和一个XML配置文件组成XML文件中定义了一个命名空间namespace
// 获取 <mapper>节点 resource、url、mapperClass属性这三个属性互斥只能有一个不为空
// Mybatis 提供了通过包名、映射文件路径、类全名、URL 四种方式引入映射器。
// 映射器由一个接口和一个 XML配置文件 组成XML文件 中定义了一个 命名空间namespace
// 它的值就是接口对应的全路径。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 如果<mapper>节点指定了resource或是url属性则创建XMLMapperBuilder对象解析
// resource或是url属性指定的Mapper配置文件
// 如果 <mapper>节点 指定了 resource 或是 url属性则创建 XMLMapperBuilder对象 解析
// resource 或是 url属性 指定的 Mapper配置文件
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
@ -316,7 +316,7 @@ mybatis初始化时除了加载mybatis-config.xml文件还会加载全部
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 如果<mapper>节点指定了class属性则向MapperRegistry注册该Mapper接口
// 如果 <mapper>节点 指定了 class属性则向 MapperRegistry 注册 该Mapper接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
@ -328,38 +328,38 @@ mybatis初始化时除了加载mybatis-config.xml文件还会加载全部
}
```
## 3 XMLMapperBuilder
和XMLConfigBuilder一样XMLMapperBuilder也继承了BaseBuilder其主要负责解析映射配置文件其解析配置文件的入口方法也是parse()另外XMLMapperBuilder也将各个节点的解析过程拆分成了一个个小方法然后由configurationElement()方法统一调用。
XMLConfigBuilder 一样XMLMapperBuilder 也继承了 BaseBuilder其主要负责解析映射配置文件其解析配置文件的入口方法也是 parse()另外XMLMapperBuilder 也将各个节点的解析过程拆分成了一个个小方法,然后由 configurationElement()方法 统一调用。
```java
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
// 是否已经加载过该配置文件
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper>节点
// 解析 <mapper>节点
configurationElement(parser.evalNode("/mapper"));
// 将resource添加到configuration的loadedResources属性中
// 该属性是一个HashSet<String>类型的集合,其中记录了已经加载过的映射文件
// 将 resource 添加到 configuration loadedResources属性 中,
// 该属性是一个 HashSet<String>类型的集合,其中记录了已经加载过的映射文件
configuration.addLoadedResource(resource);
// 注册Mapper接口
// 注册 Mapper接口
bindMapperForNamespace();
}
// 处理configurationElement()方法中解析失败的<resultMap>节点
// 处理 configurationElement()方法 中解析失败的 <resultMap>节点
parsePendingResultMaps();
// 处理configurationElement()方法中解析失败的<cacheRef>节点
// 处理 configurationElement()方法 中解析失败的 <cacheRef>节点
parsePendingCacheRefs();
// 处理configurationElement()方法中解析失败的<statement>节点
// 处理 configurationElement()方法 中解析失败的 <statement>节点
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取<mapper>节点的namespace属性
// 获取 <mapper>节点 namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 使用MapperBuilderAssistant对象的currentNamespace属性 记录namespace命名空间
// 使用 MapperBuilderAssistant对象 currentNamespace属性 记录 namespace命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>节点,后面的解析方法 也都见名知意
// 解析 <cache-ref>节点,后面的解析方法 也都见名知意
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
@ -372,37 +372,37 @@ public class XMLMapperBuilder extends BaseBuilder {
}
}
```
XMLMapperBuilder也根据配置文件进行了一系列节点解析我们着重分析一下比较重要且常见的&lt;resultMap&gt;节点和&lt;sql&gt;节点
XMLMapperBuilder 也根据配置文件进行了一系列节点解析,我们着重分析一下比较重要且常见的 &lt;resultMap&gt;节点 &lt;sql&gt;节点
### 3.1 解析&lt;resultMap&gt;节点
select语句查询得到的结果是一张二维表水平方向上是一个个字段垂直方向上是一条条记录。而Java是面向对象的程序设计语言对象是根据类的定义创建的类之间的引用关系可以认为是嵌套结构。JDBC编程中为了将结果集中的数据映射成VO对象我们需要自己写代码从结果集中获取数据然后将数据封装成对应的VO对象并设置好对象之间的关系这种ORM的过程中存在大量重复的代码。
select语句 查询得到的结果是一张二维表,水平方向上是一个个字段,垂直方向上是一条条记录。而 Java 是面向对象的程序设计语言对象是根据类的定义创建的类之间的引用关系可以认为是嵌套结构。JDBC编程 中,为了将结果集中的数据映射成 VO对象我们需要自己写代码从结果集中获取数据然后将数据封装成对应的 VO对象并设置好对象之间的关系这种 ORM 的过程中存在大量重复的代码。
mybatis通过&lt;resultMap&gt;节点定义了ORM规则可以满足大部分的映射需求减少重复代码提高开发效率。
Mybatis 通过 &lt;resultMap&gt;节点 定义了 ORM规则可以满足大部分的映射需求减少重复代码提高开发效率。
在分析&lt;resultMap&gt;节点的解析过程之前先看一下该过程使用的数据结构。每个ResultMapping对象记录了结果集中的一列与JavaBean中一个属性之间的映射关系。&lt;resultMap&gt;节点下除了&lt;discriminator&gt;子节点的其它子节点 都会被解析成对应的ResultMapping对象。
在分析 &lt;resultMap&gt;节点 的解析过程之前,先看一下该过程使用的数据结构。每个 ResultMapping对象 记录了结果集中的一列与 JavaBean 中一个属性之间的映射关系。&lt;resultMap&gt;节点 下除了 &lt;discriminator&gt;子节点 的其它子节点,都会被解析成对应的 ResultMapping对象。
```java
public class ResultMapping {
private Configuration configuration;
// 对应节点的property属性表示 该列进行映射的属性
// 对应节点的 property属性表示 该列进行映射的属性
private String property;
// 对应节点的column属性表示 从数据库中得到的列名或列名的别名
// 对应节点的 column属性表示 从数据库中得到的列名或列名的别名
private String column;
// 表示 一个JavaBean的完全限定名或一个类型别名
// 表示 一个 JavaBean 的完全限定名,或一个类型别名
private Class<?> javaType;
// 进行映射列的JDBC类型
// 进行映射列的 JDBC类型
private JdbcType jdbcType;
// 类型处理器
private TypeHandler<?> typeHandler;
// 该属性通过id引用了另一个<resultMap>节点,它负责将结果集中的一部分列映射成
// 它所关联的结果对象。这样我们就可以通过join方式进行关联查询然后直接映射成
// 该属性通过 id 引用了另一个 <resultMap>节点,它负责将结果集中的一部分列映射成
// 它所关联的结果对象。这样我们就可以通过 join方式 进行关联查询,然后直接映射成
// 多个对象,并同时设置这些对象之间的组合关系(nested嵌套的)
private String nestedResultMapId;
// 该属性通过id引用了另一个<select>节点它会把指定的列值传入select属性指定的
// select语句 作为参数进行查询。使用该属性可能会导致ORM中的N+1问题请谨慎使用
// 该属性通过 id 引用了另一个 <select>节点,它会把指定的列值传入 select属性 指定的
// select语句 作为参数进行查询。使用该属性可能会导致 ORM 中的 N+1问题请谨慎使用
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;
// 处理后的标志共有两个id和constructor
// 处理后的标志共有两个id constructor
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
@ -411,38 +411,38 @@ public class ResultMapping {
private boolean lazy;
}
```
另一个比较重要的类是ResultMap每个&lt;resultMap&gt;节点都会被解析成一个ResultMap对象其中每个节点所定义的映射关系则使用ResultMapping对象表示。
另一个比较重要的类是 ResultMap每个 &lt;resultMap&gt;节点 都会被解析成一个 ResultMap对象其中每个节点所定义的映射关系则使用 ResultMapping对象 表示。
```java
public class ResultMap {
private Configuration configuration;
// 这些属性一一对应了<resultMap>中的属性
// 这些属性一一对应了 <resultMap> 中的属性
private String id;
private Class<?> type;
// 记录了除<discriminator>节点之外的其它映射关系(即ResultMapping对象集合)
// 记录了除 <discriminator>节点 之外的其它映射关系(即ResultMapping对象集合)
private List<ResultMapping> resultMappings;
// 记录了映射关系中带有ID标志的映射关系<id>节点和<constructor>节点的<idArg>子节点
// 记录了映射关系中带有 ID标志 的映射关系,如:<id>节点 <constructor>节点 <idArg>子节点
private List<ResultMapping> idResultMappings;
// 记录了映射关系中带有Constructor标志的映射关系<constructor>所有子元素
// 记录了映射关系中带有 Constructor标志 的映射关系,如:<constructor>所有子元素
private List<ResultMapping> constructorResultMappings;
// 记录了映射关系中不带有Constructor标志的映射关系
// 记录了映射关系中不带有 Constructor标志 的映射关系
private List<ResultMapping> propertyResultMappings;
// 记录了所有映射关系中涉及的column属性的集合
// 记录了所有映射关系中涉及的 column属性 的集合
private Set<String> mappedColumns;
// 记录了所有映射关系中涉及的property属性的集合
// 记录了所有映射关系中涉及的 property属性 的集合
private Set<String> mappedProperties;
// 鉴别器,对应<discriminator>节点
// 鉴别器,对应 <discriminator>节点
private Discriminator discriminator;
// 是否含有嵌套的结果映射如果某个映射关系中存在resultMap属性
// 且不存在resultSet属性则为true
// 是否含有嵌套的结果映射,如果某个映射关系中存在 resultMap属性
// 且不存在 resultSet属性则为true
private boolean hasNestedResultMaps;
// 是否含有嵌套查询如果某个属性映射存在select属性则为true
// 是否含有嵌套查询,如果某个属性映射存在 select属性则为true
private boolean hasNestedQueries;
// 是否开启自动映射
private Boolean autoMapping;
}
```
了解了ResultMapping 和ResultMap 记录的信息之后,下面开始介绍&lt;resultMap&gt;节点的解析过程。在XMLMapperBuilder中通过resultMapElements()方法解析映射配置文件中的全部&lt;resultMap&gt;节点,该方法会循环调用resultMapElement()方法处理每个resultMap节点。
了解了 ResultMapping 和 ResultMap 记录的信息之后,下面开始介绍 &lt;resultMap&gt;节点 的解析过程。在 XMLMapperBuilder 中通过 resultMapElements()方法 解析映射配置文件中的全部 &lt;resultMap&gt;节点,该方法会循环调用 resultMapElement()方法 处理每个 &lt;resultMap&gt; 节点。
```java
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
@ -450,42 +450,42 @@ public class ResultMap {
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// <resultMap>的id属性默认值会拼装所有父节点的id 或value或property属性值
// <resultMap> id属性默认值会拼装所有父节点的 id 或 value property属性值
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// <resultMap>的type属性表示结果集将被映射成type指定类型的对象
// <resultMap> type属性表示结果集将被映射成 type 指定类型的对象
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 该属性指定了该<resultMap>节点的继承关系
// 该属性指定了该 <resultMap>节点 的继承关系
String extend = resultMapNode.getStringAttribute("extends");
// 为true则启动自动映射功能该功能会自动查找与列明相同的属性名并调用setter方法
// 为false则需要在<resultMap>节点内注明映射关系才会调用对应的setter方法
// 为 true 则启动自动映射功能,该功能会自动查找与列明相同的属性名,并调用 setter方法
// 为 false则需要在 <resultMap>节点 内注明映射关系才会调用对应的 setter方法
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 解析type类型
// 解析 type类型
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// 该集合用来记录解析结果
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
// 获取并处理<resultMap>的子节点
// 获取并处理 <resultMap> 的子节点
List<XNode> resultChildren = resultMapNode.getChildren();
// child单数形式children复数形式
// child 单数形式children 复数形式
for (XNode resultChild : resultChildren) {
// 处理<constructor>节点
// 处理 <constructor>节点
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
// 处理<discriminator>节点
// 处理 <discriminator>节点
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 处理<id>,<result>,<association>,<collection>等节点
// 处理 <id>, <result>, <association>, <collection> 等节点
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 创建ResultMapping对象并添加到resultMappings集合
// 创建 ResultMapping对象并添加到 resultMappings集合
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
@ -498,10 +498,10 @@ public class ResultMap {
}
}
```
从上面的代码我们可以看到,mybatis从&lt;resultMap&gt;节点获取到id属性和type属性值之后就会通过XMLMapperBuilder的buildResultMappingFromContext()方法为&lt;result&gt;节点创建对应的ResultMapping 对象。
从上面的代码我们可以看到,Mybatis 从 &lt;resultMap&gt;节点 获取到 id属性 和 type属性值 之后,就会通过 XMLMapperBuilder 的 buildResultMappingFromContext()方法 为 &lt;result&gt;节点 创建对应的 ResultMapping对象。
```java
/**
* 根据上下文环境构建ResultMapping
* 根据上下文环境构建 ResultMapping
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
// 获取各个节点的属性,见文知意
@ -527,30 +527,30 @@ public class ResultMap {
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 创建ResultMapping对象并返回
// 创建 ResultMapping对象 并返回
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
```
得到ResultMapping对象集合之后会调用ResultMapResolver的resolve()方法该方法会调用MapperBuilderAssistant的addResultMap()方法创建ResultMap对象并将ResultMap对象添加到Configuration的resultMaps集合中保存。
得到 ResultMapping对象集合 之后,会调用 ResultMapResolver resolve()方法,该方法会调用 MapperBuilderAssistant addResultMap()方法 创建 ResultMap对象并将 ResultMap对象 添加到 Configuration resultMaps集合 中保存。
```java
public class MapperBuilderAssistant extends BaseBuilder {
public ResultMap addResultMap(String id, Class<?> type, String extend,
Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
// ResultMap的完整id是"namespace.id"的格式
// ResultMap 完整id "namespace.id" 的格式
id = applyCurrentNamespace(id, false);
// 获取 父ResultMap的完整id
// 获取 父ResultMap 完整id
extend = applyCurrentNamespace(extend, true);
// 针对extend属性进行的处理
// 针对 extend属性 进行的处理
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
// 父ResultMap对象
ResultMap resultMap = configuration.getResultMap(extend);
// 父ResultMap对象的ResultMapping集合
// 父ResultMap对象 ResultMapping集合
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
// 删除需要覆盖的ResultMapping集合
// 删除需要覆盖的 ResultMapping集合
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
@ -568,7 +568,7 @@ public class MapperBuilderAssistant extends BaseBuilder {
}
}
}
// 添加需要被继承下来的ResultMapping集合
// 添加需要被继承下来的 ResultMapping集合
resultMappings.addAll(extendedResultMappings);
}
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
@ -580,7 +580,7 @@ public class MapperBuilderAssistant extends BaseBuilder {
}
```
### 3.2 解析&lt;sql&gt;节点
在映射配置文件中,可以使用&lt;sql&gt;节点定义可重用的SQL语句片段当需要重用&lt;sql&gt;节点中定义的SQL语句片段时只需要使用&lt;include&gt;节点引入相应的片段即可这样在编写SQL语句以及维护这些SQL语句时都会比较方便。XMLMapperBuilder的sqlElement()方法负责解析映射配置文件中定义的全部&lt;sql&gt;节点。
在映射配置文件中,可以使用 &lt;sql&gt;节点 定义可重用的 SQL语句片段当需要重用 &lt;sql&gt;节点 中定义的 SQL语句片段 时,只需要使用 &lt;include&gt;节点 引入相应的片段即可,这样,在编写 SQL语句 以及维护这些 SQL语句 都会比较方便。XMLMapperBuilder sqlElement()方法 负责解析映射配置文件中定义的 全部&lt;sql&gt;节点。
```java
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
@ -590,27 +590,27 @@ public class MapperBuilderAssistant extends BaseBuilder {
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
// 遍历<sql>节点
// 遍历 <sql>节点
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// 为id添加命名空间
// 为 id 添加命名空间
id = builderAssistant.applyCurrentNamespace(id, false);
// 检测<sql>的databaseId与当前Configuration中记录的databaseId是否一致
// 检测 <sql> databaseId 与当前 Configuration 中记录的 databaseId 是否一致
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 记录到sqlFragments(Map<String, XNode>)中保存
// 记录到 sqlFragments(Map<String, XNode>) 中保存
sqlFragments.put(id, context);
}
}
}
```
## 4 XMLStatementBuilder
这一部分看的不是很懂,暂时保留,日后深入理解了再写。
## 5 绑定Mapper接口
通过之前对binding模块的解析可知每个映射配置文件的命名空间可以绑定一个Mapper接口并注册到MapperRegistry中。XMLMapperBuilder的bindMapperForNamespace()方法中完成了映射配置文件与对应Mapper 接
口的绑定。
通过之前对 binding模块 的解析可知,每个映射配置文件的命名空间可以绑定一个 Mapper接口并注册到 MapperRegistry中。XMLMapperBuilder 的 bindMapperForNamespace()方法 中,完成了映射配置文件与对应 Mapper接口 的绑定。
```java
public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
@ -625,12 +625,12 @@ public class XMLMapperBuilder extends BaseBuilder {
//ignore, bound type is not required
}
if (boundType != null) {
// 是否已加载boundType接口
// 是否已加载 boundType接口
if (!configuration.hasMapper(boundType)) {
// 追加个"namespace:"的前缀并添加到Configuration的loadedResources集合中
// 追加个 "namespace:" 的前缀,并添加到 Configuration loadedResources集合
configuration.addLoadedResource("namespace:" + namespace);
// 添加到Configuration的mapperRegistry集合中另外往这个方法栈的更深处看 会发现
// 其创建了MapperAnnotationBuilder对象并调用了该对象的parse()方法解析Mapper接口
// 添加到 Configuration的mapperRegistry集合 中,另外,往这个方法栈的更深处看 会发现
// 其创建了 MapperAnnotationBuilder对象并调用了该对象的 parse()方法 解析 Mapper接口
configuration.addMapper(boundType);
}
}
@ -647,7 +647,7 @@ public class MapperRegistry {
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// 解析Mapper接口type中的信息
// 解析 Mapper接口 type 中的信息
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
@ -665,34 +665,34 @@ public class MapperAnnotationBuilder {
String resource = type.toString();
// 是否已经加载过该接口
if (!configuration.isResourceLoaded(resource)) {
// 检查是否加载过该接口对应的映射文件如果未加载则创建XMLMapperBuilder对象
// 检查是否加载过该接口对应的映射文件,如果未加载,则创建 XMLMapperBuilder对象
// 解析对应的映射文件,该过程就是前面介绍的映射配置文件解析过程
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析@CacheNamespace注解
// 解析 @CacheNamespace注解
parseCache();
// 解析@CacheNamespaceRef注解
// 解析 @CacheNamespaceRef注解
parseCacheRef();
// type接口的所有方法
// type接口 的所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
if (!method.isBridge()) {
// 解析SelectKey、ResultMap等注解并创建MappedStatement对象
// 解析 SelectKey、ResultMap 等注解,并创建 MappedStatement对象
parseStatement(method);
}
} catch (IncompleteElementException e) {
// 如果解析过程出现IncompleteElementException异常可能是因为引用了
// 如果解析过程出现 IncompleteElementException异常可能是因为引用了
// 未解析的注解,这里将出现异常的方法记录下来,后面提供补偿机制,重新进行解析
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 遍历configuration中的incompleteMethods集合集合中记录了未解析的方法
// 遍历 configuration 中的 incompleteMethods集合集合中记录了未解析的方法
// 重新调用这些方法进行解析
parsePendingMethods();
}
}
```
另外在MapperAnnotationBuilder的parse()方法中解析的注解都能在映射配置文件中找到与之对应的XML节点且两者的解析过程也非常相似。
另外,在 MapperAnnotationBuilder parse()方法 中解析的注解,都能在映射配置文件中找到与之对应的 XML节点且两者的解析过程也非常相似。

@ -0,0 +1,73 @@
本博文用于重点分析 Netty 的逻辑架构及关键的架构质量属性,希望有助于大家从 Netty 的架构设计中汲取营养,设计出高性能、高可靠
性和可扩展的程序。
## Netty的三层架构设计
Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。
![avatar](/images/Netty/Netty逻辑架构图.png)
### 通信调度层 Reactor
它由一系列辅助类完成,包括 Reactor线程 NioEventLoop 及其父类NioSocketChannel / NioServerSocketChannel 及其父类Buffer组件Unsafe组件 等。该层的主要职责就是**监听网络的读写和连接操作**,负责**将网络层的数据读取到内存缓冲区**,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到 PipeLine 中,由 PipeLine 管理的责任链来进行后续的处理。
### 责任链层 Pipeline
它负责上述的各种网络事件 在责任链中的有序传播,同时负责动态地编排责任链。责任链可以选择监听和处理自己关心的事件,它可以拦截处理事件,以及向前向后传播事件。不同应用的 Handler节点 的功能也不同,通常情况下,往往会开发 编解码Hanlder 用于消息的编解码,可以将外部的协议消息转换成 内部的POJO对象这样上层业务则只需要关心处理业务逻辑即可不需要感知底层的协议差异和线程模型差异实现了架构层面的分层隔离。
### 业务逻辑编排层 Service ChannelHandler
业务逻辑编排层通常有两类一类是纯粹的业务逻辑编排还有一类是其他的应用层协议插件用于特定协议相关的会话和链路管理。例如CMPP协议用于管理和中国移动短信系统的对接。
架构的不同层面,需要关心和处理的对象都不同,通常情况下,对于业务开发者,只需要关心责任链的拦截和 业务Handler 的编排。因为应用层协议栈往往是开发一次,到处运行,所以实际上对于业务开发者来说,只需要关心服务层的业务逻辑开发即可。各种应用协议以插件的形式提供,只有协议开发人员需要关注协议插件,对于其他业务开发人员来说,只需关心业务逻辑定制。这种分层的架构设计理念实现了 NIO框架 各层之间的解耦,便于上层业务协议栈的开发和业务逻辑的定制。
正是由于 Netty 的分层架构设计非常合理,基于 Netty 的各种应用服务器和协议栈开发才能够如雨后春笋般得到快速发展。
## 关键的架构质量属性
### 性能
影响最终产品的性能因素非常多,其中软件因素如下。
- 架构不合理导致的性能问题;
- 编码实现不合理导致的性能问题,例如,锁没用好导致的性能瓶颈。
硬件因素如下。
- 服务器硬件配置太低导致的性能问题;
- 带宽、磁盘的 IOPS 等限制导致的 IO操作 性能差;
- 测试环境被共用导致被测试的软件产品受到影响。
尽管影响产品性能的因素非常多,但是架构的性能模型合理与否对性能的影响非常大。如果一个产品的架构设计得不好,无论开发如何努力,都很难开发出一个高性能、高可用的软件产品。
“性能是设计出来的,而不是测试出来的”。下面我们看看 Netty 的架构设计是如何实现高性能的。
1. 采用非阻塞的 NIO类库基于 Reactor 模式实现,解决了传统 同步阻塞IO模式 下一个服务端无法平滑地处理线性增长的客户端的问题。
2. TCP 接收和发送缓冲区**使用直接内存代替堆内存,避免了内存复制**,提升了 IO 读取和写入的性能。
3. 支持通过内存池的方式循环利用 ByteBuffer避免了频繁创建和销毁 ByteBuffer 带来的性能损耗。
4. 可配置的 IO线程数、TCP参数 等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。
5. 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。
6. 合理地使用线程安全容器、原子类等,提升系统的并发处理能力。
7. 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的 CPU资源消耗问题。
8. 通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管理降低了 GC 的频率,减少了频繁 GC 带来的延时和 CPU损耗。
### 可靠性
作为一个高性能的异步通信框架,架构的可靠性是大家选择的另一个重要依据。下面我们看一下 Netty架构 的可靠性设计。
**1、链路有效性检测**
由于长连接不需要每次发送消息都创建链路,也不需要在消息交互完成时关闭链路,因此相对于短连接性能更高。对于长连接,一旦链路建立成功便一直维系双方之间的链路,直到系统退出。
为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用周期性心跳的原因是:在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙 Hang住或者遭遇网络闪断、网络单通等通信双方无法识别出这类链路异常。等到第二天业务高峰期到来时瞬间的海量业务冲击会导致消息积压无法发送给对方由于链路的重建需要时间这期间业务会大量失败 (集群或者分布式组网情况会好一些)。为了解决这个问题,需要周期性的 “心跳检测” 对链路进行有效性检查,一旦发生问题,可以及时关闭链路,重建 TCP连接。
当有业务消息时无须心跳检测可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。为了支持心跳机制Netty 提供了如下两种链路空闲检测机制。
- 读空闲超时机制:当经过 连续的周期 T 没有消息可读时,触发 超时Handler用户可以基于 该读空闲超时Handler 发送心跳消息,进行链路检测,如果连续 N个周期 仍然没有读取到心跳消息,可以主动关闭这条链路。
- 写空闲超时机制:当经过 连续的周期 T 没有消息要发送时,触发 超时Handler用户可以基于 该写空闲超时Handler 发送心跳消息,进行链路检测,如果连续 N 个周期 仍然没有接收到对方的心跳消息,可以主动关闭这条链路。
为了满足不同用户场景的心跳定制Netty 提供了空闲状态检测事件通知机制,用户可以订阅:空闲超时事件、读空闲超时机制、写空闲超时事件,在接收到对应的空闲事件之后,灵活地进行定制。
**2、内存保护机制**
Netty 提供多种机制对内存进行保护,包括以下几个方面。
- 通过对象引用计数器对 Netty 的 ByteBuffer 等内置对象进行细粒度的内存申请和释放,对非法的对象引用进行检测和保护。
- 通过内存池来重用 ByteBuffer节省内存。
- 可设置的内存容量上限,包括 ByteBuffer、线程池线程数等。
### 可定制性
Netty 的可定制性主要体现在以下几点。
- 责任链模式ChannelPipeline 基于责任链模式开发,便于业务逻辑的拦截、定制和扩展。
- 基于接口的开发:关键的类库都提供了接口或者抽象类,如果 Netty 自身的实现无法满足用户的需求,可以由用户自定义实现相关接口。
- 提供了大量工厂类,通过重载这些工厂类可以按需创建出用户实现的对象。
- 提供了大量的系统参数供用户按需设置,增强系统的场景定制性。
### 可扩展性
基于 Netty 的 基本NIO框架可以方便地进行应用层协议定制例如HTTP协议栈、Thrift协议栈、FTP协议栈 等。这些扩展不需要修改 Netty 的源码,直接基于 Netty 的二进制类库即可实现协议的扩展和定制。目前,业界存在大量的基于 Netty框架 开发的协议,例如基于 Netty 的 HTTP协议、Dubbo协议、RocketMQ内部私有协议 等。

@ -0,0 +1,150 @@
作为一个高性能的 NIO通信框架Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求,作为综合性能最高的 NIO框架 之一Netty 可以完全满足不同领域对高性能通信的需求。本章我们将从架构层对 Netty 的高性能设计和关键代码实现进行剖析,看 Netty 是如何支撑高性能网络通信的。
## RPC 调用性能模型分析
### 传统 RPC 调用性能差的原因
**一、网络传输方式问题。**
传统的 RPC框架 或者基于 RMI 等方式的 远程过程调用 采用了同步阻塞I/O当客户端的并发压力或者网络时延增大之后同步阻塞I/O 会由于频繁的 wait 导致 I/O线程 经常性的阻塞由于线程无法高效的工作I/O 处理能力自然下降。
采用 BIO通信模型 的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,接收到客户端连接之后,为其创建一个新的线程处理请求消息,处理完成之后,返回应答消息给客户端,线程销毁,这就是典型的 “ 一请求,一应答 ” 模型。该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比,由于线程是 Java虛拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能急剧下降,随着并发量的继续增加,可能会发生句柄溢出、线程堆栈溢出等问题,并导致服务器最终宕机。
**二、序列化性能差。**
Java序列化 存在如下几个典型问题:
1. Java 序列化机制是 Java 内部的一 种对象编解码技术无法跨语言使用。例如对于异构系统之间的对接Java序列化 后的码流需要能够通过其他语言反序列化成原始对象,这很难支持。
2. 相比于其他开源的序列化框架Java序列化 后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用。
3. 序列化性能差,资源占用率高 ( 主要是 CPU 资源占用高 )。
**三、线程模型问题。**
由于采用 同步阻塞I/O这会导致每个 TCP连接 都占用 1 个线程,由于线程资源是 JVM虚拟机 非常宝贵的资源,当 I/O 读写阻塞导致线程无法及时释放时,会导致系统性能急剧下降,严重的甚至会导致虚拟机无法创建新的线程。
### IO 通信性能三原则
尽管影响 I/O通信性能 的因素非常多,但是从架构层面看主要有三个要素。
1. 传输:用什么样的通道将数据发送给对方。可以选择 BIO、NIO 或者 AIOI/O模型 在很大程度上决定了通信的性能;
2. 协议采用什么样的通信协议HTTP等公有协议或者内部私有协议。协议的选择不同性能也不同。相比于公有协议内部私有协议的性能通常可以被设计得更优
3. 线程模型数据报如何读取读取之后的编解码在哪个线程进行编解码后的消息如何派发Reactor 线程模型的不同,对性能的影响也非常大。
## 异步非阻塞通信
在 I/O编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 I/O多路复用技术 进行处理。I/O多路复用技术 通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程 / 多进程模型比I/O多路复用 的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
JDK1.4 提供了对非阻塞 I/O 的支持JDK1.5 使用 epoll 替代了传统的 select / poll极大地提升了 NIO通信 的性能。
与 Socket 和 ServerSocket 类相对应NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择 同步阻塞I/O 以降低编程复杂度。但是对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。
Netty 的 I/O 线程 NioEventLoop 由于聚合了 多路复用器Selector可以同时并发处理成百上千个客户端 SocketChannel。由于读写操作都是非阻塞的这就可以充分提升 I/O线程 的运行效率,避免由频繁的 I/O阻塞 导致的线程挂起。另外,由于 Netty 采用了异步通信模式,一个 I/O线程 可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统 同步阻塞I/O “ 一连接,一线程 ” 模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
## 高效的 Reactor 线程模型
常用的 Reactor线程模型 有三种,分别如下。
1. Reactor单线程模型
2. Reactor 多线程模型;
3. 主从Reactor多线程模型。
Reactor单线程模型指的是所有的 I/O操作 都在同一个 NIO线程 上面完成NIO线程 的职责如下:
1. 作为 NIO服务端接收客户端的 TCP连接
2. 作为 NIO客户端向服务端发起 TCP连接
3. 读取通信对端的请求或者应答消息;
4. 向通信对端发送消息请求或者应答消息。
由于 Reactor模式 使用的是 异步非阻塞I/O所有的 I/O操作 都不会导致阻塞,理论上一个线程可以独立处理所有 I/O 相关的操作。从架构层面看,一个 NIO线程 确实可以完成其承担的职责。例如,通过 Acceptor 接收客户端的 TCP连接请求消息链路建立成功之后通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户Handler 可以通过 NIO线程 将消息发送给客户端。
对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下。
1. 一个 NIO线程 同时处理成百上千的链路,性能上无法支撑。即便 NIO线程 的 CPU负荷 达到 100%,也无法满足海量消息的编码,解码、读取和发送;
2. 当 NIO线程 负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO线程 的负载最终会导致大量消息积压和处理超时NIO线程 会成为系统的性能瓶颈;
3. 可靠性问题。一旦 NIO线程 意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
为了解决这些问题,演进出了 Reactor多线程模型下面我们看一下 Reactor多线程模型。
Rector多线程模型 与单线程模型最大的区别就是有一组 NIO线程 处理 I/O操作它的特点如下。
1. 有一个专门的 NIO线程 —— Acceptor线程 用于监听服务端口,接收客户端的 TCP连接请求
2. 网络IO操作 —— 读、写等由一个 NIO线程池 负责,线程池可以采用标准的 JDK线程池 实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO线程 负责消息的读取、解码、编码和发送;
3. 1 个 NIO线程 可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO线程以防止发生并发操作问题。
在绝大多数场景下Reactor多线程模型 都可以满足性能需求,但是,在极特殊应用场景中,一个 NIO线程 负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手消息进行安全认证,认证本身非常损耗性能。在这类场景下,单独一个 Acceptor线程 可能会存在性能不足问题,为了解决性能问题,产生了第三种 Reactor线程模型 —— 主从Reactor多线程模型。
主从Reactor线程模型 的特点是服务端用于接收客户端连接的不再是个单线程的连接处理Acceptor而是一个独立的 Acceptor线程池。Acceptor 接收到客户端 TCP连接请求 处理完成后 ( 可能包含接入认证等 ),将新创建的 SocketChannel 注册到 I/O处理线程池 的某个I/O线程 上,由它负责 SocketChannel 的读写和编解码工作。Acceptor线程池 只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到 I/O处理线程池的 I/O线程 上,每个 I/O线程 可以同时监听 N 个链路,对链路产生的 IO事件 进行相应的 消息读取、解码、编码及消息发送等操作。
利用 主从Reactor线程模型可以解决 1 个 Acceptor线程 无法有效处理所有客户端连接的性能问题。因此Netty官方 也推荐使用该线程模型。
事实上Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup实例 并进行适当的参数配置,就可以支持上述三种 Reactor线程模型。可以根据业务场景的性能诉求选择不同的线程模型。
Netty 单线程模型 服务端代码示例如下。
```java
EventLoopGroup reactor = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(reactor, reactor)
.channel(NioServerSocketChannel.class)
......
```
Netty 多线程模型 代码示例如下。.
```java
EventLoopGroup acceptor = new NioEventLoopGroup(1);
EventLoopGroup ioGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(acceptor, ioGroup)
.channel(NioServerSocketChannel.class)
......
```
Netty 主从多线程模型 代码示例如下。
```java
EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup ioGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(acceptorGroup, ioGroup)
.channel(NioServerSocketChannel.class)
......
```
## 无锁化的串行设计
在大多数场景下,并行多线程处理可以提升系统的并发性能。但是,如果对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最终会导致性能的下降。为了尽可能地避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。
为了尽可能提升性能Netty 对消息的处理 采用了串行无锁化设计,在 I/O线程 内部进行串行操作避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。
![avatar](/images/Netty/Netty串行化设计工作原理.png)
Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的Handler期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争从性能角度看是最优的。
## 零拷贝
Netty的 “ 零拷贝 ” 主要体现在如下三个方面。
第一种情况。Netty 的接收和发送 ByteBuffer 采用 堆外直接内存 (DIRECT BUFFERS) 进行 Socket读写不需要进行字节缓冲区的二次拷贝。如果使用传统的 堆内存(HEAP BUFFERS) 进行 Socket读写JVM 会将 堆内存Buffer 拷贝一份到 直接内存 中,然后才写入 Socket。相比于堆外直接内存消息在发送过程中多了一次缓冲区的内存拷贝。
下面我们继续看第二种 “ 零拷贝 ” 的实现 CompositeByteBuf它对外将多个 ByteBuf 封装成一个 ByteBuf对外提供统一封装后的 ByteBuf接口。CompositeByteBuf 实际就是个 ByteBuf 的装饰器,它将多个 ByteBuf 组合成一个集合,然后对外提供统一的 ByteBuf接口添加 ByteBuf不需要做内存拷贝。
第三种 “ 零拷贝 ” 就是文件传输Netty 文件传输类 DefaultFileRegion 通过 transferTo()方法 将文件发送到目标 Channel 中。很多操作系统直接将文件缓冲区的内容发送到目标 Channel 中,而不需要通过循环拷贝的方式,这是一种更加高效的传输方式,提升了传输性能,降低了 CPU 和内存占用,实现了文件传输的 “ 零拷贝 ” 。
## 内存池
随着 JVM虚拟机 和 JIT即时编译技术 的发展,对象的分配和回收是个非常轻量级的工作。但是对于 缓冲区Buffer情况却稍有不同特别是对于堆外直接内存的分配和回收是一件耗时的操作。为了尽量重用缓冲区Netty 提供了基于内存池的缓冲区重用机制。ByteBuf 的子类中提供了多种 PooledByteBuf 的实现,基于这些实现 Netty 提供了多种内存管理策略,通过在启动辅助类中配置相关参数,可以实现差异化的定制。
## Socket 与 SocketChannel
网络由下往上分为 物理层、数据链路层、网络层、传输层和应用层。IP协议 对应于网络层TCP协议 对应于传输层,而 HTTP协议 对应于应用层三者从本质上来说没有可比性Socket 则是对 TCP/IP协议 的封装和应用 (程序员层面上)。也可以说TPC/IP协议 是传输层协议,主要解决数据如何在网络中传输,而 HTTP 是应用层协议主要解决如何包装数据。Socket 是对 TCP/IP协议 的封装Socket 本身并不是协议,而是一个 调用接口(API)。 通过 Socket我们才能使用 TCP/IP协议。
### 一、利用 Socket 建立网络连接的步骤
建立 Socket连接 至少需要一对套接字,其中一个运行于客户端,称为 clientSocket ,另一个运行于服务器端,称为 serverSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1. 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2. 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3. 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给 客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
### 二、HTTP连接 的特点
  HTTP协议 是 Web联网 的基础也是手机联网常用的协议之一HTTP协议 是建立在 TCP协议 之上的一种应用。HTTP连接 最显著的特点是客户端发送的每次请求 都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为 “一次连接”。
### 三、TCP 和 UDP 的区别
1. TCP 是面向连接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但 TCP 的三次握手在很大程度上 保证了连接的可靠性。而 UDP 不是面向连接的UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说 UDP 是无连接的、不可靠的一种数据传输协议。
2. 也正由于 1 所说的特点,使得 UDP 的开销更小,数据传输速率更高,因为不必进行收发数据的确认,所以 UDP 的实时性更好。
### 四、Socket 与 SocketChannel 有什么区别
Socket、SocketChannel 二者的实质都是一样的,都是为了实现客户端与服务器端的连接而存在的,但是在使用上却有很大的区别。具体如下:
1. 所属包不同。Socket 在 java.net包 中,而 SocketChannel 在 java.nio包 中。
2. 异步方式不同。从包的不同我们大体可以推断出他们主要的区别Socket 是阻塞连接SocketChannel 可以设置为非阻塞连接。使用 ServerSocket 与 Socket 的搭配服务端Socket 往往要为每一个 客户端Socket 分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中。过多的线程也会影响服务器的性能。而使用 SocketChannel 与 ServerSocketChannel 的搭配可以非阻塞通信,这样使得服务器端只需要一个线程就能处理所有 客户端Socket 的请求。
3. 性能不同。一般来说,高并发场景下,使用 SocketChannel 与 ServerSocketChannel 的搭配会有更好的性能。
4. 使用方式不同。Socket、ServerSocket类 可以传入不同参数直接实例化对象并绑定 IP 和 端口。而 SocketChannel、ServerSocketChannel类 需要借助 Selector类。
下面是 SocketChannel方式 需要用到的几个核心类:
ServerSocketChannelServerSocket 的替代类, 支持阻塞通信与非阻塞通信。
SocketChannelSocket 的替代类, 支持阻塞通信与非阻塞通信。
Selector为 ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel 监控连接服务器读就绪和写就绪事件。
SelectionKey代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄。当一个 SelectionKey对象 位于 Selector对象 的 selected-keys集合 中时,就表示与这个 SelectionKey对象 相关的事件发生了。在 SelectionKey类 中有如下几个静态常量:
- SelectionKey.OP_ACCEPT客户端连接就绪事件等于监听 serverSocket.accept(),返回一个 socket。
- SelectionKey.OP_CONNECT准备连接服务器就绪跟上面类似只不过是对于 socket 的,相当于监听了 socket.connect()。
- SelectionKey.OP_READ读就绪事件, 表示输入流中已经有了可读数据, 可以执行读操作了。
- SelectionKey.OP_WRITE写就绪事件, 表示可以执行写操作了。

@ -0,0 +1,57 @@
## Linux 网络 IO 模型简介
Linux 的内核将所有外部设备都看做一个文件来操作对一个文件的读写操作会调用内核提供的系统命令返回一个fd (file descriptor文件描述符)。而对一个 socket 的读写也会有相应的描述符,称为 socket fd (socket 描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。根据UNIX网络编程对 I/O模型 的分类UNIX 提供了5种 I/O模型分别如下。
#### 1、阻塞IO模型
在内核将数据准备好之前系统调用会一直等待所有的套接字Socket传来数据默认的是阻塞方式。
![avatar](/images/Netty/阻塞IO模型.png)
Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom方法OS 会判断来自网络的数据报是否准备好当数据报准备好了之后OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。
BIO中的阻塞就是阻塞在2个地方
1. OS 等待数据报通过网络发送过来,如果建立连接后数据一直没过来,就会白白浪费线程的资源;
2. 将数据从内核空间拷贝到用户空间。
在这2个时候我们的线程会一直被阻塞啥事情都不干。
#### 2、非阻塞IO模型
![avatar](/images/Netty/非阻塞IO模型.png)
每次应用程序询问内核是否有数据报准备好当有数据报准备好时就进行拷贝数据报的操作从内核拷贝到用户空间和拷贝完成返回的这段时间应用进程是阻塞的。但在没有数据报准备好时并不会阻塞程序内核直接返回未准备好的信号等待应用进程的下一次询问。但是轮寻对于CPU来说是较大的浪费一般只有在特定的场景下才使用。
从图中可以看到非阻塞IO 的 recvfrom调用 会立即得到一个返回结果(数据报是否准备好)我们可以根据返回结果继续执行不同的逻辑。而阻塞IO 的recvfrom调用如果无数据报准备好一定会被阻塞住。虽然 非阻塞IO 比 阻塞IO 少了一段阻塞的过程,但事实上 非阻塞IO 这种方式也是低效的,因为我们不得不使用轮询方法区一直问 OS“我的数据好了没啊”。
**BIO 不会在 拷贝数据之前 阻塞但会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方Non-Blocking 还是会阻塞的。**
#### 3、IO复用模型
Linux 提供 select/poll进程通过将一个或多个 fd 传递给 select 或 poll系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限因此它的使用也受到了一些制约。Linux 还提供了一个 epoll系统调用epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。
![avatar](/images/Netty/IO复用模型.png)
#### 4、信号驱动IO模型
首先开启套接口信号驱动IO功能并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO信号通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。
![avatar](/images/Netty/信号驱动IO模型.png)
#### 5、异步IO模型
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是信号驱动IO 由内核通知我们何时可以开始一个 IO 操作异步IO模型 由内核通知我们 IO操作何时已经完成。
![avatar](/images/Netty/异步IO模型.png)
从这五种 IO模型的结构 也可以看出阻塞程度阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO效率是由低到高的。
最后我们看一下数据从客户端到服务器再由服务器返回结果数据的整体IO流程以便我们更好地理解上述的IO模型。
![avatar](/images/Netty/数据在客户端及服务器之间的整体IO流程.png)
## IO 多路复用技术
Java NIO 的核心类库中 多路复用器Selector 就是基于 epoll 的多路复用技术实现。
在 IO编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO多路复用技术 进行处理。IO多路复用技术 通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比IO多路复用 的最大优势是系统开销小系统不需要创建新的额外进程或线程也不需要维护这些进程和线程的运行降低了系统的维护工作量节省了系统资源IO多路复用 的主要应用场景如下。
- 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;
- 服务器需要同时处理多种网络协议的套接字。
目前支持 IO多路复用 的系统调用有 select、pselect、poll、epoll在 Linux网络编程 过程中,很长一段时间都使用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择了 epoll。epoll 与 select 的原理比较类似,为了克服 select 的缺点epoll 作了很多重大改进,现总结如下。
1. 支持一个进程打开的 socket描述符 (fd) 不受限制(仅受限于操作系统的最大文件句柄数)
2. IO效率 不会随着 FD 数目的增加而线性下降;
3. epoll的API更加简单。
值得说明的是,用来克服 select/poll 缺点的方法不只有 epoll, epoll 只是一种 Linux 的实现方案。

@ -0,0 +1,318 @@
## 传统的BIO编程
网络编程的基本模型是 Client/Server 模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket) 进行通信。
在基于传统同步阻塞模型开发中ServerSocket 负责绑定IP 地址启动监听端口Socket负责发起连接操作。连接成功之后双方通过输入和输出流进行同步阻塞式通信。
### BIO通信模型
通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,它接收到客户
端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。
![avatar](/images/Netty/BIO通信模型.png)
该模型最大的问题就是缺乏弹性伸缩能力当客户端并发访问量增加后服务端的线程个数和客户端并发访问数呈1: 1的正比关系由于线程是 Java虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。
在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足高性能、高并发接入的场景。为了改进 一线程一连接 模型后来又演进出了一种通过线程池或者消息队列实现1个或者多个线程处理N个客户端的模型由于它的底层通信机制依然使用 同步阻塞IO所以被称为 “伪异步”。
## 伪异步IO编程
为了解决 同步阻塞IO 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成 客户端个数M线程池最大线程数N 的比例关系,其中 M 可以远远大于 N。通过线程池可以灵活地调配线程资源设置线程的最大值防止由于海量并发接入导致线程耗尽。
### 伪异步IO模型图
采用线程池和任务队列可以实现一种叫做 伪异步的IO通信框架其模型图下。当有新的客户端接入时将客户端的 Socket 封装成一个 Task对象 (该类实现了java.lang.Runnable接口)投递到后端的线程池中进行处理JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
![avatar](/images/Netty/伪异步IO通信模型.png)
伪异步 IO通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。
### 伪异步IO编程弊端分析
要对 伪异步IO编程 的弊端进行深入分析,首先我们看两个 Java同步IO 的 API说明随后结合代码进行详细分析。
```java
public abstract class InputStream implements Closeable {
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the stream
* has been reached, the value <code>-1</code> is returned. This method
* blocks until input data is available, the end of the stream is detected,
* or an exception is thrown.
*
* <p> A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public abstract int read() throws IOException;
}
```
注意其中的一句话 **“This method blocks until input data is available, the end of the stream is detected, or an exception is thrown”**,当对 Socket 的输入流进行读取操作的时候,它会一直阻塞下去,直到发生如下三种事件。
- 有数据可读;
- 可用数据已经读取完毕;
- 发生空指针或者 IO异常。
这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程将被长时间阻塞,如果对方要 60s 才能够将数据发送完成,读取一方的 IO线程 也将会被同步阻塞 60s在此期间其他接入消息只能在消息队列中排队。
下面我们接着对输出流进行分析,还是看 JDK IO类库 输出流的 API文档然后结合文档说明进行故障分析。
```java
public abstract class OutputStream implements Closeable, Flushable {
/**
* Writes an array of bytes. This method will block until the bytes are *actually written.
*
* @param b the data.
* @exception IOException if an I/O error occurs.
* @see java.io.OutputStream#write(byte[], int, int)
*/
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
}
```
当调用 OutputStream 的 write()方法 写输出流的时候,它将会被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。学习过 TCP/IP 相关知识的人都知道,当消息的接收方处理缓慢的时候,将不能及时地从 TCP缓冲区 读取数据,这将会导致发送方的 TCP window size 不断减小,直到为 0双方处于 Keep-Alive状态消息发送方将不能再向 TCP缓冲区 写入消息,这时如果采用的是 同步阻塞IOwrite操作 将会被无限期阻塞,直到 TCP window size 大于0 或者发生 IO异常。
通过对输入和输出流的 API文档 进行分析,我们了解到读和写操作都是同步阻塞的,阻塞的时间取决于对方 IO线程 的处理速度和 网络IO 的传输速度。本质上来讲,我们无法保证生产环境的网络状况和对方的应用程序能足够快,如果我们的应用程序依赖对方的处理速度,它的可靠性就非常差。也许在实验室进行的性能测试结果令人满意,但是一旦上线运行,面对恶劣的网络环境和良莠不齐的第三方系统,问题就会如火山一样喷发。
伪异步IO 实际上仅仅是对之前 IO线程模型 的一个简单优化,它无法从根本上解决 同步IO 导致的通信线程阻塞问题。下面我们就简单分析下通信对方返回应答时间过长会引起的级联故障。
1. 服务端处理缓慢返回应答消息耗费60s 平时只需要10ms。
2. 采用伪异步I/O的线程正在读取故障服务节点的响应由于读取输入流是阻塞的它将会被同步阻塞60s。
3. 假如所有的可用线程都被故障服务器阻塞那后续所有的1/O消息都将在队列中排队。
4. 由于线程池采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞。
5. 由于前端只有一个Accptor线程接收客户端接入它被阻塞在线程池的同步阻塞队列之后新的客户端请求消息将被拒绝客户端会发生大量的连接超时。
6. 由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。
## NIO编程
与 Socket类 和 ServerSocket类 相对应NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员可以根据自
己的需要来选择合适的模式。一般来说,低负载、低并发的应用程序可以选择 同步阻塞IO以降低编程复杂度对于高负载、高并发的网络应用需要使用 NIO 的非阻塞模式进行开发。
### NIO类库简介
NIO类库 是在 JDK 1.4 中引入的。NIO 弥补了原来 同步阻塞IO 的不足,它在 标准Java代码 中提供了高速的、面向块的IO。下面我们简单看一下 NIO类库 及其 相关概念。
**1、缓冲区Buffer**
Buffer对象 包含了一些要写入或者要读出的数据。在 NIO类库 中加入 Buffer对象是其与 原IO类库 的一个重要区别。在面向流的 IO 中,可以将数据直接写入或者将数据直接读到 Stream对象 中。在NIO库中所有数据都是用缓冲区处理的。在读取数据时它是直接读到缓冲区中的在写入数据时写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。
缓冲区实质上是一个数组。通常它是一个字节数组ByteBuffer也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组缓冲区提供了对数据的结构化访问以及维护读写位置limit等信息。最常用的缓冲区是 ByteBuffer一个 ByteBuffer 提供了一组功能用于操作 byte数组。除了 ByteBuffer还有其他的一些缓冲区事实上每一种 Java基本类型除了 boolean都对应有一种与之对应的缓冲区CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer组件中主要类的类图如下所示。
![avatar](/images/Netty/Buffer组件类图.png)
除了ByteBuffer每一个 Buffer类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准IO操作 都使用 ByteBuffer所以它在具有一般缓冲区的操作之外还提供了一些特有的操作以方便网络读写。
**2、通道Channel**
Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX网络编程模型 中底层操作系统的通道都是全双工的同时支持读写操作。Channel组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。
![avatar](/images/Netty/Channel组件类图.png)
**3、多路复用器Selector**
多路复用器Selector 是 Java NIO编程 的基础,熟练地掌握 Selector 对于 NIO编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲Selector会不断地轮询 “注册在其上的Channel”如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO操作。
一个 多路复用器Selector 可以同时轮询多个 Channel由于 JDK 使用了 epoll() 代替传统的 select 的实现,所以它并没有最大连接句柄的限制。这也就意味着,只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。下面,我们通过 NIO编程的序列图 和 源码分析来熟悉相关的概念。
### NIO服务端序列图
![avatar](/images/Netty/NIO服务端序列图.png)
下面,我们看一下 NIO服务端 的主要创建过程。
1、打开 ServerSocketChannel用于监听客户端的连接它是所有客户端连接的
父管道,示例代码如下。
```java
ServerSocketChannel acceptorSvr = ServerSocketChannel.open();
```
2、绑定监听端口设置连接为非阻塞模式示例代码如下。
```java
acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port));
acceptorSvr.configureBlocking(false);
```
3、创建 Reactor线程创建多路复用器并启动线程示例代码如下。
```java
Selector selector = Selector.open();
New Thread (new ReactorTask()).start();
```
4、将 ServerSocketChannel 注册到 Reactor线程 的 多路复用器Selector 上,监听 ACCEPT事件示例代码如下。
```java
SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ ACCEPT, ioHandler);
```
5、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的Key示例代码如下。
```java
int num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
// .... deal with IO event ...
```
6、多路复用器Selector 监听到有新的客户端接入,处理新的接入请求,完成 TCP三次握手建立物理链路示例代码如下。
```java
SocketChannel channel = svrChannel.accept();
```
7、设置客户端链路为非阻塞模式示例代码如下。
```java
channel.configureBlocking(false);
channel.socket().setReuseAddress(true);
......
```
8、将新接入的客户端连接注册到 Reactor线程 的多路复用器上,监听读操作,读取客户端发送的网络消息,示例代码如下。
```java
SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ, ioHandler);
```
9、异步读取客户端请求消息到缓冲区示例代码如下。
```java
int readNumber = channel.read(receivedBuffer);
```
10、对 ByteBuffer 进行编解码,如果有半包消息指针 reset继续读取后续的报文将解码成功的消息封装成 Task投递到业务线程池中,进行业务逻辑编排,示例代码如下。
```java
List messageList = null;
while (byteBuffer.hasRemain()) {
byteBuffer.mark();
Object message = decode(byteBuffer) ;
if (message == null) {
byteBuffer.reset();
break;
}
messageList.add(message);
}
if (!byteBuffer.hasRemain()) {
byteBuffer.clear();
} else {
byteBuffer.compact();
}
if (messageList != null && !messageList.isEmpty()) {
for (Object message : messageList) {
handlerTask(message);
}
}
```
11、将 POJO对象 encode 成 ByteBuffer调用 SocketChannel 的 异步write接口将消息异步发送给客户端示例代码如下。
```java
socketChannel.write(byteBuffer);
```
注意:如果发送区 TCP缓冲区满会导致写半包此时需要注册监听写操作位循环写直到整包消息写入 TCP缓冲区。对于 “半包问题” 此处暂不赘述,后续会单独写一篇详细分析 Netty 的处理策略。
### NIO 客户端序列图
![avatar](/images/Netty/NIO客户端序列图.png)
1、打开 SocketChannel绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。
```java
SocketChannel clientChannel = SocketChannel.open();
```
2、设置 SocketChannel 为非阻塞模式,同时设置客户端连接的 TCP参数示例代码如下。
```java
clientChannel.configureBlocking(false);
socket.setReuseAddress(true);
socket.setReceiveBufferSize(BUFFER_SIZE);
socket.setSendBufferSize(BUFFER_SIZE);
```
3、异步连接服务端示例代码如下。
```java
boolean connected = clientChannel.connect( new InetSocketAddress("ip", port) );
```
4、判断是否连接成功如果连接成功则直接注册读状态位到多路复用器中如果当前没有连接成功则重连 (异步连接返回false说明客户端已经发送 syne包服务端没有返回 ack包物理链路还没有建立),示例代码如下。
```java
if (connected) {
clientChannel.register(selector, SelectionKey.OP_READ, ioHandler);
} else {
clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler);
}
```
5、向 Reactor线程 的多路复用器注册 OP_CONNECT 状态位,监听服务端的 TCP ACK应答示例代码如下。
```java
clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler);
```
6、创建 Reactor线程创建多路复用器并启动线程代码如下。
```java
Selector selector = Selector.open();
New Thread( new ReactorTask() ).start();
```
7、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的Key代码如下。
```java
int num = selector.select();
Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
// ... deal with IO event ...
}
```
8、接收 connect事件并进行处理示例代码如下。
```java
if (key.isConnectable()) {
// handlerConnect();
}
```
9、判断连接结果如果连接成功注册读事件到多路复用器示例代码如下。
```java
if(channel.finishConnect()) {
registerRead();
}
```
10、注册读事件到多路复用器示例代码如下。
```java
clientChannel.register(selector, SelectionKey.OP_READ, ioHandler);
```
11、异步读客户端请求消息到缓冲区示例代码如下。
```java
int readNumber = channel.read(receivedBuffer);
```
12、对 ByteBuffer 进行编解码,如果有半包消息接收缓冲区 Reset继续读取后续的报文将解码成功的消息封装成 Task投递到业务线程池中进行业务逻辑编排。示例代码如下。
```java
List messageList = null;
while (byteBuffer.hasRemain()) {
byteBuffer.mark();
object message = decode(byteBuffer);
if (message == nu11) {
byteBuffer.reset();
break;
}
messageList.add(message);
}
if (!byteBuffer.hasRemain()) {
byteBuffer.clear();
} else {
byteBuffer.compact();
}
if ( messageList != null && !messageList.isEmpty() )
for (Object message : messageList) {
handlerTask(message);
}
}
```
13、将 POJO对象 encode成 ByteBuffer调用 SocketChannel 的 异步write接口将消息异步发送给客户端。示例代码如下。
```java
socketChannel.write(buffer);
```
## AIO编程
NIO2.0 引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取获取操作结果。
- 通过 java.util.concurrent.Future类 来表示异步操作的结果;
- 在执行异步操作的时候传入一个 java.nio.channels.CompletionHandler接口 的实现类作为操作完成的回调。
NIO2.0 的异步套接字通道是真正的 异步非阻塞IO对应于 UNIX网络编程 中的 事件驱动IO (AIO)。它不需要通过多路复用器 (Selector) 对注册的通道进行轮询操作即可实现异步读写,从而简化了 NIO 的编程模型。
由于在实际开发中使用较少,所以这里不对 AIO 进行详细分析。
## 四种IO编程模型的对比
对比之前,这里再澄清一下 “伪异步IO” 的概念。伪异步IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO编程 没有流行之前,为了解决 Tomcat 通信线程同步IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO线程 和业务线程间的直接访问,这样业务线程就不会被 IO线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO线程 或者进行 IO读写这样也就不会被同步阻塞。
![avatar](/images/Netty/四种IO模型的功能特性对比图.png)
## 选择 Netty 开发项目的理由
从可维护性角度看,由于 NIO 采用了异步非阻塞编程模型,而且是一个 IO线程 处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。
### 为什么不选择 Java原生NIO 进行开发
1. NIO 的类库和 API 使用起来非常繁杂,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。
2. 需要具备其他的额外技能做铺垫,例如,熟悉 Java多线程编程。这是因为 NIO编程 涉及到 Reactor模式你必须对 多线程 和 网路编程 非常熟悉,才能编写出高质量的 NIO程序。
3. 可靠性能力补齐,工作量和难度都非常大。例如客户端面临:断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理,等问题。
4. JDK NIO 的 BUG例如臭名昭著的 epoll bug它会导致 Selector空轮询最终导致 CPU 100%。虽然官方声称修复了该问题,但是直到 JDK 1.7版本 该问题仍旧未得到彻底的解决。
### 为什么选择 Netty 进行开发
Netty 是业界最流行的 NIO框架 之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,已经得到成百上千的商用项目验证,例如 Hadoop 的 RPC框架 Avro ,阿里的 RPC框架 Dubbo 就使用了 Netty 作为底层通信框架。通过对Netty的分析我们将它的优点总结如下。
- API使用简单开发门槛低
- 功能强大,预置了多种编解码功能,支持多种主流协议;
- 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活地扩展;
- 性能高,通过与其他业界主流的 NIO框架 对比Netty 的综合性能最优;
- 成熟、稳定Netty 修复了已经发现的所有 JDK NIO BUG业务开发人员不需要再为 NIO 的 BUG 而烦恼;
- 社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时,更多的新功能会加入;
- 经历了大规模的商业应用考验质量得到验证。Netty 在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。
正是因为这些优点Netty 逐渐成为了 Java NIO编程 的首选框架。

@ -0,0 +1,47 @@
## TCP粘包/拆包
熟悉 TCP编程 的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP底层 的 粘包/拆包机制。TCP粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现 粘包 / 拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。本篇博文,我们先简单了解 TCP粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的。
### TCP粘包/拆包问题说明
TCP 是个 “流” 协议所谓流就是没有界限的一串数据。TCP底层 并不了解上层(如 HTTP协议业务数据的具体含义它会根据 TCP缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP粘包和拆包问题。我们可以通过下面的示例图对 TCP粘包和拆包问题 进行说明。
![avatar](/images/Netty/TCP粘包拆包问题.png)
假设客户端依次发送了两个数据包 DI 和 D2 给服务端由于服务端一次读取到的字节数是不确定的故可能存在以下4种情况。
1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2没有粘包和拆包
2. 服务端一次接收到了两个数据包DI 和 D2 粘合在一起,被称为 TCP粘包
3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 DI包 和 D2包的部分内容第二次读取到了 D2包 的剩余内容,这被称为 TCP拆包
4. 服务端分两次读取到了两个数据包,第一次读取到了 D1包的部分内容第二次读取到了 D1包的剩余内容 和 D2包的整包。
如果此时服务端 TCP 接收滑窗非常小,而 数据包DI 和 D2 比较大很有可能会发生第5种可能即服务端分多次才能将 D1 和 D2包 接收完全,期间发生多次拆包。
### TCP粘包/拆包发生的原因
问题产生的原因有三个,分别如下。
1. **应用程序 write写入的字节大小 超出了 套接口发送缓冲区大小;**
2. 进行 MSS 大小的 TCP分段
3. 以太网帧的 payload 大于 MTU 进行 IP分片。
### 粘拆包问题的解决策略
由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。
1. 固定消息长度,例如,每个报文的大小为 固定长度200字节如果不够空位补空格
2. 在包尾使用 “回车换行符” 等特殊字符作为消息结束的标志例如FTP协议这种方式在文本协议中应用比较广泛
3. 将消息分为消息头和消息体,在消息头中定义一个 长度字段Len 来标识消息的总长度;
4. 更复杂的应用层协议。
介绍完了 TCP粘包/拆包 的基础,下面我们来看看 Netty 是如何使用一系列 “半包解码器” 来解决 TCP粘包/拆包问题的。
## 利用 Netty的解码器 解决 TCP粘拆包问题
根据上面的 粘拆包问题解决策略Netty 提供了相应的解码器实现。有了这些解码器用户不需要自己对读取的报文进行人工解码也不需要考虑TCP的粘包和拆包。
### LineBasedFrameDecoder 和 StringDecoder 的原理分析
为了解决 TCP粘包 / 拆包 导致的 半包读写问题Netty 默认提供了多种编解码器用于处理半包只要能熟练掌握这些类库的使用TCP粘拆包问题 从此会变得非常容易,你甚至不需要关心它们,这也是其他 NIO框架 和 JDK原生的 NIO API 所无法匹敌的。对于使用者来说,只要将支持半包解码的 Handler 添加到 ChannelPipeline对象 中即可,不需要写额外的代码,使用起来非常简单。
```java
// 示例代码,其中 socketChannel 是一个 SocketChannel对象
socketChannel.pipeline().addLast( new LineBasedFrameDecoder(1024) );
socketChannel.pipeline().addLast( new StringDecoder() );
```
LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断看是否有 “\n” 或者 “\r\n”如果有就以此位置为结束位置从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器支持携带结束符或者不携带结束符两种解码方式同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符就会抛出异常同时忽略掉之前读到的异常码流。
StringDecoder 的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的 Handler。LineBasedFrameDecoder + StringDecoder组合 就是按行切换的文本解码器,它被设计用来支持 TCP 的粘包和拆包。
### 其它解码器
除了 LineBasedFrameDecoder 以外,还有两个常用的解码器 DelimiterBasedFrameDecoder 和 FixedLengthFrameDecoder前者能自动对 “以分隔符做结束标志的消息” 进行解码,后者可以自动完成对定长消息的解码。使用方法也和前面的示例代码相同,结合 字符串解码器StringDecoder轻松完成对很多消息的自动解码。

@ -0,0 +1,247 @@
相对于服务端Netty客户端 的创建更加复杂,除了要考虑线程模型、异步连接、客户端连接超时等因素外,还需要对连接过程中的各种异常进行考虑。本章将对 Netty客户端 创建的关键流程和源码进行分析,以期读者能够了解客户端创建的细节。
## 基于 Netty 创建客户端的流程分析
Netty 为了向使用者屏蔽 NIO通信 的底层细节在和用户交互的边界做了封装目的就是为了减少用户开发工作量降低开发难度。Bootstrap 是 Socket 客户端创建工具类,用户通过 Bootstrap 可以方便地创建 Netty 的客户端并发起 异步TCP连接操作。
### 基于 Netty 创建客户端 时序图
![avatar](/images/Netty/基于Netty创建客户端时序图.png)
### Netty 创建客户端 流程分析
1. 用户线程创建 Bootstrap实例通过 API 设置客户端相关的参数,异步发起客户端连接;
2. 创建处理客户端连接、I/O 读写的 Reactor线程组 NioEventLoopGroup。可以通过构造函数指定 IO线程 的个数,默认为 CPU 内核数的 2 倍;
3. 通过 Bootstrap 的 ChannelFactory 和用户指定的 Channel类型 创建用于客户端连接的 NioSocketChannel它的功能类似于 JDK NIO类库 提供的 SocketChannel
4. 创建默认的 Channel、Handler、Pipeline用于调度和执行网络事件
5. 异步发起 TCP连接判断连接是否成功。如果成功则直接将 NioSocketChannel 注册到多路复用器上,监听读操作位,用于数据报读取和消息发送;如果没有立即连接成功,则注册连接监听位到多路复用器,等待连接结果;
6. 注册对应的网络监听状态位到多路复用器;
7. 由多路复用器在 IO 现场中轮询各 Channel处理连接结果
8. 如果连接成功,设置 Future结果发送连接成功事件触发 ChanneIPipeline 执行;
9. 由 ChannelPipeline 调度执行系统和用户的 ChannelHandler执行业务逻辑。
## Netty 客户端创建源码分析
Netty客户端 的创建流程比较繁琐,下面我们针对关键步骤和代码进行分析,通过梳理关键流程来掌握客户端创建的原理。
### 客户端连接辅助类 BootStrap
Bootstrap 是 Netty 提供的客户端连接工具类,主要用于简化客户端的创建,下面我们对它的 主要API 进行讲解。
设置 lO线程组NIO的特点就是一个多路复用器可以同时处理上干条链路这就意味着NIO模式中 一个线程可以处理多个 TCP连接。考虑到 lO线程 的处理性能,大多数 NIO框架 都采用线程池的方式处理 IO读写Netty 也不例外。客户端相对于服务端,只需要一个处理 IO读写 的线程组即可,因为 Bootstrap 提供了 设置IO线程组 的接口,代码如下。
```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
volatile EventLoopGroup group;
public B group(EventLoopGroup group) {
if (group == null) {
throw new NullPointerException("group");
} else if (this.group != null) {
throw new IllegalStateException("group set already");
} else {
this.group = group;
return this;
}
}
}
```
由于 Netty 的 NIO线程组 默认采用 EventLoopGroup接口因此线程组参数使用 EventLoopGroup。
TCP参数设置接口无论是 NIO还是 BIO创建客户端套接字的时候通常都会设置连接参数例如接收和发送缓冲区大小、连接超时时间等。Bootstrap 也提供了客户端 TCP参数设置接口代码如下。
```java
public <T> B option(ChannelOption<T> option, T value) {
if (option == null) {
throw new NullPointerException("option");
} else {
if (value == null) {
synchronized(this.options) {
this.options.remove(option);
}
} else {
synchronized(this.options) {
this.options.put(option, value);
}
}
return this;
}
}
```
Netty 提供的 主要TCP参数 如下。
1、SO_TIMEOUT控制读取操作将阻塞多少毫秒。如果返回值为0计时器就被禁止了该线程将无限期阻塞
2、SO_SNDBUF套接字使用的发送缓冲区大小
3、SO_RCVBUF套接字使用的接收缓冲区大小
4、SO_REUSEADDR用于决定 如果网络上仍然有数据向旧的 ServerSocket 传输数据,是否允许新的 ServerSocket 绑定到与旧的 ServerSocket 同样的端口上。SO_REUSEADDR选项 的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口;
5、CONNECT_TIMEOUT_MILLIS客户端连接超时时间由于 NIO原生的客户端 并不提供设置连接超时的接口因此Netty 采用的是自定义连接超时定时器负责检测和超时控制;
Channel接口用于指定客户端使用的 Channel接口对于 TCP客户端连接默认使用 NioSocketChannel代码如下。
```java
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
} else {
return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
}
}
```
BootstrapChannelFactory 利用 参数channelClass通过反射机制创建 NioSocketChannel对象。
设置 Handler接口Bootstrap 为了简化 Handler 的编排,提供了 Channellnitializer它继承了 ChannelHandlerAdapter当 TCP链路 注册成功之后,调用 initChannel 接口,用于设置用户 ChanneIHandler。它的代码如下。
```java
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
if (this.initChannel(ctx)) {
ctx.pipeline().fireChannelRegistered();
} else {
ctx.fireChannelRegistered();
}
}
}
```
最后一个比较重要的接口就是发起客户端连接,代码如下。
```java
ChannelFuture f = b.connect(host, port).sync();
```
### 客户端连接操作
首先要创建和初始化 NioSocketChannel代码如下。
```java
public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
} else {
this.validate();
return this.doResolveAndConnect(remoteAddress, localAddress);
}
}
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
// 首先要创建和初始化 NioSocketChannel
ChannelFuture regFuture = this.initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.isDone()) {
// 初始化 Channel 之后,将其注册到 Selector 上
return !regFuture.isSuccess() ? regFuture : this.doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
Bootstrap.this.doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
try {
EventLoop eventLoop = channel.eventLoop();
AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
doConnect(remoteAddress, localAddress, promise);
return promise;
}
Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
if (resolveFuture.isDone()) {
Throwable resolveFailureCause = resolveFuture.cause();
if (resolveFailureCause != null) {
channel.close();
promise.setFailure(resolveFailureCause);
} else {
doConnect((SocketAddress)resolveFuture.getNow(), localAddress, promise);
}
return promise;
}
resolveFuture.addListener(new FutureListener<SocketAddress>() {
public void operationComplete(Future<SocketAddress> future) throws Exception {
if (future.cause() != null) {
channel.close();
promise.setFailure(future.cause());
} else {
Bootstrap.doConnect((SocketAddress)future.getNow(), localAddress, promise);
}
}
});
} catch (Throwable var9) {
promise.tryFailure(var9);
}
return promise;
}
private static void doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
}
```
需要注意的是SocketChannel 执行 connect() 操作后有以下三种结果。
1. 连接成功返回True
2. 暂时没有连接上,服务端没有返回 ACK应答连接结果不确定返回False
3. 连接失败,直接抛出 IO异常。
如果是第二种结果,需要将 NioSocketChannel 中的 selectionKey 设置为 OP_CONNECT监听连接结果。异步连接返回之后需要判断连接结果如果连接成功则触发 ChannelActive 事件。ChannelActive事件 最终会将 NioSocketChannel 中的 selectionKey 设置为 SelectionKey.OP_READ用于监听网络读操作。如果没有立即连接上服务端则注册 SelectionKey.OP_CONNECT 到多路复用器。如果连接过程发生异常,则关闭链路,进入连接失败处理流程。
### 异步连接结果通知
NioEventLoop 的 Selector 轮询 客户端连接Channel当服务端返回握手应答之后对连接结果进行判断代码如下。
```java
if ((readyOps & 8) != 0) {
int ops = k.interestOps();
ops &= -9;
k.interestOps(ops);
unsafe.finishConnect();
}
```
下面对 finishConnect()方法 进行分析,代码如下。
```java
try {
boolean wasActive = AbstractNioChannel.this.isActive();
AbstractNioChannel.this.doFinishConnect();
this.fulfillConnectPromise(AbstractNioChannel.this.connectPromise, wasActive);
} catch (Throwable var5) {
......
}
```
doFinishConnect()方法 用于判断 JDK 的 SocketChannel 的连接结果,如果未出错 表示连接成功,其他值或者发生异常表示连接失败。
```java
protected void doFinishConnect() throws Exception {
if (!this.javaChannel().finishConnect()) {
throw new Error();
}
}
```
连接成功之后,调用 fufillConectPromise()方法,触发链路激活事件,该事件由 ChannelPipeline 进行传播。
### 客户端连接超时机制
对于SocketChannel接口JDK并没有提供连接超时机制需要NIO框架或者用户自己扩展实现。Netty利用定时器提供了客户端连接超时控制功能下面我们对该功能进行详细讲解。
首先,用户在创建Netty 客户端的时候可以通过ChannelOption.CONNECT_TIMEOUT_MILLIS配置项设置连接超时时间代码如下。
```java
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
```

@ -0,0 +1,606 @@
## Netty 服务端创建源码分析
当我们直接使用 JDK 的 NIO类库 开发基于 NIO 的异步服务端时,需要用到 多路复用器Selector、ServerSocketChannel、SocketChannel、ByteBuffer、SelectionKey 等,相比于传统的 BIO开发NIO 的开发要复杂很多开发出稳定、高性能的异步通信框架一直是个难题。Netty 为了向使用者屏蔽 NIO通信 的底层细节在和用户交互的边界做了封装目的就是为了减少用户开发工作量降低开发难度。ServerBootstrap 是 Socket服务端 的启动辅助类,用户通过 ServerBootstrap 可以方便地创建 Netty 的服务端。
### Netty 服务端创建时序图
![avatar](/images/Netty/Netty服务端创建时序图.png)
下面我们对 Netty服务端创建 的关键步骤和原理进行详细解析。
1、**创建 ServerBootstrap实例**。ServerBootstrap 是 Netty服务端 的 启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层对各种 原生NIO 的 API 进行了封装,减少了用户与 底层API 的接触降低了开发难度。ServerBootstrap 中只有一个 public 的无参的构造函数可以给用户直接使用ServerBootstrap 只开放一个无参的构造函数 的根本原因是 它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入 Builder建造者模式。
2、**设置并绑定 Reactor线程池**。Netty 的 Reactor线程池 是 EventLoopGroup它实际上是一个 EventLoop数组。EventLoop 的职责是处理所有注册到本线程多路复用器 Selector 上的 ChannelSelector 的轮询操作由绑定的 EventLoop线程 的 run()方法 驱动在一个循环体内循环执行。值得说明的是EventLoop 的职责不仅仅是处理 网络IO事件用户自定义的Task 和 定时任务Task 也统一由 EventLoop 负责处理,这样线程模型就实现了统一。从调度层面看,也不存在从 EventLoop线程 中再启动其他类型的线程用于异步执行另外的任务,这样就避免了多线程并发操作和锁竞争,提升了 IO线程 的处理和调度性能。
3、**设置并绑定 服务端Channel**。作为 NIO服务端需要创建 ServerSocketChannelNetty 对 原生NIO类库 进行了封装对应的实现是NioServerSocketChannel。对于用户而言不需要关心 服务端Channel 的底层实现细节和工作原理,只需要指定具体使用哪种服务端 Channel 即可。因此Netty 中 ServerBootstrap的基类 提供了 channel()方法,用于指定 服务端Channel 的类型。Netty 通过工厂类,利用反射创建 NioServerSocketChannel对象。由于服务端监听端口往往只需要在系统启动时才会调用因此反射对性能的影响并不大。相关代
码如下。
```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
/**
* 通过 参数channelClass 创建一个 Channel实例
*/
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
}
```
4、**链路建立的时候创建并初始化 ChannelPipeline**。ChannelPipeline 并不是 NIO服务端 必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行 ChannelHandler。网络事件以事件流的形式在 ChannelPipeline 中流转,由 ChannelPipeline 根据 ChannelHandler的执行策略 调度 ChannelHandler的执行。典型的网络事件如下。
1. 链路注册;
2. 链路激活;
3. 链路断开;
4. 接收到请求消息;
5. 请求消息接收并处理完毕;
6. 发送应答消息;
7. 链路发生异常;
8. 发生用户自定义事件。
5、**初始化 ChannelPipeline 完成之后,添加并设置 ChannelHandler**。ChannelHandler 是 Netty 提供给用户定制和扩展的关键接口。利用 ChannelHandler 用户可以完成大多数的功能定制例如消息编解码、心跳、安全认证、TSL/SSL 认证、流量控制和流量整形等。Netty 同时也提供了大量的 系统ChannelHandler 供用户使用,比较实用的 系统ChannelHandler 总结如下。
1. 系统编解码框架ByteToMessageCodec
2. 基于长度的半包解码器LengthFieldBasedFrameDecoder
3. 码流日志打印 HandlerLoggingHandler
4. SSL 安全认证 HandlerSslHandler
5. 链路空闲检测 HandlerIdleStateHandler
6. 流量整形 HandlerChannelTrafficShapingHandler
7. Base64 编解码Base64Decoder 和 Base64Encoder。
创建和添加 ChannelHandler 的代码示例如下。
```java
.childHandler( new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast( new EchoServerHandler() );
}
});
```
6、**绑定并启动监听端口**。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将 ServerSocketChannel 注册到 Selector 上监听客户端连接。
7、**Selector 轮询**。由 Reactor线程 NioEventLoop 负责调度和执行 Selector 轮询操作,选择准备就绪的 Channel集合相关代码如下。
```java
public final class NioEventLoop extends SingleThreadEventLoop {
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
......
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
......
}
}
```
8、**当轮询到 准备就绪的Channel 之后,就由 Reactor线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。
![avatar](/images/Netty/ChannelPipeline的调度相关方法.png)
9、**执行 Netty 中 系统的ChannelHandler 和 用户添加定制的ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler相关代码如下。
```java
public class DefaultChannelPipeline implements ChannelPipeline {
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
}
```
### 结合 Netty源码 对服务端的创建过程进行解析
首先通过构造函数创建 ServerBootstrap实例随后通常会创建两个 EventLoopGroup实例 (也可以只创建一个并共享),代码如下。
```java
EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup iOGroup = new NioEventLoopGroup();
```
NioEventLoopGroup 实际就是一个 Reactor线程池负责调度和执行客户端的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过 ServerBootstrap 的 group()方法 将两个 EventLoopGroup实例 传入,代码如下。
```java
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
/**
* Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
* {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and
* {@link Channel}'s.
*/
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup;
return this;
}
}
```
其中 parentGroup对象 被设置进了 ServerBootstrap 的父类 AbstractBootstrap 中,代码如下。
```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
volatile EventLoopGroup group;
/**
* The {@link EventLoopGroup} which is used to handle all the events for the to-be-created
* {@link Channel}
*/
public B group(EventLoopGroup group) {
if (group == null) {
throw new NullPointerException("group");
}
if (this.group != null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return self();
}
}
```
该方法会被客户端和服务端重用,用于设置 工作IO线程执行和调度网络事件的读写。线程组和线程类型设置完成后需要设置 服务端Channel 用于端口监听和客户端链路接入。Netty 通过 Channel工厂类 来创建不同类型的 Channel对于服务端需要创建 NioServerSocketChannel。所以通过指定 Channel类型 的方式创建 Channel工厂。ReflectiveChannelFactory 可以根据 Channel的类型 通过反射创建 Channel的实例服务端需要创建的是 NioServerSocketChannel实例代码如下。
```java
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
" does not have a public non-arg constructor", e);
}
}
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
}
```
指定 NioServerSocketChannel 后,需要设置 TCP 的一些参数,作为服务端,主要是设置 TCP 的 backlog参数。
backlog 指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未链接队列 和 已连接队列,根据 TCP三次握手 的 三个子过程来分隔这两个队列。服务器处于 listen状态 时,收到客户端 syn过程(connect) 时在未完成队列中创建一个新的条目,然后用三次握手的第二个过程,即服务器的 syn响应客户端此条目在第三个过程到达前 (客户端对服务器 syn 的 ack) 一直保留在未完成连接队列中,如果三次握手完成,该条目将从未完成连接队列搬到已完成连接队列尾部。当进程调用 accept 时从已完成队列中的头部取出一个条目给进程当已完成队列为空时进程将睡眠直到有条目在已完成连接队列中才唤醒。backlog 被规定为两个队列总和的最大值,大多数实现默认值为 5但在高并发 Web服务器 中此值显然不够。 需要设置此值更大一些的原因是,未完成连接队列的长度可能因为客户端 syn 的到达及等待三次握手的第三个过程延时 而增大。Netty 默认的 backlog 为 100当然用户可以修改默认值这需要根据实际场景和网络状况进行灵活设置。
TCP参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。
![avatar](/images/Netty/ServerBootstrap的Handler模型.png)
本质区别就是ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的所有连接该监听端口的客户端都会执行它父类AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。
服务端启动的最后一步,就是绑定本地端口,启动服务,下面我们来分析下这部分代码。
```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
}
```
先看下上述代码调用的 initAndRegister()方法。它首先实例化了一个 NioServerSocketChannel类型 的 Channel对象。相关代码如下。
```java
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
```
NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作主要有以下三点。
```java
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
// 1、设置 Socket参数 和 NioServerSocketChannel 的附加属性
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
// 2、将 AbstractBootstrap 的 Handler 添加到 NioServerSocketChannel
// 的 ChannelPipeline 中
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
}
// 3、将用于服务端注册的 Handler ServerBootstrapAcceptor 添加到 ChannelPipeline 中
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
```
到此Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。
![avatar](/images/Netty/NioServerSocketChannel的ChannelPipeline.png)
最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor线程 的多路复用器上监听新客户端的接入,代码如下。
```java
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
protected abstract class AbstractUnsafe implements Unsafe {
/**
* 将完成初始化的 NioServerSocketChannel 注册到 Reactor线程
* 的多路复用器上,监听新客户端的接入
*/
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
......
// 首先判断是否是 NioEventLoop 自身发起的操作。如果是,则不存在并发操作,直接
// 执行 Channel注册如果由其他线程发起则封装成一个 Task 放入消息队列中异步执行。
// 此处,由于是由 ServerBootstrap 所在线程执行的注册操作,所以会将其封装成 Task 投递
// 到 NioEventLoop 中执行
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
......
}
}
}
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
// 该方法在本类中是一个空实现,下面看一下它在子类 AbstractNioChannel 中的实现
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
}
public abstract class AbstractNioChannel extends AbstractChannel {
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 将 NioServerSocketChannel 注册到 NioEventLoop 的 多路复用器Selector 上
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
......
}
}
}
}
```
到此,服务端监听启动部分源码已经分析完成。
## 结合 Netty源码 对客户端接入过程进行解析
负责处理网络读写、连接和客户端请求接入的 Reactor线程 就是 NioEventLoop下面我们看下 NioEventLoop 是如何处理新的客户端连接接入的。当 多路复用器 检测到新的准备就绪的 Channel 时,默认执行 processSelectedKeysOptimized()方法,代码如下。
```java
public final class NioEventLoop extends SingleThreadEventLoop {
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
selectedKeys.keys[i] = null;
final Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
// 根据就绪的操作位 SelectionKey执行不同的操作
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
// 根据就绪的操作位 SelectionKey执行不同的操作
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
// 由于不同的 Channel 执行不同的操作,所以 NioUnsafe 被设计成接口
// 由不同的 Channel 内部的 NioUnsafe实现类 负责具体实现
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
// read()方法 的实现有两个,分别是 NioByteUnsafe 和 NioMessageUnsafe
// 对于 NioServerSocketChannel它使用的是 NioMessageUnsafe
// 下面看一下 NioMessageUnsafe 对 read() 方法的实现
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
}
public abstract class AbstractNioMessageChannel extends AbstractNioChannel {
private final class NioMessageUnsafe extends AbstractNioUnsafe {
private final List<Object> readBuf = new ArrayList<Object>();
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
// 接收新的客户端连接并创建 NioSocketChannel
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// 接收到新的客户端连接后,触发 ChannelPipeline 的 channelRead方法。
// 事件在 ChannelPipeline 中传递,执行 ServerBootstrapAcceptor 的
// channelRead方法
pipeline.fireChannelRead(readBuf.get(i));
}
......
}
}
}
}
public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel {
/**
* 接收新的客户端连接并创建 NioSocketChannel
*/
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
......
}
return 0;
}
}
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
/**
* 该方法主要分为如下三个步骤。
*/
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
// 第一步:将启动时传入的 childHandler 加入到客户端 SocketChannel 的 ChannelPipeline 中
child.pipeline().addLast(childHandler);
// 第二步:设置客户端 SocketChannel 的 TCP参数
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
// 第三步:注册 SocketChannel 到多路复用器
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
}
}
```
下面我们展开看下 NioSocketChannel 的 register()方法。NioSocketChannel 的注册方法与 ServerSocketChannel 的一致, 也是将 Channel 注册到 Reactor线程 的多路复用器上。由于注册的操作位是 0所以此时 NioSocketChannel 还不能读取客户端发送的消息,下面我们看看 是什么时候修改监听操作位为 OP_READ 的。
执行完注册操作之后,紧接着会触发 ChannelReadComplete 事件。我们继续分析 ChannelReadComplete 在 ChannelPipeline 中的处理流程Netty 的 Header 和 Tail 本身不关注 ChannelReadComplete事件 就直接透传,执行完 ChannelReadComplete 后,接着执行 PipeLine 的 read()方法,最终执行 HeadHandler 的 read()方法。
HeadHandler 的 read()方法用来将网络操作位修改为读操作。创建 NioSocketChannel 的时候已经将 AbstractNioChannel 的 readInterestOp 设置为 OP_ READ这样执行 selectionKey. interestOps(interestOps | readInterestOp)操作 时就会把操作位设置为 OP_READ。代码如下。
```java
public abstract class AbstractNioByteChannel extends AbstractNioChannel {
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
}
```
到此,新接入的客户端连接处理完成,可以进行网络读写等 IO操作。

File diff suppressed because it is too large Load Diff

@ -1,43 +1,42 @@
最近在看 SpringAOP 部分的源码,所以对 JDK 动态代理具体是如何实现的这件事产生了很高的兴趣,而且能从源码上了解这个原理的话,也有助于对 spring-aop 模块的理解。话不多说,上代码。
最近在看 Spring AOP 部分的源码所以对JDK动态代理具体是如何实现的这件事产生了很高的兴趣而且能从源码上了解这个原理的话也有助于对 spring-aop 模块的理解。话不多说,上代码。
```java
/**
* 一般会使用实现了 InvocationHandler接口的类 作为代理对象生成工厂,
* 并且通过持有被代理对象 target在 invoke() 方法中对被代理对象的目标方法进行调用和增强,
* 这些我们都能通过下面这段代码看懂但代理对象是如何生成的invoke() 方法又是如何被调用的呢?
* 一般会使用实现了 InvocationHandler接口 的类作为代理对象的生产工厂,
* 并且通过持有 被代理对象target在 invoke()方法 中对被代理对象的目标方法进行调用和增强,
* 这些我们都能通过下面这段代码看懂但代理对象是如何生成的invoke()方法 又是如何被调用的呢?
*/
public class ProxyFactory implements InvocationHandler{
public class ProxyFactory implements InvocationHandler {
private Object target = null;
private Object target = null;
public Object getInstanse(Object target){
public Object getInstanse(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object ret = null;
System.out.println("前置增强");
ret = method.invoke(target, args);
System.out.println("后置增强");
return ret;
}
Object ret = null;
System.out.println("前置增强");
ret = method.invoke(target, args);
System.out.println("后置增强");
return ret;
}
}
/**
* 实现了接口 MyInterface 和接口的 play() 方法,可以作为被代理类
* 实现了 接口MyInterface 和接口的 play()方法,可以作为被代理类
*/
public class TargetObject implements MyInterface {
@Override
public void play() {
System.out.println("妲己,陪你玩 ~");
}
@Override
public void play() {
System.out.println("妲己,陪你玩 ~");
}
}
/**
@ -45,57 +44,57 @@ public class TargetObject implements MyInterface {
*/
public class ProxyTest {
public static void main(String[] args) {
TargetObject target = new TargetObject();
// ProxyFactory 实现了 InvocationHandler 接口,其中的 getInstanse() 方法利用 Proxy 类帮助生成了
// target 目标对象的代理对象,并返回;且 ProxyFactory 持有对 target 的引用,可以在 invoke() 中完成对 target 相应方法
// 的调用,以及目标方法前置后置的增强处理
ProxyFactory proxyFactory = new ProxyFactory();
// 这个 mi 就是 JDK 的 Proxy 类生成的代理类$Proxy0 的对象,这个对象中的方法都持有对 invoke() 方法的回调
// 所以当调用其方法时,就能够执行 invoke() 中的增强处理
MyInterface mi = (MyInterface) proxyFactory.getInstanse(target);
// 这样可以看到 mi 的 Class 到底是什么
System.out.println(mi.getClass());
// 这里实际上调用的就是$Proxy0 中对 play() 方法的实现,可以看到 play 方法通过 super.h.invoke()
// 完成了对 InvocationHandler 对象 proxyFactory 的 invoke() 方法的回调
// 所以我才能够通过 invoke() 方法实现对 target 对象方法的前置后置增强处理
mi.play();
// 总的来说,就是在 invoke() 方法中完成 target 目标方法的调用,及前置后置增强
// 然后通过生成的代理类完成对对 invoke() 的回调
}
/**
* 将 ProxyGenerator 生成的动态代理类的输出到文件中,利用反编译工具 luyten 等就可
* 以看到生成的代理类的源码咯,下面给出了其反编译好的代码实现
*/
@Test
public void generatorSrc(){
byte[] bytesFile = ProxyGenerator.generateProxyClass("$Proxy0", TargetObject.class.getInterfaces());
FileOutputStream fos = null;
try{
String path = System.getProperty("user.dir") + "\\$Proxy0.class";
File file = new File(path);
fos = new FileOutputStream(file);
fos.write(bytesFile);
fos.flush();
} catch (Exception e){
e.printStackTrace();
} finally{
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TargetObject target = new TargetObject();
// ProxyFactory 实现了 InvocationHandler接口其中的 getInstanse()方法 利用 Proxy类
// 生成了 target目标对象 的代理对象,并返回;且 ProxyFactory 持有对 target 的引用,可以在
// invoke() 中完成对 target 相应方法的调用,以及目标方法前置后置的增强处理
ProxyFactory proxyFactory = new ProxyFactory();
// 这个 mi 就是 JDK 的 Proxy类 动态生成的代理类 $Proxy0 的实例,该实例中的方法都持有对
// invoke()方法 的回调,所以当调用其方法时,就能够执行 invoke() 中的增强处理
MyInterface mi = (MyInterface) proxyFactory.getInstanse(target);
// 这样可以看到 mi 的 Class 到底是什么
System.out.println(mi.getClass());
// 这里实际上调用的就是 $Proxy0代理类 中对 play()方法 的实现,结合下面的代码可以看到
// play()方法 通过 super.h.invoke() 完成了对 InvocationHandler对象(proxyFactory)中
// invoke()方法 的回调,所以我们才能够通过 invoke()方法 实现对 target对象 方法的
// 前置后置增强处理
mi.play();
// 总的来说,就是在 invoke()方法 中完成 target目标方法 的调用,及前置后置增强,
// JDK 动态生成的代理类中对 invoke()方法 进行了回调
}
/**
* 将 ProxyGenerator 生成的动态代理类的输出到文件中,利用反编译工具 luyten 等就可
* 以看到生成的代理类的源码咯,下面给出了其反编译好的代码实现
*/
@Test
public void generatorSrc(){
byte[] bytesFile = ProxyGenerator.generateProxyClass("$Proxy0", TargetObject.class.getInterfaces());
FileOutputStream fos = null;
try{
String path = System.getProperty("user.dir") + "\\$Proxy0.class";
File file = new File(path);
fos = new FileOutputStream(file);
fos.write(bytesFile);
fos.flush();
} catch (Exception e){
e.printStackTrace();
} finally{
try {
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* Proxy 生成的代理类,可以看到,其继承了 Proxy并且实现了被代理类的接口
* Proxy 生成的代理类,可以看到,其继承了 Proxy并且实现了 被代理类的接口MyInterface
*/
public final class $Proxy0 extends Proxy implements MyInterface
{
public final class $Proxy0 extends Proxy implements MyInterface {
private static Method m1;
private static Method m0;
private static Method m3;
@ -105,7 +104,7 @@ public final class $Proxy0 extends Proxy implements MyInterface
try {
$Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
$Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
//实例化 MyInterface 的 play 方法
// 实例化 MyInterface 的 play()方法
$Proxy0.m3 = Class.forName("com.shuitu.test.MyInterface").getMethod("play", (Class<?>[])new Class[0]);
$Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
}
@ -123,9 +122,9 @@ public final class $Proxy0 extends Proxy implements MyInterface
public final void play() {
try {
//这个 h 其实就是我们调用 Proxy.newProxyInstance() 方法时传进去的 ProxyFactory 对象,
//该对象的 invoke() 方法中实现了对目标对象的目标方法的增强。看到这里,利用动态代理实现方法增强的
//调用原理就全部理清咯
// 这个 h 其实就是我们调用 Proxy.newProxyInstance()方法 时传进去的 ProxyFactory对象(它实现了
// InvocationHandler接口)该对象的 invoke()方法 中实现了对目标对象的目标方法的增强。
// 看到这里,利用动态代理实现方法增强的实现原理就全部理清咯
super.h.invoke(this, $Proxy0.m3, null);
}
catch (Error | RuntimeException error) {
@ -171,11 +170,5 @@ public final class $Proxy0 extends Proxy implements MyInterface
throw new UndeclaredThrowableException(t);
}
}
}
```
#### 动态代理原理图
![avatar](/images/动态代理原理图1.png)
![avatar](/images/动态代理原理图2.png)

@ -1,352 +1,362 @@
## 前言
之前一直想系统的拜读一下 spring 的源码,看看它到底是如何吸引身边的大神们对它的设计赞不绝口,虽然每天工作很忙,每天下班后总感觉脑子内存溢出,想去放松一下,但总是以此为借口,恐怕会一直拖下去。所以每天下班虽然有些疲惫,但还是按住自己啃下这块硬骨头。
spring 源码这种东西真的是一回生二回熟,第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向,不知道这个方法调用的是哪个父类的实现,另一个方法又调的是哪个子类的实现,但当你耐下心来多走几遍,会发现越看越熟练,每次都能 get 到新的点。
spring 源码这种东西真的是一回生二回熟,第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向,不知道看到的这些方法调用的是哪个父类的实现IoC相关的类图实在太复杂咯继承体系又深又广,但当你耐下心来多走几遍,会发现越看越熟练,每次都能 get 到新的点。
另外,对于第一次看 spring 源码的同学,建议先在 B 站上搜索相关视频看一下然后再结合计文柯老师的《spring 技术内幕》深入理解,最后再输出自己的理解加强印象。
另外,对于第一次看 spring 源码的同学,建议先在 B 站上搜索相关视频看一下然后再结合计文柯老师的《spring 技术内幕》深入理解,最后再输出自己的理解(写博文或部门内部授课)加强印象。
首先对于我们新手来说,还是从我们最常用的两个 IoC 容器开始分析,这次我们先分析 FileSystemXmlApplicationContext 这个 IoC 容器的具体实现ClassPathXmlApplicationContext 留着下次讲解。
PS可以结合我 GitHub 上对 spring 框架源码的翻译注解一起看,会更有助于各位开发姥爷的理解。
地址:
PS可以结合我 GitHub 上对 Spring 框架源码的翻译注释一起看,会更有助于各位同学的理解。地址:
spring-beans https://github.com/AmyliaY/spring-beans-reading
spring-context https://github.com/AmyliaY/spring-context-reading
## FileSystemXmlApplicationContext 的构造方法
当我们传入一个 spring 配置文件去实例化 FileSystemXmlApplicationContext() 时,可以看一下它的构造方法都做了什么。
## 正文
当我们传入一个 Spring 配置文件去实例化 FileSystemXmlApplicationContext 时,可以看一下它的构造方法都做了什么。
```java
/**
* 下面这 4 个构造方法都调用了第 5 个构造方法
* @param configLocation
* @throws BeansException
*/
// configLocation 包含了 BeanDefinition 所在的文件路径
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
// 可以定义多个 BeanDefinition 所在的文件路径
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
// 在定义多个 BeanDefinition 所在的文件路径 的同时,还能指定自己的双亲 IoC 容器
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
/**
* 如果应用直接使用 FileSystemXmlApplicationContext 进行实例化,则都会进到这个构造方法中来
* @param configLocations
* @param refresh
* @param parent
* @throws BeansException
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
//动态地确定用哪个加载器去加载我们的配置文件
super(parent);
//告诉读取器 配置文件放在哪里,该方法继承于爷类 AbstractRefreshableApplicationContext
setConfigLocations(configLocations);
if (refresh) {
//容器初始化
refresh();
}
}
/**
* 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作
* 本方法是在其父类 DefaultResourceLoader 的 getResource 方法中被调用的,
*/
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
/**
* 下面这 4 个构造方法都调用了第 5 个构造方法
* @param configLocation
* @throws BeansException
*/
// configLocation 包含了 BeanDefinition 所在的文件路径
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
// 可以定义多个 BeanDefinition 所在的文件路径
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
// 在定义多个 BeanDefinition 所在的文件路径 的同时,还能指定自己的双亲 IoC 容器
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
/**
* 如果应用直接使用 FileSystemXmlApplicationContext 进行实例化,则都会进到这个构造方法中来
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
// 动态地确定用哪个加载器去加载我们的配置文件
super(parent);
// 告诉读取器 配置文件放在哪里,该方法继承于爷类 AbstractRefreshableApplicationContext
setConfigLocations(configLocations);
if (refresh) {
// 容器初始化
refresh();
}
}
/**
* 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作
* 本方法是在其父类 DefaultResourceLoader 的 getResource 方法中被调用的,
*/
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
```
## 看看其父类 AbstractApplicationContext 实现的 refresh() 方法,该方法就是 IoC 容器初始化的入口类
看看其父类 AbstractApplicationContext 实现的 refresh() 方法,该方法就是 IoC 容器初始化的入口类
```java
/**
* 容器初始化的过程BeanDefinition 的 Resource 定位、BeanDefinition 的载入、BeanDefinition 的注册。
* BeanDefinition 的载入和 bean 的依赖注入是两个独立的过程,依赖注入一般发生在 应用第一次通过 getBean() 方法从容器获取 bean 时。
*
* 另外需要注意的是IoC 容器有一个预实例化的配置(即,将 AbstractBeanDefinition 中的 lazyInit 属性设为 true使用户可以对容器的初始化
* 过程做一个微小的调控lazyInit 设为 false 的 bean 将在容器初始化时进行依赖注入,而不会等到 getBean() 方法调用时才进行
*/
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 调用容器准备刷新的方法,获取容器的当前时间,同时给容器设置同步标识
prepareRefresh();
// 告诉子类启动 refreshBeanFactory() 方法Bean 定义资源文件的载入从子类的 refreshBeanFactory() 方法启动开始
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 为 BeanFactory 配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
// 为容器的某些子类指定特殊的 BeanPost 事件处理器
postProcessBeanFactory(beanFactory);
// 调用所有注册的 BeanFactoryPostProcessor 的 Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 为 BeanFactory 注册 BeanPost 事件处理器.
// BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
// 初始化信息源,和国际化相关.
initMessageSource();
// 初始化容器事件传播器
initApplicationEventMulticaster();
// 调用子类的某些特殊 Bean 初始化方法
onRefresh();
// 为事件传播器注册事件监听器.
registerListeners();
// 初始化 Bean并对 lazy-init 属性进行处理
finishBeanFactoryInitialization(beanFactory);
// 初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
// 销毁以创建的单态 Bean
destroyBeans();
// 取消 refresh 操作,重置容器的同步标识.
cancelRefresh(ex);
throw ex;
}
}
}
/**
* 容器初始化的过程BeanDefinition 的 Resource 定位、BeanDefinition 的载入、BeanDefinition 的注册。
* BeanDefinition 的载入和 bean 的依赖注入是两个独立的过程,依赖注入一般发生在 应用第一次通过
* getBean() 方法从容器获取 bean 时。
*
* 另外需要注意的是IoC 容器有一个预实例化的配置(即,将 AbstractBeanDefinition 中的 lazyInit 属性
* 设为 true使用户可以对容器的初始化过程做一个微小的调控lazyInit 设为 false 的 bean
* 将在容器初始化时进行依赖注入,而不会等到 getBean() 方法调用时才进行
*/
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 调用容器准备刷新,获取容器的当前时间,同时给容器设置同步标识
prepareRefresh();
// 告诉子类启动 refreshBeanFactory() 方法BeanDefinition 资源文件的载入从子类的 refreshBeanFactory() 方法启动开始
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 为 BeanFactory 配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
// 为容器的某些子类指定特殊的 BeanPost 事件处理器
postProcessBeanFactory(beanFactory);
// 调用所有注册的 BeanFactoryPostProcessor 的 Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 为 BeanFactory 注册 BeanPost 事件处理器.
// BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
// 初始化信息源,和国际化相关.
initMessageSource();
// 初始化容器事件传播器
initApplicationEventMulticaster();
// 调用子类的某些特殊 Bean 初始化方法
onRefresh();
// 为事件传播器注册事件监听器.
registerListeners();
// 初始化 Bean并对 lazy-init 属性进行处理
finishBeanFactoryInitialization(beanFactory);
// 初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
// 销毁以创建的单态 Bean
destroyBeans();
// 取消 refresh 操作,重置容器的同步标识.
cancelRefresh(ex);
throw ex;
}
}
}
```
## 看看 obtainFreshBeanFactory 方法,该方法告诉了子类去刷新内部的 beanFactory
看看 obtainFreshBeanFactory() 方法,该方法告诉了子类去刷新内部的 beanFactory
```java
/**
* Tell the subclass to refresh the internal bean factory.
* 告诉子类去刷新内部的 beanFactory
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 自己定义了抽象的 refreshBeanFactory() 方法,具体实现交给了自己的子类
refreshBeanFactory();
// getBeanFactory() 也是一个抽象方法,委派给子类实现
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
/**
* Tell the subclass to refresh the internal bean factory.
* 告诉子类去刷新内部的 beanFactory
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 自己定义了抽象的 refreshBeanFactory() 方法,具体实现交给了自己的子类
refreshBeanFactory();
// getBeanFactory() 也是一个抽象方法,交由子类实现
// 看到这里是不是很容易想起 “模板方法模式”,父类在模板方法中定义好流程,定义好抽象方法
// 具体实现交由子类完成
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
```
## AbstractRefreshableApplicationContext 中对 refreshBeanFactory() 方法的实现
FileSystemXmlApplicationContext 从上层体系的各抽象类中继承了大量的方法实现,抽象类中抽取大量公共行为进行具体实现,留下 abstract 的个性化方法交给具体的子类实现,这是一个很好的 OOP 编程设计,我们在自己编码时也可以尝试这样设计自己的类图。理清 FileSystemXmlApplicationContext 的上层体系设计,就不易被各种设计模式搞晕咯。
下面看一下 AbstractRefreshableApplicationContext 中对 refreshBeanFactory() 方法的实现。FileSystemXmlApplicationContext 从上层体系的各抽象类中继承了大量的方法实现,抽象类中抽取大量公共行为进行具体实现,留下 abstract 的个性化方法交给具体的子类实现,这是一个很好的 OOP 编程设计,我们在自己编码时也可以尝试这样设计自己的类图。理清 FileSystemXmlApplicationContext 的上层体系设计,就不易被各种设计模式搞晕咯。
```java
// 在这里完成了容器的初始化,并赋值给自己 private 的 beanFactory 属性,为下一步调用做准备
// 从父类 AbstractApplicationContext 继承的抽象方法,自己做了实现
@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果已经建立了 IoC 容器,则销毁并关闭容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建 IoC 容器DefaultListableBeanFactory 类实现了 ConfigurableListableBeanFactory 接口
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
// 对 IoC 容器进行定制化,如设置启动参数,开启注解的自动装配等
customizeBeanFactory(beanFactory);
// 载入 BeanDefinition在当前类中只定义了抽象的 loadBeanDefinitions 方法,具体实现 调用子类容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
// 给自己的属性赋值
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
/**
* 在这里完成了容器的初始化,并赋值给自己私有的 beanFactory 属性,为下一步调用做准备
* 从父类 AbstractApplicationContext 继承的抽象方法,自己做了实现
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
// 如果已经建立了 IoC 容器,则销毁并关闭容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建 IoC 容器DefaultListableBeanFactory 类实现了 ConfigurableListableBeanFactory 接口
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
// 对 IoC 容器进行定制化,如设置启动参数,开启注解的自动装配等
customizeBeanFactory(beanFactory);
// 载入 BeanDefinition在当前类中只定义了抽象的 loadBeanDefinitions() 方法,具体实现 调用子类容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
// 给自己的属性赋值
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
```
## AbstractXmlApplicationContext 中对 loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的实现
AbstractXmlApplicationContext 中对 loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的实现
```java
/*
* 实现了爷类 AbstractRefreshableApplicationContext 的抽象方法
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// DefaultListableBeanFactory 实现了 BeanDefinitionRegistry 接口,在初始化 XmlBeanDefinitionReader 时
// 将 BeanDefinition 注册器注入该 BeanDefinition 读取器
// 创建 用于从 Xml 中读取 BeanDefinition 的读取器,并通过回调设置到 IoC 容器中去,容器使用该读取器读取 BeanDefinition 资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 为 beanDefinition 读取器设置 资源加载器,由于本类的基类 AbstractApplicationContext
// 继承了 DefaultResourceLoader因此本容器自身也是一个资源加载器
beanDefinitionReader.setResourceLoader(this);
// 设置 SAX 解析器SAXsimple API for XML是另一种 XML 解析方法。相比于 DOMSAX 速度更快,占用内存更小。
// 它逐行扫描文档,一边扫描一边解析。相比于先将整个 XML 文件扫描近内存,再进行解析的 DOMSAX 可以在解析文档的任意时刻停止解析,但操作也比 DOM 复杂。
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 初始化 beanDefinition 读取器,该方法同时启用了 Xml 的校验机制
initBeanDefinitionReader(beanDefinitionReader);
// Bean 读取器真正实现加载的方法
loadBeanDefinitions(beanDefinitionReader);
}
/*
* 实现了基类 AbstractRefreshableApplicationContext 的抽象方法
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// DefaultListableBeanFactory 实现了 BeanDefinitionRegistry 接口,在初始化 XmlBeanDefinitionReader 时
// 将 BeanDefinition 注册器注入该 BeanDefinition 读取器
// 创建用于从 Xml 中读取 BeanDefinition 的读取器,并通过回调设置到 IoC 容器中去,容器使用该读取器读取 BeanDefinition 资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 为 beanDefinition 读取器设置 资源加载器,由于本类的基类 AbstractApplicationContext
// 继承了 DefaultResourceLoader因此本容器自身也是一个资源加载器
beanDefinitionReader.setResourceLoader(this);
// 设置 SAX 解析器SAXsimple API for XML是另一种 XML 解析方法。相比于 DOMSAX 速度更快,占用内存更小。
// 它逐行扫描文档,一边扫描一边解析。相比于先将整个 XML 文件扫描近内存,再进行解析的 DOMSAX 可以在解析文档的
// 任意时刻停止解析,但操作也比 DOM 复杂。
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 初始化 beanDefinition 读取器,该方法同时启用了 Xml 的校验机制
initBeanDefinitionReader(beanDefinitionReader);
// Bean 读取器真正实现加载的方法
loadBeanDefinitions(beanDefinitionReader);
}
```
## 继续看 AbstractXmlApplicationContext 中 loadBeanDefinitions 的重载方法
继续看 AbstractXmlApplicationContext 中 loadBeanDefinitions() 的重载方法
```java
// 用传进来的 XmlBeanDefinitionReader 读取器加载 Xml 文件中的 BeanDefinition
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
/**
* ClassPathXmlApplicationContext 与 FileSystemXmlApplicationContext
* 在这里的调用出现分歧,各自按不同的方式加载解析 Resource 资源
* 最后在具体的解析和 BeanDefinition 定位上又会殊途同归
*/
// 获取存放了 BeanDefinition 的所有 Resource
// FileSystemXmlApplicationContext 类未对 getConfigResources() 进行重新,
// 所以调用父类的return null。
// 而 ClassPathXmlApplicationContext 对该方法进行了重写,返回设置的值
Resource[] configResources = getConfigResources();
if (configResources != null) {
// Xml Bean 读取器调用其父类 AbstractBeanDefinitionReader 读取定位的 Bean 定义资源
reader.loadBeanDefinitions(configResources);
}
// 调用父类 AbstractRefreshableConfigApplicationContext 实现的返回值为 String[] 的 getConfigLocations() 方法,
// 优先返回 FileSystemXmlApplicationContext 构造方法中调用 setConfigLocations() 方法设置的资源
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// XmlBeanDefinitionReader 读取器调用其父类 AbstractBeanDefinitionReader 的方法从配置位置加载 BeanDefinition
reader.loadBeanDefinitions(configLocations);
}
}
/**
* 用传进来的 XmlBeanDefinitionReader 读取器加载 Xml 文件中的 BeanDefinition
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
/**
* ClassPathXmlApplicationContext 与 FileSystemXmlApplicationContext 在这里的调用出现分歧,
* 各自按不同的方式加载解析 Resource 资源,最后在具体的解析和 BeanDefinition 定位上又会殊途同归。
*/
// 获取存放了 BeanDefinition 的所有 ResourceFileSystemXmlApplicationContext 类未对
// getConfigResources() 进行重写所以调用父类的return null。
// 而 ClassPathXmlApplicationContext 对该方法进行了重写,返回设置的值
Resource[] configResources = getConfigResources();
if (configResources != null) {
// XmlBeanDefinitionReader 调用其父类 AbstractBeanDefinitionReader 的方法加载 BeanDefinition
reader.loadBeanDefinitions(configResources);
}
// 调用父类 AbstractRefreshableConfigApplicationContext 的实现,
// 优先返回 FileSystemXmlApplicationContext 构造方法中调用 setConfigLocations() 方法设置的资源
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// XmlBeanDefinitionReader 调用其父类 AbstractBeanDefinitionReader 的方法从配置位置加载 BeanDefinition
reader.loadBeanDefinitions(configLocations);
}
}
```
## AbstractBeanDefinitionReader 中对 loadBeanDefinitions 方法的各种重载及调用
AbstractBeanDefinitionReader 中对 loadBeanDefinitions 方法的各种重载及调用
```java
// loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String)
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
// 计数 加载了多少个配置文件
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
// 重载方法之一,调用了下面的 loadBeanDefinitions(String, Set<Resource>) 方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
// 获取在 IoC 容器初始化过程中设置的资源加载器
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 在实例化 XmlBeanDefinitionReader 后 IoC 容器将自己注入进该读取器作为 resourceLoader 属性
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 将指定位置的 BeanDefinition 资源文件解析为 IoC 容器封装的资源
// 加载多个指定位置的 BeanDefinition 资源文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
/**
*
* AbstractApplicationContext 继承了 DefaultResourceLoader所以 AbstractApplicationContext
* 及其子类都会调用 DefaultResourceLoader 中的实现,将指定位置的资源文件解析为 Resource
* 至此完成了对 BeanDefinition 的资源定位
*
*/
Resource resource = resourceLoader.getResource(location);
// 从 resource 中加载 BeanDefinitionloadCount 为加载的 BeanDefinition 个数
// 该 loadBeanDefinitions() 方法来自其 implements 的 BeanDefinitionReader 接口,
// 且本类是一个抽象类,并未对该方法进行实现。而是交由子类进行实现,如果是用 xml 文件进行
// IoC 容器初始化的,则调用 XmlBeanDefinitionReader 中的实现
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
/**
* loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String)
*/
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
// 计数器,统计加载了多少个配置文件
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
/**
* 重载方法之一,调用了下面的 loadBeanDefinitions(String, Set<Resource>) 方法
*/
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
/**
* 获取在 IoC 容器初始化过程中设置的资源加载器
*/
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 在实例化 XmlBeanDefinitionReader 后 IoC 容器将自己注入进该读取器作为 resourceLoader 属性
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 将指定位置的 BeanDefinition 资源文件解析为 IoC 容器封装的资源
// 加载多个指定位置的 BeanDefinition 资源文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
/**
*
* AbstractApplicationContext 继承了 DefaultResourceLoader所以 AbstractApplicationContext
* 及其子类都会调用 DefaultResourceLoader 中的实现,将指定位置的资源文件解析为 Resource
* 至此完成了对 BeanDefinition 的资源定位
*
*/
Resource resource = resourceLoader.getResource(location);
// 从 resource 中加载 BeanDefinitionloadCount 为加载的 BeanDefinition 个数
// 该 loadBeanDefinitions() 方法来自其实现的 BeanDefinitionReader 接口,
// 且本类是一个抽象类,并未对该方法进行实现。而是交由子类进行实现,如果是用 xml 文件进行
// IoC 容器的初始化,则调用 XmlBeanDefinitionReader 中的实现
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
```
## resourceLoader 的 getResource() 方法有多种实现,看清 FileSystemXmlApplicationContext 的继承体系就可以明确,其走的是 DefaultResourceLoader 中的实现
resourceLoader 的 getResource() 方法有多种实现,看清 FileSystemXmlApplicationContext 的继承体系就可以明确,其走的是 DefaultResourceLoader 中的实现
```java
// 获取 Resource 的具体实现方法
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 如果 location 是类路径的方式,返回 ClassPathResource 类型的文件资源对象
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 如果是 URL 方式,返回 UrlResource 类型的文件资源对象,
// 否则将抛出的异常进入 catch 代码块,返回另一种资源对象
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// 如果既不是 classpath 标识,又不是 URL 标识的 Resource 定位,则调用
// 容器本身的 getResourceByPath 方法获取 Resource
// 根据实例化的子类对象,调用其子类对象中重写的此方法,
// 如 FileSystemXmlApplicationContext 子类中对此方法的重新
return getResourceByPath(location);
}
}
}
/**
* 获取 Resource 的具体实现方法
*/
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 如果 location 是类路径的方式,返回 ClassPathResource 类型的文件资源对象
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 如果是 URL 方式,返回 UrlResource 类型的文件资源对象,
// 否则将抛出的异常进入 catch 代码块,返回另一种资源对象
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// 如果既不是 classpath 标识,又不是 URL 标识的 Resource 定位,则调用容器本身的
// getResourceByPath() 方法获取 Resource。根据实例化的子类对象调用其子类对象中
// 重写的此方法,如 FileSystemXmlApplicationContext 子类中对此方法的重写
return getResourceByPath(location);
}
}
}
```
## 其中的 getResourceByPath(location) 方法的实现则是在 FileSystemXmlApplicationContext 中完成的
其中的 getResourceByPath(location) 方法的实现则是在 FileSystemXmlApplicationContext 中完成的
```java
/**
* 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作
* 本方法是在 DefaultResourceLoader 的 getResource 方法中被调用的,
*/
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
/**
* 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作
* 本方法是在 DefaultResourceLoader 的 getResource() 方法中被调用的,
*/
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
```
至此我们可以看到FileSystemXmlApplicationContext 的 getResourceByPath() 方法返回了一个 FileSystemResource 对象,接下来 spring 就可以对这个对象进行相关的 I/O 操作,进行 BeanDefinition 的读取和载入了。

File diff suppressed because it is too large Load Diff

@ -1,116 +1,133 @@
这篇文章分享一下 spring IoC 容器初始化第三部分的代码,也就是将前面解析得到的 BeanDefinition 注册进 IoC 容器,其实就是存入一个 ConcurrentHashMap<String, BeanDefinition> 中。
PS可以结合我 GitHub 上对 spring 框架源码的翻译注解一起看,会更有助于各位同学理解,地址:
## 前言
这篇文章分享一下 spring IoC 容器初始化第三部分的代码,也就是将前面解析出来的 BeanDefinition对象 注册进 IoC 容器,其实就是存入一个 ConcurrentHashMap<String, BeanDefinition> 中。
PS可以结合我 GitHub 上对 Spring 框架源码的翻译注释一起看,会更有助于各位同学理解,地址:
spring-beans https://github.com/AmyliaY/spring-beans-reading
spring-context https://github.com/AmyliaY/spring-context-reading
## 1、回过头看一下前面在 DefaultBeanDefinitionDocumentReader 中实现的 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 方法
## 正文
回过头看一下前面在 DefaultBeanDefinitionDocumentReader 中实现的 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 方法。
```java
// 解析 Bean 定义资源 Document 对象的普通元素
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
/**
* 将 .xml 文件中的元素解析成 BeanDefinition对象并注册到 IoC容器 中
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// BeanDefinitionHolder 是对 BeanDefinition 的封装,即 BeanDefinition 的封装类
// 对 Document 对象中 <Bean> 元素的解析由 BeanDefinitionParserDelegate 实现
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 对 bdHolder 进行包装处理
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
/**
*
* 向 Spring IoC 容器注册解析 BeanDefinition这是 BeanDefinition 向 IoC 容器注册的入口
*
*/
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 在完成向 Spring IOC 容器注册解析得到的 Bean 定义之后,发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
// BeanDefinitionHolder 是对 BeanDefinition 的进一步封装,它持有一个 BeanDefinition 对象 及其对应
// 的 beanName、aliases别名。
// 对 Document 对象中 <Bean> 元素的解析由 BeanDefinitionParserDelegate 实现
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 对 bdHolder 进行包装处理
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
/**
*
* 向 IoC 容器注册解析完成的 BeanDefinition对象这是 BeanDefinition 向 IoC 容器注册的入口
*
*/
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 在完成向 IOC容器 注册 BeanDefinition对象 之后,发送注册事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
```
## 2、BeanDefinitionReaderUtils 的 registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法
接着看一下 BeanDefinitionReaderUtils 的 registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法
```java
// 将解析的 BeanDefinitionHold 注册到容器中
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
/**
* 将解析到的 BeanDefinition对象 注册到 IoC容器
*/
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 获取解析的 BeanDefinition 的名称
String beanName = definitionHolder.getBeanName();
/**
*
* 开始向 IOC 容器注册 BeanDefinition
*
*/
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 获取解析的 <bean>元素 的名称 beanName
String beanName = definitionHolder.getBeanName();
/**
*
* 开始向 IoC容器 注册 BeanDefinition对象
*
*/
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 如果解析的 BeanDefinition 有别名,向容器为其注册别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
}
}
}
// 如果解析的 <bean>元素 有别名alias向容器中注册别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
}
}
}
```
## 3、BeanDefinitionRegistry 中的 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法在 DefaultListableBeanFactory 实现类中的具体实现
BeanDefinitionRegistry 中的 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法在 DefaultListableBeanFactory 实现类中的具体实现
```java
// 向 IoC 容器注册解析的 BeanDefinito
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
/** 按注册顺序排列的 beanDefinition名称列表(即 beanName) */
private final List<String> beanDefinitionNames = new ArrayList<String>();
// 校验解析的 BeanDefiniton
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
/** IoC容器 的实际体现key --> beanNamevalue --> BeanDefinition对象 */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
// 注册的过程中需要线程同步,以保证数据的一致性
synchronized (this.beanDefinitionMap) {
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
/**
* 向 IoC容器 注册解析的 beanName 和 BeanDefinition对象
*/
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 检查是否有同名的 BeanDefinition 已经在 IOC 容器中注册,如果已经注册,
// 并且不允许覆盖已注册的 BeanDefinition则抛出注册失败异常
// allowBeanDefinitionOverriding 默认为 true
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else {// 如果允许覆盖,则同名的 Bean后注册的覆盖先注册的
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
else {// IOC 容器中没有已经注册同名的 Bean按正常注册流程注册
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 重置所有已经注册过的 BeanDefinition 的缓存
resetBeanDefinition(beanName);
}
```
## 最后看一下 spring 的 IoC 容器在代码中最直接的体现
```java
// 存储注册信息的 BeanDefinition 集合,也就是所谓的 IoC 容器
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
// 校验解析的 BeanDefiniton对象
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
// 注册的过程中需要线程同步,以保证数据的一致性
synchronized (this.beanDefinitionMap) {
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
// 检查是否有同名(beanName)的 BeanDefinition 存在于 IoC容器 中,如果已经存在,且不允许覆盖
// 已注册的 BeanDefinition则抛出注册异常allowBeanDefinitionOverriding 默认为 true
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
// 如果允许覆盖同名的 bean后注册的会覆盖先注册的
else {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
// 若该 beanName 在 IoC容器 中尚未注册,将其注册到 IoC容器中
else {
// 将 beanName 注册到 beanDefinitionNames列表
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
// beanDefinitionMap 是 IoC容器 的最主要体现,他是一个 ConcurrentHashMap
// 直接存储了 bean的唯一标识 beanName及其对应的 BeanDefinition对象
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 重置所有已经注册过的 BeanDefinition 的缓存
resetBeanDefinition(beanName);
}
}
```

File diff suppressed because it is too large Load Diff

@ -1,9 +1,9 @@
## 1 Web环境中的SpringMVC
Web环境中SpringMVC是建立在IoC容器基础上的。了解SpringMVC首先要了解Spring的IoC容器是如何在Web环境中被载人并起作用的。
Web环境 中SpringMVC 是建立在 IoC容器 基础上的。了解 SpringMVC首先要了解 Spring 的 IoC容器 是如何在 Web环境 中被载入并起作用的。
Spring的IoC是一个独立模块它并不直接在Web容器中发挥作用如果要在Web环境中使用IoC容器需要Spring为IoC设计一个启动过程把IoC容器导入并在Web容器中建立起来。具体说来这个启动过程是和Web容器的启动过程集成在一起的。在这个过程中一方面处理Web容器的启动另一方面通过设计特定的Web容器拦截器将IoC容器载人到Web环境中来并将其初始化。在这个过程建立完成以后IoC容器才能正常工作而SpringMVC是建立在IoC容器的基础上的这样才能建立起MVC框架的运行机制从而响应从Web容器传递的HTTP请求。
Spring IoC 是一个独立模块,它并不直接在 Web容器 中发挥作用,如果要在 Web环境 中使用 IoC容器需要 Spring IoC 设计一个启动过程,把 IoC容器 导入,并在 Web容器 中建立起来。具体说来,这个启动过程是和 Web容器 的启动过程集成在一起的。在这个过程中,一方面处理 Web容器 的启动,另一方面通过设计特定的 Web容器拦截器 IoC容器 载人到 Web环境 中来并将其初始化。在这个过程建立完成以后IoC容器 才能正常工作,而 SpringMVC 是建立在 IoC容器 的基础上的,这样才能建立起 MVC框架 的运行机制,从而响应从 Web容器 传递的 HTTP请求。
下面以Tomcat作为Web容器的例子进行分析。在Tomcat中web.xml是应用的部署描述文件。在web.xml中常常经常能看到与Spring相关的部署描述。
下面以 Tomcat 作为 Web容器 的例子进行分析。在 Tomcat web.xml 是应用的部署描述文件。在 web.xml 中常常经常能看到与 Spring 相关的部署描述。
```xml
<servlet>
<servlet-name>sample</servlet-name>
@ -22,320 +22,318 @@ Spring的IoC是一个独立模块它并不直接在Web容器中发挥作用
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
```
web.xml是SpringMVC与Tomcat的接口部分。这个部署描述文件中首先定义了一个Servlet对象它是SpringMVC的DispatcherServlet。这个DispatcherServlet是MVC中很重要的一个类起着分发请求的作用。
web.xml SpringMVC Tomcat 的接口部分。这个部署描述文件中,首先定义了一个 Servlet对象它是 SpringMVC DispatcherServlet。这个 DispatcherServlet MVC 中很重要的一个类,起着分发请求的作用。
同时在部署描述中还为这个DispatcherServlet定义了对应的URL映射以指定这个Servlet需要处理的HTTP请求范围。context-param参数用来指定IoC容器读取Bean的XML文件的路径在这里这个配置文件被定义为WEB-INF/applicationContext.xml。其中可以看到Spring应用的Bean配置。
同时,在部署描述中,还为这个 DispatcherServlet 定义了对应的 URL映射以指定这个 Servlet 需要处理的 HTTP请求范围。context-param参数 用来指定 IoC容器 读取 Bean XML文件 的路径,在这里,这个配置文件被定义为 WEB-INF/applicationContext.xml。其中可以看到 Spring应用 Bean配置。
最后作为Spring MVC的启动类ContextLoaderListener被定义为一个监听器这个监听器是与Web服务器的生命周期相关联的由ContextLoaderListener监听器负责完成 IoC容器在Web环境中的启动工作。
最后,作为 Spring MVC 的启动类ContextLoaderListener 被定义为一个监听器,这个监听器是与 Web服务器 的生命周期相关联的,由 ContextLoaderListener监听器 负责完成 IoC容器 Web环境 中的启动工作。
DispatchServlet和ContextLoaderListener提供了在Web容器中对Spring的接口也就是说这些接口与Web容器耦合是通过ServletContext来实现的ServletContext是容器和应用沟通的桥梁从一定程度上讲ServletContext就是servlet规范的体现。这个ServletContext为Spring的IoC容器提供了一个宿主环境在宿主环境中Spring MVC建立起一个IoC容器的体系。这个IoC容器体系是通过ContextLoaderListener的初始化来建立的在建立IoC容器体系后把DispatchServlet作为Spring MVC处理Web请求的转发器建立起来从而完成响应HTTP请求的准备。有了这些基本配置建立在IoC容器基础上的SpringMVC就可以正常地发挥作用了。下面我们看一下loC容器在Web容器中的启动代码实现。
DispatchServlet ContextLoaderListener 提供了在 Web容器 中对 Spring 的接口,也就是说,这些接口与 Web容器 耦合是通过 ServletContext 来实现的ServletContext 是容器和应用沟通的桥梁,从一定程度上讲 ServletContext 就是 servlet规范 的体现)。这个 ServletContext Spring IoC容器 提供了一个宿主环境在宿主环境中Spring MVC 建立起一个 IoC容器 的体系。这个 IoC容器体系 是通过 ContextLoaderListener 的初始化来建立的,在建立 IoC容器体系 后,把 DispatchServlet 作为 Spring MVC 处理 Web请求 的转发器建立起来,从而完成响应 HTTP请求 的准备。有了这些基本配置,建立在 IoC容器 基础上的 SpringMVC 就可以正常地发挥作用了。下面我们看一下 IoC容器 在 Web容器 中的启动代码实现。
## 2 IoC容器启动的基本过程
IoC容器的启动过程就是建立上下文的过程该上下文是与ServletContext相伴而生的同时也是IoC容器在Web应用环境中的具体表现之一。由ContextLoaderListener启动的上下文为根上下文。在根上下文的基础上还有一个与Web MVC相关的上下文用来保存控制器(DispatcherServlet)需要的MVC对象作为根上下文的子上下文构成一个层次化的上下文体系。在Web容器中启动Spring应用程序时首先建立根上下文然后建立这个上下文体系这个上下文体系的建立是由ContextLoder来完成的其UML时序图如下图所示。
IoC容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC容器 Web应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC对象作为根上下文的子上下文构成一个层次化的上下文体系。在 Web容器 中启动 Spring应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML时序图 如下图所示。
![avatar](/images/springMVC/Web容器启动spring应用程序过程图.png)
在web.xml中已经配置了ContextLoaderListener它是Spring提供的类是为在Web容器中建立IoC容器服务的它实现了ServletContextListener接口这个接口是在Servlet API中定义的提供了与Servlet生命周期结合的回调比如上下文初始化contextInitialized()方法和上下文销毁contextDestroyed()方法。而在Web容器中建立WebApplicationContext的过程是在contextInitialized()方法中完成的。另外ContextLoaderListener还继承了ContextLoader具体的载入IoC容器的过程是由ContextLoader来完成的。
web.xml 中,已经配置了 ContextLoaderListener它是 Spring 提供的类,是为在 Web容器 中建立 IoC容器 服务的,它实现了 ServletContextListener接口这个接口是在 Servlet API 中定义的,提供了与 Servlet生命周期 结合的回调,比如上下文初始化 contextInitialized()方法 上下文销毁 contextDestroyed()方法。而在 Web容器 中,建立 WebApplicationContext 的过程,是在 contextInitialized()方法 中完成的。另外ContextLoaderListener 还继承了 ContextLoader具体的载入 IoC容器 的过程是由 ContextLoader 来完成的。
在ContextLoader中完成了两个IoC容器建立的基本过程一个是在Web容器中建立起双亲IoC容器另一个是生成相应的WebApplicationContext并将其初始化。
ContextLoader 中,完成了两个 IoC容器 建立的基本过程,一个是在 Web容器 中建立起 双亲IoC容器另一个是生成相应的 WebApplicationContext 并将其初始化。
## 3 Web容器中的上下文设计
先从Web容器中的上下文入手看看Web环境中的上下文设置有哪些特别之处然后再
到ContextLoaderListener中去了解整个容器启动的过程。为了方便在Web环境中使用IoC容器
Spring为Web应用提供了上下文的扩展接口WebApplicationContext来满足启动过程的需要其继承关系如下图所示。
先从 Web容器 中的上下文入手,看看 Web环境 中的上下文设置有哪些特别之处,然后再到 ContextLoaderListener 中去了解整个容器启动的过程。为了方便在 Web环境 中使用 IoC容器
Spring 为 Web应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。
![avatar](/images/springMVC/WebApplicationContext接口的类继承关系.png)
在这个类继承关系中可以从熟悉的XmlWebApplicationContext入手来了解它的接口实现。在接口设计中最后是通过ApplicationContex接口与BeanFactory接口对接的而对于具体的功能实现很多都是封装在其基类AbstractRefreshableWebApplicationContext中完成的。
在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex接口 BeanFactory接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。
同样在源代码中也可以分析出类似的继承关系在WebApplicationContext中可以看到相关的常量设计比如ROOT_ WEB_ APPLICATION_CONTEXT_ATTRIBUTE等这个常量是用来索引在ServletContext中存储的根上下文的。这个接口类定义的接口方法比较简单在这个接口中定义了一
个getServletContext方法通过这个方法可以得到当前Web容器的Servlet上下文环境通过
这个方法相当于提供了一个Web容器级别的全局环境。
同样,在源代码中,也可以分析出类似的继承关系,在 WebApplicationContext 中可以看到相关的常量设计,比如 ROOT_ WEB_ APPLICATION_CONTEXT_ATTRIBUTE 等,这个常量是用来索引在 ServletContext 中存储的根上下文的。这个接口类定义的接口方法比较简单,在这个接口中,定义了一
getServletContext()方法,通过这个方法可以得到当前 Web容器 Servlet上下文环境通过
这个方法,相当于提供了一个 Web容器级别的 全局环境。
```java
public interface WebApplicationContext extends ApplicationContext {
/**
* 该常量用于在ServletContext中存取根上下文
*/
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
/**
* 该常量用于在 ServletContext 中存取根上下文
*/
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
/**
* 对于WebApplicationContext来说需要得到Web容器的ServletContext
*/
ServletContext getServletContext();
/**
* 对于 WebApplicationContext 来说,需要得到 Web容器 的 ServletContext
*/
ServletContext getServletContext();
}
```
在启动过程中Spring会使用一个默认的WebApplicationContext实现作为IoC容器这个默认使用的IoC容器就是XmlWebApplicationContext它继承了ApplicationContext在ApplicationContext的基础上增加了对Web环境和XML配置定义的处理。在XmlWebApplicationContext的初始化过程中Web容器中的IoC容器被建立起来从而在Web容器中建立起整个Spring应用。与前面博文中分析的IoC容器的初始化一样这个过程也有loadBeanDefinition对BeanDefinition的载入。在Web环境中对定位BeanDefinition的Resource有特别的要求这个要求的处理体现在对getDefaultConfigLocations方法的处理中。这里使用了默认的BeanDefinition的配置路径这个路径在XmlWebApplicationContext中作为一个常量定义好了即/WEB-INF/applicationContext.xml。
在启动过程中Spring 会使用一个默认的 WebApplicationContext 实现作为 IoC容器这个默认使用的 IoC容器 就是 XmlWebApplicationContext它继承了 ApplicationContext ApplicationContext 的基础上,增加了对 Web环境 XML配置定义 的处理。在 XmlWebApplicationContext 的初始化过程中Web容器 中的 IoC容器 被建立起来,从而在 Web容器 中建立起整个 Spring应用。与前面博文中分析的 IoC容器 的初始化一样,这个过程也有 loadBeanDefinition()方法 BeanDefinition 的载入。在 Web环境 中,对定位 BeanDefinition Resource 有特别的要求,这个要求的处理体现在对 getDefaultConfigLocations()方法 的处理中。这里使用了默认的 BeanDefinition 的配置路径,这个路径在 XmlWebApplicationContext 中作为一个常量定义好了,即 /WEB-INF/applicationContext.xml。
```java
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
/** 若不指定其它文件spring默认从"/WEB-INF/applicationContext.xml"目录文件 初始化IoC容器 */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
/** 默认的配置文件在 /WEB-INF/ 目录下 */
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
/** 默认的配置文件后缀名为.xml */
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
/**
* 此加载过程在 容器refresh()时启动
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 使用XmlBeanDefinitionReader对指定的BeanFactory进行解析
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 初始化beanDefinitionReader的属性其中设置ResourceLoader是因为
// XmlBeanDefinitionReader是DefaultResource的子类所有这里同样会使用
// DefaultResourceLoader来定位BeanDefinition
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 该方法是一个空实现
initBeanDefinitionReader(beanDefinitionReader);
// 使用初始化完成的beanDefinitionReader来加载BeanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
/**
* 获取所有的配置文件然后一个一个载入BeanDefinition
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
/**
* 获取默认路径"/WEB-INF/***.xml"下的配置文件,
* 或者获取"/WEB-INF/applicationContext.xml"配置文件
*/
@Override
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
/** 若不指定其它文件Spring 默认从 "/WEB-INF/applicationContext.xml" 目录文件 初始化 IoC容器 */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
/** 默认的配置文件在 /WEB-INF/ 目录下 */
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
/** 默认的配置文件后缀名为 .xml */
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
/**
* 此加载过程在容器 refresh() 时启动
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 使用 XmlBeanDefinitionReader 对指定的 BeanFactory 进行解析
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 初始化 beanDefinitionReader 的属性,其中,设置 ResourceLoader 是因为 XmlBeanDefinitionReader
// 是 DefaultResource 的子类,所有这里同样会使用 DefaultResourceLoader 来定位 BeanDefinition
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 该方法是一个空实现
initBeanDefinitionReader(beanDefinitionReader);
// 使用初始化完成的 beanDefinitionReader 来加载 BeanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
/**
* 获取所有的配置文件,然后一个一个载入 BeanDefinition
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
/**
* 获取默认路径 "/WEB-INF/***.xml" 下的配置文件,
* 或者获取 "/WEB-INF/applicationContext.xml" 配置文件
*/
@Override
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
}
```
从上面的代码中可以看到在XmlWebApplicationContext中基本的上下文功能都已经通过类的继承获得这里需要处理的是如何获取Bean定义信息在这里就转化为如何在Web容器环境中获得Bean定义信息。在获得Bean定义信息之后后面的过程基本上就和前面分析的XmlFileSystemBeanFactory一样是通过XmlBeanDefinitionReader来载人Bean定义信息的,最终完成整个上下文的初始化过程。
从上面的代码中可以看到,在 XmlWebApplicationContext 中,基本的上下文功能都已经通过类的继承获得,这里需要处理的是,如何获取 BeanDefinition信息在这里就转化为如何在 Web容器环境 中获得 BeanDefinition信息。在获得 BeanDefinition信息 之后,后面的过程基本上就和前面分析的 XmlFileSystemBeanFactory 一样,是通过 XmlBeanDefinitionReader 来载入 BeanDefinition信息 的,最终完成整个上下文的初始化过程。
## 4 ContextLoader的设计与实现
对于Spring承载的Web应用而言可以指定在Web应用程序启动时载入IoC容器或者称为WebApplicationContext。这个功能是由ContextLoaderListener来完成的它是在Web容器中配置的监听器会监听Web容器的启动然后载入IoC容器。这个ContextLoaderListener通过使用ContextLoader来完成实际的WebApplicationContext也就是IoC容器的初始化工作。这个ContextLoader就像Spring应用程序在Web容器中的启动器。这个启动过程是在Web容器中发生的所以需要根据Web容器部署的要求来定义ContextLoader相关的配置在概述中已经看到了这里就不重复了。
对于 Spring 承载的 Web应用 而言,可以指定在 Web应用程序 启动时载入 IoC容器或者称为WebApplicationContext。这个功能是由 ContextLoaderListener 来完成的,它是在 Web容器 中配置的监听器,会监听 Web容器 的启动,然后载入 IoC容器。这个 ContextLoaderListener 通过使用 ContextLoader 来完成实际的 WebApplicationContext也就是 IoC容器 的初始化工作。这个 ContextLoader 就像 Spring应用程序 Web容器 中的启动器。这个启动过程是在 Web容器 中发生的,所以需要根据 Web容器 部署的要求来定义 ContextLoader相关的配置在概述中已经看到了这里就不重复了。
为了了解IoC容器在Web容器中的启动原理这里对启动器ContextLoaderListener的实现进行分析。**这个监听器是启动根IoC容器并把它载入到Web容器的主要功能模块也是整个Spring Web应用加载IoC的第一个地方**。从加载过程可以看到首先从Servlet事件中得到ServletContext然后可以读取配置在web.xml中的各个相关的属性值接着ContextLoader会实例化WebApplicationContext并完成其载人和初始化过程。这个被初始化的第一个上下文作为根上下文而存在这个根上下文载入后被绑定到Web应用程序的ServletContext上。任何需要访问根上下文的应用程序代码都可以从WebApplicationContextUtils类的静态方法中得到。
为了了解 IoC容器 Web容器 中的启动原理,这里对 启动器ContextLoaderListener 的实现进行分析。**这个监听器是启动 根IoC容器 并把它载入到 Web容器 的主要功能模块,也是整个 Spring Web应用 加载 IoC 的第一个地方**。从加载过程可以看到,首先从 Servlet事件 中得到 ServletContext然后可以读取配置在 web.xml 中的各个相关的属性值,接着 ContextLoader 会实例化 WebApplicationContext并完成其载人和初始化过程。这个被初始化的第一个上下文作为根上下文而存在这个根上下文载入后被绑定到 Web应用程序 ServletContext 上。任何需要访问根上下文的应用程序代码都可以从 WebApplicationContextUtils类 的静态方法中得到。
下面分析具体的根上下文的载人过程。在ContextLoaderListener中实现的是**ServletContextListener接口这个接口里的函数会结合Web容器的生命周期被调用**。因为ServletContextListener是ServletContext的监听者如果ServletContext发生变化会触发出相应的事件而监听器一直在对这些事件进行监听如果接收到了监听的事件就会做出预先设计好的响应动作。由于ServletContext的变化而触发的监听器的响应具体包括在服务器启动时ServletContext被创建的时候服务器关闭时ServletContext将被销毁的时候等。对应这些事件及Web容器状态的变化在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时ServletContextListener的contextInitialized()方法被调用服务器将要关闭时ServletContextListener的contextDestroyed()方法被调用。了解了Web容器中监听器的工作原理下面看看服务器启动时 ContextLoaderListener的调用完成了什么。在这个初始化回调中创建了ContextLoader同时会利用创建出来的ContextLoader来完成IoC容器的初始化。
下面分析具体的根上下文的载人过程。在 ContextLoaderListener 中,实现的是 **ServletContextListener接口这个接口里的函数会结合 Web容器 的生命周期被调用**。因为 ServletContextListener ServletContext 的监听者,如果 ServletContext 发生变化,会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于 ServletContext 的变化而触发的监听器的响应具体包括在服务器启动时ServletContext 被创建的时候服务器关闭时ServletContext 将被销毁的时候等。对应这些事件及 Web容器状态 的变化在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时ServletContextListener contextInitialized()方法 被调用服务器将要关闭时ServletContextListener contextDestroyed()方法 被调用。了解了 Web容器 中监听器的工作原理,下面看看服务器启动时 ContextLoaderListener 的调用完成了什么。在这个初始化回调中,创建了 ContextLoader同时会利用创建出来的 ContextLoader 来完成 IoC容器 的初始化。
```java
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
private ContextLoader contextLoader;
/**
* 启动web应用的 根上下文
*/
public void contextInitialized(ServletContextEvent event) {
// 由于本类直接继承了ContextLoader所以能直接使用ContextLoader来初始化IoC容器
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
// 具体的初始化工作交给ContextLoader完成
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
private ContextLoader contextLoader;
/**
* 启动 web应用 的根上下文
*/
public void contextInitialized(ServletContextEvent event) {
// 由于本类直接继承了 ContextLoader所以能直接使用 ContextLoader 来初始化 IoC容器
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
// 具体的初始化工作交给 ContextLoader 完成
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
}
public class ContextLoader {
public static final String CONTEXT_CLASS_PARAM = "contextClass";
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
/**
* 由ContextLoader完成根上下文在Web容器中的创建。这个根上下文是作为Web容器中唯一的实例而存在的,
* 根上下文创建成功后 会被存到Web容器的ServletContext中,供需要时使用。存取这个根上下文的路径是由
* Spring预先设置好的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE中进行了定义
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 如果ServletContext中已经包含了根上下文,则抛出异常
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
// 这里创建在ServletContext中存储的根上下文
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 载入根上下文的 双亲上下文
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置 并且初始化IoC容器看到Refresh应该能想到AbstractApplicationContext
// 中的refresh()方法猜到它是前面介绍的IoC容器的初始化入口
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将上面创建的WebApplicationContext实例 存到ServletContext中,注意同时被存入的常量
// ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE以后的应用都会根据这个属性获取根上下文
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
/**
* 创建WebApplicationContext的实例化对象
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 判断使用什么样的类在Web容器中作为IoC容器
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 直接实例化需要产生的IoC容器
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
/**
* 在确定使用何种IoC容器的过程中可以看到应用可以在部署描述符中指定使用什么样的IoC容器
* 这个指定操作是通过CONTEXT_ CLASS_ PARAM参数的设置完成的。如果没有指定特定的IoC容器
* 将使用默认的IoC容器也就是XmlWebApplicationContext对象作为在Web环境中使用的IoC容器。
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
// 获取servletContext中对CONTEXT_CLASS_PARAMcontextClass参数的配置
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
// 获取配置的contextClassName对应的clazz对象
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
// 如果没有配置CONTEXT_CLASS_PARAM则使用默认的ContextClass
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getServletContextName()));
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
}
// 设置ServletContext 及配置文件的位置参数
wac.setServletContext(sc);
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
// IoC容器初始化的入口想不起来的把前面IoC容器初始化的博文再读10遍
wac.refresh();
}
public static final String CONTEXT_CLASS_PARAM = "contextClass";
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
/**
* 由 ContextLoader 完成根上下文在 Web容器 中的创建。这个根上下文是作为 Web容器 中唯一的实例而存在的,
* 根上下文创建成功后会被存到 Web容器 的 ServletContext 中,供需要时使用。存取这个根上下文的路径是由
* Spring 预先设置好的,在 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中进行了定义
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 如果 ServletContext 中已经包含了根上下文,则抛出异常
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
// 这里创建在 ServletContext 中存储的根上下文
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 载入根上下文的 双亲上下文
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并初始化 IoC容器看到下面方法中的 Refresh单词 应该能想到
// AbstractApplicationContext 中的 refresh()方法,猜到它是前面介绍的 IoC容器 的初始化入口
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将上面创建的 WebApplicationContext实例 存到 ServletContext 中,注意同时被存入的常量
// ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE以后的应用都会根据这个属性获取根上下文
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
/**
* 创建 WebApplicationContext 的实例化对象
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 判断使用什么样的类在 Web容器 中作为 IoC容器
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 直接实例化需要产生的 IoC容器
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
/**
* 在确定使用何种 IoC容器 的过程中可以看到,应用可以在部署描述符中指定使用什么样的 IoC容器
* 这个指定操作是通过 CONTEXT_ CLASS_ PARAM参数 的设置完成的。如果没有指定特定的 IoC容器
* 将使用默认的 IoC容器也就是 XmlWebApplicationContext对象 作为在 Web环境 中使用的 IoC容器。
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
// 获取 servletContext 中对 CONTEXT_CLASS_PARAMcontextClass参数 的配置
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
// 获取配置的 contextClassName 对应的 clazz对象
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
// 如果没有配置 CONTEXT_CLASS_PARAM则使用默认的 ContextClass
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getServletContextName()));
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
}
// 设置 ServletContext 及配置文件的位置参数
wac.setServletContext(sc);
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
// IoC容器 初始化的入口,想不起来的把前面 IoC容器 初始化的博文再读10遍
wac.refresh();
}
}
```
这就是IoC容器在Web容器中的启动过程 应用中启动IoC容器的方式相类似所不同的是这里需要考虑Web容器的环境特点比如各种参数的设置IoC容器与Web容器ServletContext的结合等。在初始化这个上下文以后该上下文会被存储到SevletContext中这样就建立了一个全局的关于整个应用的上下文。同时在启动Spring MVC时我们还会看到这个上下文被以后的DispatcherServlet在进行自己持有的上下文的初始化时设置为DispatcherServlet自带的上下文的双亲上下文。
这就是 IoC容器 Web容器 中的启动过程,与应用中启动 IoC容器 的方式相类似,所不同的是这里需要考虑 Web容器 的环境特点比如各种参数的设置IoC容器 Web容器 ServletContext 的结合等。在初始化这个上下文以后,该上下文会被存储到 SevletContext 中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动 SpringMVC 时,我们还会看到这个上下文被以后的 DispatcherServlet 在进行自己持有的上下文的初始化时,设置为 DispatcherServlet 自带的上下文的双亲上下文。

@ -1,35 +1,36 @@
## 1 SpringMVC应用场景
在使用SpringMVC时除了要在web.xml中配置ContextLoaderListener外还要对DispatcherServlet进行配置。作为一个Servlet这个DispatcherServlet实现的是Sun的J2EE核心模式中的前端控制器模式(Front Controller) 作为一个前端控制器所有的Web请求都需要通过它来处理,进行转发、匹配、数据处理后,并转由页面进行展现因此这个DispatcerServlet可以看成是Spring MVC实现中最为核心的部分。
在使用 SpringMVC 时,除了要在 web.xml 中配置 ContextLoaderListener 外,还要对 DispatcherServlet 进行配置。作为一个 Servlet这个 DispatcherServlet 实现的是 Sun J2EE核心模式 中的 前端控制器模式(Front Controller) 作为一个前端控制器,所有的 Web请求 都需要通过它来进行转发、匹配、数据处理,然后转由页面进行展现,因此这个 DispatcerServlet 可以看成是 SpringMVC实现 中最为核心的部分。
在Spring MVC中对于不同的Web请求的映射需求Spring MVC提供了不同的HandlerMapping的实现可以让应用开发选取不同的映射策略。DispatcherSevlet默认了BeanNameUrlHandlerMapping作为映射策略实现。除了映射策略可以定制外Spring MVC提供了各种Controller的实现来供应用扩展和使用以应对不同的控制器使用场景这些Controller控制器需要实现handleRequest接口方法并返回ModelAndView对象。Spring MVC还提供了各种视图实现比如常用的JSP视图。除此之外Spring MVC还提供了拦截器供应用使用允许应用对Web请求进行拦截以及前置处理和后置处理。
SpringMVC 中,对于不同的 Web请求 的映射需求SpringMVC 提供了不同的 HandlerMapping 的实现可以让应用开发选取不同的映射策略。DispatcherSevlet 默认了 BeanNameUrlHandlerMapping 作为映射策略实现。除了映射策略可以定制外SpringMVC提供了各种 Controller 的实现来供应用扩展和使用,以应对不同的控制器使用场景,这些 Controller控制器 需要实现 handleRequest()接口方法,并返回 ModelAndView对象。SpringMVC 还提供了各种视图实现,比如常用的 JSP视图。除此之外SpringMVC 还提供了拦截器供应用使用,允许应用对 Web请求 进行拦截,以及前置处理和后置处理。
## 2 SpringMVC设计概览
在完成对ContextLoaderListener的初始化以后Web容器开始初始化DispatcherServlet这个初始化的启动与在web.xml中对载入次序的定义有关。DispatcherServlet会建立自己的上下文来持有Spring MVC的Bean对象在建立这个自己持有的IoC容器时会**从ServletContext中得到根上下文**作为DispatcherServlet持有上下文的双亲上下文。有了这个根上下文再对自己持有的上下文进行初始化最后把自己持有的这个上下文保存到ServletContext中供以后检索和使用。
在完成对 ContextLoaderListener 的初始化以后Web容器 开始初始化 DispatcherServlet这个初始化的启动与在 web.xml 中对载入次序的定义有关。DispatcherServlet 会建立自己的上下文来持有SpringMVC Bean对象在建立这个自己持有的 IoC容器 时,会**从 ServletContext 中得到根上下文**作为 DispatcherServlet 持有上下文的双亲上下文。有了这个根上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文保存到 ServletContext 中,供以后检索和使用。
为了解这个过程可以从DispatcherServlet的父类FrameworkServlet的代码入手去探寻DispatcherServlet的启动过程它同时也是SpringMVC的启动过程。ApplicationContext的创建过程和ContextLoader创建根上下文的过程有许多类似的地方。下面来看一下这个DispatcherServlet类的继承关系。
为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet类 的继承关系。
![avatar](/images/springMVC/DispatcherServlet的继承关系.png)
DispatcherServlet通过继承FrameworkServlet和HttpServletBean而继承了HttpServlet通过使用Servlet API来对HTTP请求进行响应成为Spring MVC的前端处理器同时成为MVC模块与Web容器集成的处理前端。
DispatcherServlet 通过继承 FrameworkServlet HttpServletBean 而继承了 HttpServlet通过使用Servlet API 来对 HTTP请求 进行响应,成为 SpringMVC 的前端处理器,同时成为 MVC模块 Web容器 集成的处理前端。
DispatcherServlet的工作大致可以分为两个部分一个是初始化部分由initServletBean()启动通过initWebApplicationContext()方法最终调用DispatcherServlet的initStrategies()方法在这个方法里DispatcherServlet对MVC模块的其他部分进行了初始化比如handlerMapping、ViewResolver等另一个是对HTTP请求进行响应作为一个ServletWeb容器会调用Servlet的doGet()和doPost()方法在经过FrameworkServlet的processRequest()简单处理后会调用DispatcherServlet的doService()方法在这个方法调用中封装了doDispatch()这个doDispatch()是Dispatcher实现MVC模式的主要部分下图为DispatcherServlet的处理过程时序图。
DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet initStrategies()方法在这个方法里DispatcherServlet MVC模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP请求 进行响应,作为一个 ServletWeb容器 会调用 Servlet 的doGet() doPost()方法,在经过 FrameworkServlet processRequest() 简单处理后,会调用 DispatcherServlet doService()方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() Dispatcher 实现 MVC模式 的主要部分,下图为 DispatcherServlet 的处理过程时序图。
![avatar](/images/springMVC/DispatcherServlet的处理过程.png)
## 3 DispatcherServlet的启动和初始化
前面大致描述了Spring MVC的工作流程下面看一下DispatcherServlet的启动和初始化的代码设计及实现。
前面大致描述了 SpringMVC 的工作流程,下面看一下 DispatcherServlet 的启动和初始化的代码设计及实现。
作为ServletDispatcherServlet的启动与Servlet的启动过程是相联系的。在Servlet的初始化过程中Servlet的init()方法会被调用以进行初始化DispatcherServlet的基类HttpServletBean实现了该方法。在初始化开始时需要读取配置在ServletContext中的Bean属性参数这些属性参数设置在web.xml的Web容器初始化参数中。使用编程式的方式来设置这些Bean属性在这里可以看到对PropertyValues和BeanWrapper的使用。对于这些和依赖注人相关的类的使用在分析IoC容器的初始化时尤其是在依赖注入实现分析时有过“亲密接触”。只是这里的依赖注人是与Web容器初始化相关的。
作为 ServletDispatcherServlet 的启动与 Servlet 的启动过程是相联系的。在 Servlet 的初始化过程中Servlet init()方法 会被调用以进行初始化DispatcherServlet 的基类 HttpServletBean 实现了该方法。在初始化开始时,需要读取配置在 ServletContext 中的 Bean属性参数这些属性参数设置在 web.xml Web容器初始化参数 中。使用编程式的方式来设置这些 Bean属性在这里可以看到对 PropertyValues BeanWrapper 的使用。对于这些和依赖注人相关的类的使用,在分析 IoC容器 的初始化时,尤其是在依赖注入实现分析时,有过“亲密接触”。只是这里的依赖注人是与 Web容器 初始化相关的。
接着会执行DispatcherServlet持有的IoC容器的初始化过程在这个初始化过程中一个新的上下文被建立起来这个DispatcherServlet持有的上下文被设置为根上下文的子上下文。一个Web应用中可以容纳多个Servlet存在与此相对应对于应用在Web容器中的上下体系一个根上下文可以作为许多Servlet上下文的双亲上下文。了解IoC工作原理的读者知道在向IoC容器getBean()时IoC容器会首先向其双亲上下文去getBean()也就是说在根上下文中定义的Bean是可以被各个Servlet持有的上下文得到和共享的。DispatcherServlet持有的 上下文被建立起来以后也需要和其他IoC容器一样完成初始化这个初始化也是通过refresh()方法来完成的。最后DispatcherServlet给这个自己持有的上下文命名并把它设置到Web容器的上下文中这个名称和在web.xml中设置的DispatcherServlet的Servlet名称有关从而保证了这个上下文在Web环境上下文体系中的唯一性。
接着会执行 DispatcherServlet 持有的 IoC容器 的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个 DispatcherServlet 持有的上下文被设置为根上下文的子上下文。一个 Web应用 中可以容纳多个 Servlet 存在;与此相对应,对于应用在 Web容器 中的上下体系,一个根上下文可以作为许多 Servlet上下文 的双亲上下文。了解 IoC 工作原理的读者知道,在向 IoC容器 getBean() IoC容器 会首先向其双亲上下文去 getBean(),也就是说,在根上下文中定义的 Bean 是可以被各个 Servlet 持有的上下文得到和共享的。DispatcherServlet 持有的 上下文被建立起来以后,也需要和其他 IoC容器 一样完成初始化,这个初始化也是通过 refresh()方法 来完成的。最后DispatcherServlet 给这个自己持有的上下文命名,并把它设置到 Web容器 的上下文中,这个名称和在 web.xml 中设置的 DispatcherServlet Servlet名称 有关,从而保证了这个上下文在 Web环境上下文体系 中的唯一性。
```java
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 获取Servlet的初始化参数对bean属性进行配置
// 获取 Servlet 的初始化参数,对 bean属性 进行配置
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
@ -54,10 +55,11 @@ public abstract class HttpServletBean extends HttpServlet implements Environment
public abstract class FrameworkServlet extends HttpServletBean {
/** 此servlet的WebApplicationContext */
/** 此 servlet 的 WebApplicationContext */
private WebApplicationContext webApplicationContext;
/** 我们是否应该将当前Servlet的上下文webApplicationContext设为ServletContext的属性 */
/** 我们是否应该将当前 Servlet 的上下文 webApplicationContext 设为 ServletContext 的属性 */
private boolean publishContext = true;
public FrameworkServlet() {
@ -68,7 +70,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
/**
* 覆盖了父类HttpServletBean的空实现
* 覆盖了父类 HttpServletBean 的空实现
*/
@Override
protected final void initServletBean() throws ServletException {
@ -100,16 +102,16 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
/**
* 为这个Servlet初始化一个公共的WebApplicationContext实例
* 为这个 Servlet 初始化一个公共的 WebApplicationContext实例
*/
protected WebApplicationContext initWebApplicationContext() {
// 获取 根上下文 作为当前MVC上下文的双亲上下文这个根上下文保存在ServletContext中
// 获取根上下文作为当前 MVC上下文 的双亲上下文,这个根上下文保存在 ServletContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 可以在本对象被构造时注入一个webApplicationContext实例
// 可以在本对象被构造时注入一个 webApplicationContext实例
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
@ -126,24 +128,24 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
if (wac == null) {
// 在本对象被构造时没有注入上下文实例 ->
// 查看是否已在servlet上下文中注册了上下文实例。
// 查看是否已在 servlet上下文 中注册了上下文实例。
// 如果存在一个,则假定父上下文(如果有的话)已经被设置,
// 并且用户已经执行了任何初始化例如设置上下文ID
wac = findWebApplicationContext();
}
if (wac == null) {
// 没有为此servlet定义上下文实例 -> 创建本地实例
// 没有为此 servlet 定义上下文实例 -> 创建本地实例
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 上下文 不是支持刷新的ConfigurableApplicationContext或者
// 在构造时注入的上下文已经完成刷新 -> 在此处手动触发onRefresh()方法
// 上下文不是支持刷新的 ConfigurableApplicationContext或者
// 在构造时注入的上下文已经完成刷新 -> 在此处手动触发 onRefresh()方法
onRefresh(wac);
}
if (this.publishContext) {
// 把当前建立的上下文保存到ServletContext中使用的属性名是和当前servlet名相关的
// 把当前建立的上下文保存到 ServletContext 中,使用的属性名是和 当前servlet名 相关的
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
@ -156,7 +158,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
}
```
至此这个MVC的上下文就建立起来了具体取得根上下文的过程在WebApplicationContextUtils中实现。这个根上下文是ContextLoader设置到ServletContext中去的使用的属性是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEContextLoader还对这个IoC容器的Bean配置文件进行了设置默认的位置是在/WEB-INF/applicationContext.xml文件中。由于这个根上下文是DispatcherServlet建立的上下文的 双亲上下文所以根上下文中管理的Bean也可以被DispatcherServlet的上下文使用。通过getBean()向IoC容器获取Bean时容器会先到它的双亲IoC容器中获取。
至此,这个 MVC 的上下文就建立起来了,具体取得根上下文的过程在 WebApplicationContextUtils 中实现。这个根上下文是 ContextLoader 设置到 ServletContext 中去的,使用的属性是 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEContextLoader 还对这个 IoC容器 Bean 配置文件进行了设置,默认的位置是在 /WEB-INF/applicationContext.xml文件 中。由于这个根上下文是 DispatcherServlet 建立的上下文的 双亲上下文,所以根上下文中管理的 Bean 也可以被 DispatcherServlet 的上下文使用。通过 getBean() IoC容器 获取 Bean 时,容器会先到它的 双亲IoC容器 中获取。
```java
/**
* 这是一个封装了很多静态方法的抽象工具类,所以只能调用其静态方法,
@ -164,8 +166,8 @@ public abstract class FrameworkServlet extends HttpServletBean {
*/
public abstract class WebApplicationContextUtils {
/**
* 使用了WebApplicationContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性获取
* ServletContext中的根上下文这个属性代表的根上下文在ContextLoaderListener初始化的
* 使用了 WebApplicationContext ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性获取
* ServletContext 中的根上下文,这个属性代表的根上下文在 ContextLoaderListener 初始化的
* 过程中被建立
*/
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
@ -173,7 +175,7 @@ public abstract class WebApplicationContextUtils {
}
/**
* 查找此web应用程序的自定义WebApplicationContext
* 查找此 web应用程序 的自定义 WebApplicationContext
*/
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
@ -197,20 +199,20 @@ public abstract class WebApplicationContextUtils {
}
)
```
回到FrameworkServlet的实现中来看一下DispatcherServlet的上下文是怎样建立的这个建立过程与前面建立根上下文的过程非常类似。建立DispatcherServlet的上下文需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象并为它设置参数。根据默认的配置这个上下文对象也是XmlWebApplicationContext对象这个类型是在DEFAULT_CONTEXT_CLASS参数中设置好并允许BeanUtilis使用的。在实例化结束后需要为这个上下文对象设置好一些基本的配置这些配置包括它的双亲上下文、Bean配置文件的位置等。完成这些配置以后最后通过调用IoC容器的refresh()方法来完成IoC容器的最终初始化这和前面我们对IoC容器实现原理的分析中所看到的IoC容器初始化的过程是一致的。
回到 FrameworkServlet 的实现中来看一下DispatcherServlet 的上下文是怎样建立的,这个建立过程与前面建立根上下文的过程非常类似。建立 DispatcherServlet 的上下文,需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象,并为它设置参数。根据默认的配置,这个上下文对象也是 XmlWebApplicationContext对象这个类型是在 DEFAULT_CONTEXT_CLASS参数 中设置好并允许 BeanUtilis 使用的。在实例化结束后需要为这个上下文对象设置好一些基本的配置这些配置包括它的双亲上下文、Bean配置文件 的位置等。完成这些配置以后,最后通过调用 IoC容器 refresh()方法 来完成 IoC容器 的最终初始化,这和前面我们对 IoC容器实现原理 的分析中所看到的 IoC容器初始化 的过程是一致的。
```java
public abstract class FrameworkServlet extends HttpServletBean {
/**
* 为此servlet实例化一个WebApplicationContext可以是默认的XmlWebApplicationContext
* 也可以是用户设置的自定义Context上下文
* 为此 servlet 实例化一个 WebApplicationContext可以是默认的 XmlWebApplicationContext
* 也可以是用户设置的自定义 Context上下文
*/
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
// 默认为XmlWebApplicationContext.class
// 默认为 XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
@ -228,11 +230,11 @@ public abstract class FrameworkServlet extends HttpServletBean {
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 这里设置的 双亲上下文就是在ContextLoader中建立的根上下文
// 这里设置的 双亲上下文,就是在 ContextLoader 中建立的根上下文
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
// 配置并且刷新wac
// 配置并且刷新 WebApplicationContext对象
configureAndRefreshWebApplicationContext(wac);
return wac;
@ -240,15 +242,15 @@ public abstract class FrameworkServlet extends HttpServletBean {
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 应用程序上下文id仍设置为其原始默认值如果该id不为空的话
// 应用程序上下文id 仍设置为其原始默认值,如果该 id 不为空的话
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// 生成默认的id
// 生成默认的 id
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// 当Servlet<=2.4如果有请使用web.xml中指定的名称。
// 当 Servlet <= 2.4:如果有,请使用 web.xml 中指定的名称。
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
@ -259,7 +261,7 @@ public abstract class FrameworkServlet extends HttpServletBean {
}
}
else {
// Servlet 2.5的getContextPath可用
// Servlet 2.5 getContextPath 可用!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
}
@ -272,8 +274,8 @@ public abstract class FrameworkServlet extends HttpServletBean {
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// 在刷新上下文的任何情况下,都将会调用wac环境的initPropertySources()方法。
// 在此处执行此方法以确保在刷新上下文之前servlet属性源已准备就绪
// 在刷新上下文的任何情况下,都将会调用 此wac 的 env的 initPropertySources()方法。
// 在此处执行此方法以确保在刷新上下文之前servlet属性源 已准备就绪
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
@ -283,20 +285,20 @@ public abstract class FrameworkServlet extends HttpServletBean {
applyInitializers(wac);
// IoC容器都是通过该方法完成 容器初始化的
// IoC容器 都是通过该方法完成 容器初始化的
wac.refresh();
}
}
```
这时候DispatcherServlet中的IoC容器已经建立起来了这个IoC容器是 根上下文 的子容器。如果要查找一个由DispatcherServlet所持有的IoC容器来管理的Bean系统会首先到 根上下文 中去查找。如果查找不到才会到DispatcherServlet所管理的IoC容器去进行查找这是由IoC容器getBean()的实现来决定的。通过一系列在Web容器中执行的动作在这个上下文体系建立和初始化完毕的基础上Spring MVC就可以发挥其作用了。下面来分析一下Spring MVC的具体实现。
这时候 DispatcherServlet 中的 IoC容器 已经建立起来了,这个 IoC容器 是 根上下文 的子容器。如果要查找一个由 DispatcherServlet 所持有的 IoC容器 来管理的 Bean系统会首先到 根上下文 中去查找。如果查找不到,才会到 DispatcherServlet 所管理的 IoC容器 去进行查找,这是由 IoC容器getBean() 的实现来决定的。通过一系列在 Web容器 中执行的动作在这个上下文体系建立和初始化完毕的基础上SpringMVC 就可以发挥其作用了。下面来分析一下 SpringMVC 的具体实现。
在前面分析DispatchServlet的初始化过程中可以看到DispatchServlet持有一个以自己的Servlet名称命名的IoC容器。这个IoC容器是一个WebApplicationContext对象这个IoC容器建立起来以后意味着DispatcherServlet拥有自己的Bean定义空间这为使用各个独立的XML文件来配置MVC中各个Bean创造了条件。由于在初始化结束以后与Web容器相关的加载过程实际上已经完成了SpringMVC的具体实现和普通的Spring应用程序的实现并没有太大的差别。
在前面分析 DispatchServlet 的初始化过程中可以看到DispatchServlet 持有一个以自己的 Servlet名称 命名的 IoC容器。这个 IoC容器 是一个 WebApplicationContext对象这个 IoC容器 建立起来以后,意味着 DispatcherServlet 拥有自己的 Bean定义空间这为使用各个独立的 XML文件 来配置 MVC 中各个 Bean 创造了条件。由于在初始化结束以后,与 Web容器 相关的加载过程实际上已经完成了SpringMVC 的具体实现和普通的 Spring应用程序 的实现并没有太大的差别。
在DispatcherServlet的初始化过程中以对HandlerMapping的初始化调用作为触发点了解SpringMVC模块初始化的方法调用关系。这个调用关系最初是由HttpServletBean的init()方法触发的这个HttpServletBean是HttpServlet的子类。接着会在HttpServletBean的子类FrameworkServlet中对IoC容器完成初始化在这个初始化方法中会调用DispatcherServlet的initStrategies()方法该方法包括对各种MVC框架的实现元素比如支持国际化的LocalResolver、支持request映射的HandlerMappings以及视图生成的ViewResolver等。由该方法启动整个Spring MVC框架的初始化。
DispatcherServlet 的初始化过程中,以对 HandlerMapping 的初始化调用作为触发点,了解 SpringMVC模块 初始化的方法调用关系。这个调用关系最初是由 HttpServletBean init()方法 触发的,这个 HttpServletBean HttpServlet 的子类。接着会在 HttpServletBean 的子类 FrameworkServlet 中对 IoC容器 完成初始化,在这个初始化方法中,会调用 DispatcherServlet initStrategies()方法,该方法包括对各种 MVC框架 的实现元素,比如支持国际化的 LocalResolver、支持 request 映射的 HandlerMappings以及视图生成的 ViewResolver 等。由该方法启动整个 SpringMVC框架 的初始化。
```java
public class DispatcherServlet extends FrameworkServlet {
/**
* 初始化此servlet使用的策略对象。
* 初始化此 servlet 使用的策略对象。
* 可以在子类中重写以便初始化进一步的策略对象U8C
*/
protected void initStrategies(ApplicationContext context) {
@ -306,7 +308,7 @@ public class DispatcherServlet extends FrameworkServlet {
initLocaleResolver(context);
// 主题view层
initThemeResolver(context);
// 解析url和Method的对应关系
// 解析 url Method 的对应关系
initHandlerMappings(context);
// 适配器匹配
initHandlerAdapters(context);
@ -321,42 +323,42 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
```
对于具体的初始化过程根据上面的方法名称很容易理解。以HandlerMapping为例来说明这个initHandlerMappings()过程。这里的Mapping关系的作用是为HTTP请求找到相应的Controller控制器从而利用这些控制器Controller去完成设计好的数据处理工作。
对于具体的初始化过程,根据上面的方法名称,很容易理解。以 HandlerMapping 为例来说明这个 initHandlerMappings()过程。这里的 Mapping关系 的作用是,为 HTTP请求 找到相应的 Controller控制器从而利用这些 控制器Controller 去完成设计好的数据处理工作。
HandlerMappings完成对MVC中Controller的定义和配置只不过在Web这个特定的应用环境中这些控制器是与具体的HTTP请求相对应的。在HandlerMapping初始化的过程中把在Bean配置文件中配置好的HandlerMapping从IoC容器中取得。
HandlerMappings 完成对 MVC Controller 的定义和配置,只不过在 Web 这个特定的应用环境中,这些控制器是与具体的 HTTP请求 相对应的。在 HandlerMapping初始化 的过程中,把在 Bean配置文件 中配置好的 HandlerMapping IoC容器 中取得。
```java
/**
* 初始化此类使用的HandlerMappings。
* 如果在BeanFactory中没有为此命名空间定义的HandlerMapping bean则默认为BeanNameUrlHandlerMapping
* 初始化此类使用的 HandlerMappings。
* 如果在 BeanFactory 中没有为此命名空间定义的 HandlerMapping bean则默认为 BeanNameUrlHandlerMapping
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 这个detectAllHandlerMappings默认为true表示从所有的IoC容器中获取所有的HandlerMappings
// 这个 detectAllHandlerMappings 默认为 true表示从所有的 IoC容器 中获取所有的HandlerMappings
if (this.detectAllHandlerMappings) {
// 查找所有的HandlerMapping从应用上下文context及其双亲上下文中
// 查找所有的 HandlerMapping 应用上下文context 及其双亲上下文中
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(
matchingBeans.values());
// 保持HandlerMappings的有序性
// 保持 HandlerMappings 的有序性
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 根据名称从当前的IoC容器中通过getBean()获取HandlerMapping
// 根据名称从当前的 IoC容器 中通过 getBean() 取HandlerMapping
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,
HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// 忽略稍后将添加默认的HandlerMapping
// 忽略,稍后将添加默认的 HandlerMapping
}
}
// 如果找不到其他映射请通过注册默认的HandlerMapping确保至少有一个HandlerMapping
// 如果找不到其他映射,请通过注册默认的 HandlerMapping 确保至少有一个 HandlerMapping
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
@ -366,20 +368,18 @@ HandlerMappings完成对MVC中Controller的定义和配置只不过在Web这
}
}
```
经过以上读取过程handlerMappings变量就已经获取了在Bean中配置好的映射关系。其他的初始化过程和handlerMappings比较类似都是直接从IoC容器中读入配置所以这里的MVC初始化过程是建立在IoC容器已经初始化完成的基础上的。
经过以上读取过程handlerMappings变量 就已经获取了在 Bean 中配置好的映射关系。其他的初始化过程和 handlerMappings 比较类似,都是直接从 IoC容器 中读入配置,所以这里的 MVC初始化过程 是建立在 IoC容器 已经初始化完成的基础上的。
## 4 SpringMVC处理分发HTTP请求
### 4.1 HandlerMapping的配置和设计原理
前面分析了DispatcherServlet对Spring MVC框架的初始化过程在此基础上我们再进一步分析HandlerMapping的实现原理看看这个MVC框架中比较关键的控制部分是如何实现的。
前面分析了 DispatcherServlet SpringMVC框架 的初始化过程,在此基础上,我们再进一步分析 HandlerMapping 的实现原理,看看这个 MVC框架 中比较关键的控制部分是如何实现的。
在初始化完成时在上下文环境中已定义的所有HandlerMapping都已经被加载了这些加载的handlerMappings被放在一个List中并被排序存储着HTTP请求对应的映射数据。这个List中的每一个元素都对应着一个具体handlerMapping的配置一般每一个handlerMapping
可以持有一系列从URL请求到Controller的映射而Spring MVC提供了一系列的HandlerMapping实现。
在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。
![avatar](/images/springMVC/HandlerMapping组件.png)
以SimpleUrlHandlerMapping这个handlerMapping为例来分析HandlerMapping的设计与实现。在SimpleUrlHandlerMapping中定义了一个map来 持有 一系列的映射关系。通过这些在HandlerMapping中定义的映射关系即这些URL请求和控制器的对应关系使Spring MVC
应用可以根据HTTP请求确定一个对应的Controller。具体来说这些映射关系是通过接口HandlerMapping来封装的在HandlerMapping接 口中定义了一个getHandler方法通过这个方法可以获得与HTTP请求对应的HandlerExecutionChain在这个HandlerExecutionChain
封装了具体的Controller对象。
以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL请求 和控制器的对应关系,使 SpringMVC
应用 可以根据 HTTP请求 确定一个对应的 Controller。具体来说这些映射关系是通过 HandlerMapping接口 来封装的,在 HandlerMapping接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP请求 对应的 HandlerExecutionChain在这个 HandlerExecutionChain 中,封装了具体的 Controller对象。
```java
public interface HandlerMapping {
@ -396,15 +396,13 @@ public interface HandlerMapping {
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* 返回的这个HandlerExecutionChain不但持有handler本身还包括了处理这个HTTP请求的拦截器
* 返回的这个 HandlerExecutionChain 不但持有 handler本身还包括了处理这个 HTTP请求 的拦截器
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
```
这个HandlerExecutionChain的实现看起来比较简洁它持有一个Interceptor链和一个handler对象这个handler对象实际上就是HTTP请求对应的Controller在持有这个handler对象的同时还在HandlerExecutionChain中设置了一个拦截器链通过这个拦截器链中的拦截器,
可以为handler对象提供功能的增强。要完成这些工作需要对拦截器链和handler都进行配置这些配置都是在HandlerExecutionChain的初始化函数中完成的。为了维护这个拦截器链和handlerHandlerExecutionChain还提供了一系列与拦截器链维护相关的操作比如为拦
截器链增加拦截器的addInterceptor()方法。
这个 HandlerExecutionChain 的实现看起来比较简洁,它持有一个 拦截器链(HandlerInterceptor对象列表) 和一个 handler对象这个 handler对象 实际上就是 HTTP请求 对应的 Controller在持有这个 handler对象 的同时,还在 HandlerExecutionChain 中设置了一个拦截器链,通过这个拦截器链中的拦截器,可以为 handler对象 提供功能的增强。要完成这些工作,需要对拦截器链和 handler 都进行配置,这些配置都是在 HandlerExecutionChain 的初始化函数中完成的。为了维护这个拦截器链和 handlerHandlerExecutionChain 还提供了一系列与拦截器链维护相关的操作,比如,为拦截器链增加拦截器的 addInterceptor()方法。
```java
public class HandlerExecutionChain {
@ -460,7 +458,7 @@ public class HandlerExecutionChain {
}
/**
* 延迟初始化interceptorList和interceptors集合
* 延迟初始化 interceptorList interceptors集合
*/
private void initInterceptorList() {
if (this.interceptorList == null) {
@ -496,12 +494,13 @@ public class HandlerExecutionChain {
}
}
```
HandlerExecutionChain中定义的Handler和Interceptor需要在定义HandlerMapping时配置好例如对具体的SimpleURLHandlerMapping要做的就是根据URL映射的方式注册Handler和Interceptor从而维护一个反映这种映射关系的handlerMap。当需要匹配HTTP请求时需要查询这个handlerMap中的信息来得到对应的HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程这个注册过程在容器对Bean进行依赖注入时发生它实际上是通过一个Bean的postProcessor()来完成的。以SimpleHandlerMapping为例需要注意的是这里用到了对容器的回调只有SimpleHandlerMapping是ApplicationContextAware的子类才能启动这个注册过程。这个注册过程完成的是反映URL和Controller之间映射关系的handlerMap的建立。
HandlerExecutionChain 中定义的 Handler HandlerInterceptor[]属性 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping要做的就是根据 URL映射 的方式,注册 Handler HandlerInterceptor[],从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP请求 时,需要查询这个 handlerMap 中的信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean postProcessor() 来完成的。以 SimpleHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleHandlerMapping ApplicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL Controller 之间映射关系的 handlerMap 的建立。
![avatar](/images/springMVC/SimpleUrlHandlerMapping的继承关系.png)
```java
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
@ -509,18 +508,18 @@ public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
}
/**
* 为相应的路径注册URL映射中指定的所有handlers处理程序
* 为相应的路径注册 URL映射 中指定的所有 handlers处理程序
*/
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
// 这里对bean的配置进行解析然后调用父类的registerHandler()方法进行解析
// 这里对 bean 的配置进行解析,然后调用父类的 registerHandler()方法 进行解析
for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// 如果url没有斜线就在前面加上斜线
// 如果 url 没有斜线,就在前面加上斜线
if (!url.startsWith("/")) {
url = "/" + url;
}
@ -535,12 +534,12 @@ public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
}
}
```
这个SimpleUrlHandlerMapping注册过程的完成很大一部分需要它的基类来配合这个基类就是AbstractUrlHandlerMapping。在AbstractUrlHandlerMapping的处理过程中如果使用Bean的名称作为映射那么直接从容器中获取这个HTTP映射对应的Bean然后还要对不同的URL配置进行解析处理比如在HTTP请求中配置成“/”和通配符“/*” 的URL以及正常的URL请求完成这个解析处理过程以后
把URL和handler作为键值对放到一个handlerMap中去。
这个 SimpleUrlHandlerMapping 注册过程的完成,很大一部分需要它的基类来配合,这个基类就是 AbstractUrlHandlerMapping。在 AbstractUrlHandlerMapping 的处理过程中,如果使用 Bean 的名称作为映射,那么直接从容器中获取这个 HTTP映射 对应的 Bean然后还要对不同的 URL配置 进行解析处理,比如在 HTTP请求 中配置成 “/” 和 通配符“/*” 的 URL以及正常的 URL请求完成这个解析处理过程以后会把 URL 和 handler 作为键值对放到一个 handlerMap 中去。
```java
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
/**
* 为给定的URL路径注册指定的handler处理程序
* 为给定的 URL路径 注册指定的 handler处理程序
*/
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
@ -550,14 +549,14 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* 为给定的URL路径注册指定的handler处理程序
* 为给定的 URL路径 注册指定的 handler处理程序
*/
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// 如果使用bean名称进行映射就直接从IoC容器中获取该bean名称对应的handler
// 如果使用 bean名称 进行映射,就直接从 IoC容器 中获取该 bean名称 对应的 handler
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
@ -574,21 +573,21 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
}
else {
// 处理URL是"/"的映射,把这个"/"映射的controller设置到rootHandler中
// 处理 URL "/" 的映射,把这个 "/" 映射的 controller 设置到 rootHandler
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
// 处理URL是"/"的映射,把这个"/"映射的controller设置到defaultHandler中
// 处理 URL "/" 的映射,把这个 "/" 映射的 controller 设置到 defaultHandler
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
// 处理正常的URL映射此handlerMap的key和value分别代表URL和映射的Controller
// 处理正常的 URL映射 handlerMap key value 分别代表 URL 映射的Controller
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
@ -599,7 +598,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* 为此handler映射设置根handler即要为根路径"/"注册的handler
* 为此 handler映射 设置 根handler即要为根路径"/")注册的 handler
* <p>Default is {@code null}, indicating no root handler.
*/
public void setRootHandler(Object rootHandler) {
@ -611,7 +610,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
/**
* 设置此handler映射的默认handler。如果未找到特定映射则将返回此handler
* 设置 此handler映射 的默认 handler。如果未找到特定映射则将返回 此handler
*/
public void setDefaultHandler(Object defaultHandler) {
this.defaultHandler = defaultHandler;
@ -622,50 +621,50 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
}
```
这里的handlerMap是一个HashMap其中保存了URL请求和Controller的映射关系这个handlerMap是在AbstractUrlHandlerMapping中定义的 Map<String, object> handlerMap = new LinkedHashMap<String, object>() 这个配置好URL请求和handler映射数据的handlerMap为Spring MVC响应HTTP请求准备好了基本的映射数据根据这个handlerMap以及设置于其中的映射数据可以方便地由
URL请求得到它所对应的handler。有了这些准备工作Spring MVC就可以等待HTTP请求的到来了。
这里的 handlerMap 是一个 HashMap其中保存了 “URL请求” --> “Controller对象” 的映射关系,这个 handlerMap 是在 AbstractUrlHandlerMapping 中定义的( Map<String, object> handlerMap = new LinkedHashMap<String, object>() ),这个配置好 URL请求 和 handler映射数据 的 handlerMap为 SpringMVC 响应 HTTP请求 准备好了基本的映射数据,根据这个 handlerMap 以及设置于其中的映射数据,可以方便地由 URL请求 得到它所对应的 handler。有了这些准备工作SpringMVC 就可以等待 HTTP请求 的到来了。
### 4.2 使用HandlerMapping完成请求的映射处理
继续通过SimpleUrlHandlerMapping的实现来分析HandlerMapping的接口方法getHandler()该方法会根据初始化时得到的映射关系来生成DispatcherServlet需要的HandlerExecutionChain也就是说这个getHandler()方法是实际使用HandlerMapping完成请求的映射处理的地方。在前面的HandlerExecutionChain的执行过程中首先在AbstractHandlerMapping中启动getHandler的调用。
继续通过 SimpleUrlHandlerMapping的实现 来分析 HandlerMapping 接口方法getHandler(),该方法会根据初始化时得到的映射关系来生成 DispatcherServlet 需要的 HandlerExecutionChain也就是说这个 getHandler()方法 是实际使用 HandlerMapping 完成请求的映射处理的地方。在前面的 HandlerExecutionChain 的执行过程中,首先在 AbstractHandlerMapping 中启动 getHandler() 的调用。
```java
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
/**
* 查找给定请求的handler如果找不到特定的handler则返回到defaultHandler
* 查找给定请求的 handler如果找不到特定的 handler则返回到 defaultHandler
*/
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 模板方法模式
// 这里用到了模板方法模式getHandler() 是一个模板方法定义了流程getHandlerInternal() 则是
// 一个抽象方法,交由子类实现
Object handler = getHandlerInternal(request);
// 如果找不到特定的handler则取defaultHandler
// 如果找不到特定的 handler则取 defaultHandler
if (handler == null) {
handler = getDefaultHandler();
}
// defaultHandler也没有则返回null
// defaultHandler 也没有则返回 null
if (handler == null) {
return null;
}
// 如果该handler是String类型的说明它是一个beanname
// 根据该beanname从IoC容器中获取真正的handler对象
// 如果该 handler 是 String类型的说明它是一个 beanName
// 根据该 beanName 从 IoC容器 中获取真正的 handler对象
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
// 这里把handler添加到到HandlerExecutionChain中
// 这里把 handler 添加到到 HandlerExecutionChain
return getHandlerExecutionChain(handler, request);
}
}
```
取得handler的具体过程在getHandlerInternal()方法中实现这个方法接受HTTP请求作为参数它的实现在AbstractHandlerMapping的子类AbstractUrlHandlerMapping中这个实现过程包括从HTTP请求中得到URL并根据URL到urlMapping中获得handler。
取得 handler 的具体过程在 getHandlerInternal()方法 中实现,这个方法接受 HTTP请求 作为参数,它的实现在 AbstractHandlerMapping 的子类 AbstractUrlHandlerMapping 中,这个实现过程包括从 HTTP请求 中得到 URL并根据 URL urlMapping 中获得 handler。
```java
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
/**
* 查找给定请求的URL路径 对应的handler
* 查找 给定请求的URL 对应的 handler
*/
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 从request中获取请求的URL路径
// 从 request 中获取请求的 URL路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// 将得到的URL路径与handler进行匹配得到对应的handler如果没有对应的handler
// 则返回null这样默认的handler会被使用
// 将得到的 URL路径 handler 进行匹配,得到对应的 handler如果没有对应的 handler
// 则返回 null这样 默认的handler 会被使用
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
@ -674,7 +673,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
// 使用默认的handler
// 使用 默认的handler
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
@ -698,7 +697,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
}
/**
* 查找给定URL路径的handler实例
* 查找给定 URL路径 handler实例
*/
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// 直接匹配
@ -758,28 +757,28 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
}
}
```
经过这一系列对HTTP请求进行解析和匹配handler的过程得到了与请求对应的handler处理器。在返回的handler中已经完成了在HandlerExecutionChain中进行封装的工作为handler对HTTP请求的响应做好了准备。
经过这一系列对 HTTP请求 进行解析和匹配 handler 的过程,得到了与请求对应的 handler处理器。在返回的 handler 中,已经完成了在 HandlerExecutionChain 中进行封装的工作,为 handler HTTP请求 的响应做好了准备。
### 4.3 DispatcherServlet对HTTP请求的分发处理
DispatcherServlet是Spring MVC框架中非常重要的一个类不但建立了自己持有的IoC容器还肩负着请求分发处理的重任对HTTP请求的处理是在doService()方法中完成的。DispatcherServlet是HttpServlet的子类 与其他HttpServlet一样可以通过doService()来响应HTTP的请求。然而依照Spring MVC的使用业务逻辑的调用入口是在handler的handler()方法中实现的这是连接Spring MVC和应用业务逻辑实现的地方。
DispatcherServlet SpringMVC框架 中非常重要的一个类,不但建立了自己持有的 IoC容器还肩负着请求分发处理的重任 HTTP请求 的处理是在 doService()方法 中完成的。DispatcherServlet HttpServlet 的子类 ,与其他 HttpServlet 一样,可以通过 doService() 来响应 HTTP的请求。然而依照 SpringMVC 的使用,业务逻辑的调用入口是在 handler handler()方法 中实现的,这是连接 SpringMVC 和应用业务逻辑实现的地方。
```java
public class DispatcherServlet extends FrameworkServlet {
/** 此servlet使用的HandlerMappings列表 */
/** 此 DispatcherServlet 使用的 HandlerMapping对象列表 */
private List<HandlerMapping> handlerMappings;
/** 此servlet使用的HandlerAdapter列表 */
/** 此 DispatcherServlet 使用的 HandlerAdapter对象列表 */
private List<HandlerAdapter> handlerAdapters;
/**
* 公开DispatcherServlet特定的请求属性并将其委托给doDispatch()方法进行实际的分发
* 公开 DispatcherServlet 特定的请求属性,并将其委托给 doDispatch()方法 进行实际的分发
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (logger.isDebugEnabled()) {
// 得到请求的URI
// 得到 请求的URI
String requestUri = urlPathHelper.getRequestUri(request);
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult()
? " resumed" : "";
@ -836,8 +835,8 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* 中央控制器,控制请求的转发
* 对请求的处理实际上是由doDispatch()来完成的它是DispatcherServlet完成HTTP请求分发处理的主要方法,
* 包括准备ModelAndView调用getHandler()方法来响应HTTP请求然后通过执行Handler的处理来获取请求的
* 对请求的处理实际上是由 doDispatch() 来完成的,它是 DispatcherServlet 完成 HTTP请求 分发处理的主要方法,
* 包括准备 ModelAndView调用 getHandler()方法 来响应 HTTP请求然后通过执行 Handler的处理 来获取请求的
* 处理结果,最后把结果返回出去
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
@ -849,7 +848,7 @@ public class DispatcherServlet extends FrameworkServlet {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
// 为视图准备好一个ModelAndView这个ModelAndView持有handler处理请求的结果
// 为视图准备好一个 ModelAndView这个 ModelAndView 持有 handler处理请求的结果
ModelAndView mv = null;
Exception dispatchException = null;
@ -858,20 +857,20 @@ public class DispatcherServlet extends FrameworkServlet {
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 2.取得处理当前请求的controller这里也称为hanlder处理器这里并不是
// 直接返回controller而是返回的HandlerExecutionChain请求处理器链对象
// 该对象封装了handler和interceptors
// 2.取得处理当前请求的 Controller对象这里也称为 hanlder处理器这里并不是
// 直接返回 controller对象,而是返回的 HandlerExecutionChain请求处理器链对象
// 该对象封装了 handler interceptors
mappedHandler = getHandler(processedRequest, false);
// 如果handler为空,则返回404
// 如果 handler 为空,则返回 404
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 3. 获取处理request的处理器适配器handler adapter
// 3. 获取处理 request 的处理器适配器 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 获取请求方式GET, POST, PUT
// 获取 请求方式GET, POST, PUT
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
@ -939,7 +938,7 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* 返回此请求的HandlerExecutionChain按顺序尝试所有的HandlerMapping
* 返回此请求的 HandlerExecutionChain按顺序尝试所有的 HandlerMapping
*/
@Deprecated
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache)
@ -948,17 +947,17 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* 返回此请求的HandlerExecutionChain
* 返回此请求的 HandlerExecutionChain
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request)
throws Exception {
// 遍历 此servlet使用的HandlerMapping列表
// 遍历 此servlet 使用的 HandlerMapping列表
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler map [" + hm
+ "] in DispatcherServlet with name '" + getServletName() + "'");
}
// 查找给定请求的handler
// 查找给定请求的 handler
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
@ -968,10 +967,10 @@ public class DispatcherServlet extends FrameworkServlet {
}
/**
* 返回此处理程序对象handler的HandlerAdapter
* 返回 此处理程序对象handler HandlerAdapter
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
// 对所有持有的HandlerAdapter进行匹配
// 对所有持有的 HandlerAdapter 进行匹配
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
@ -985,11 +984,11 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
```
通过判断可以知道这个handler是不是Controller接口的实现比如可以通过具体HandlerAdapter的实现来了解这个适配过程。以SimpleControllerHandlerAdapter的实现为例来了解这个判断是怎样起作用的。
通过判断,可以知道这个 handler 是不是 Controller接口 的实现,比如可以通过具体 HandlerAdapter 的实现来了解这个适配过程。以 SimpleControllerHandlerAdapter的实现 为例来了解这个判断是怎样起作用的。
```java
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
// 判断要执行的handler是不是Controller类型的
// 判断要执行的 handler 是不是 Controller类型的
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@ -1009,8 +1008,4 @@ public class SimpleControllerHandlerAdapter implements HandlerAdapter {
}
```
经过上面一系列的处理得到了handler对象接着就可以开始调用handler对象中的HTTP响应动作了。在handler中封装了应用业务逻辑由这些逻辑对HTTP请求进行相应的处理生成需要的数据并把这些数据封装到ModelAndView对象中去这个ModelAndView的数据封装是Spring MVC框架的要求。对handler来说 这些都是通过调用handler()方法中的handleRequest()方法来触发完成的。在得到ModelAndView对象以后这个ModelAndView对象会被交给MVC模式中的视图类由视图类对ModelAndView对象中的数据进行呈现。
经过上面一系列的处理,得到了 handler对象接着就可以开始调用 handler对象 中的 HTTP响应动作了。在 handler 中封装了应用业务逻辑,由这些逻辑对 HTTP请求 进行相应的处理,生成需要的数据,并把这些数据封装到 ModelAndView对象 中去,这个 ModelAndView 的数据封装是 SpringMVC框架 的要求。对 handler 来说, 这些都是通过调用 handler()方法 中的 handleRequest()方法 来触发完成的。在得到 ModelAndView对象 以后,这个 ModelAndView对象 会被交给 MVC模式 中的视图类,由视图类对 ModelAndView对象 中的数据进行呈现。

@ -1,18 +1,18 @@
JavaEE应用中的事务处理是一个重要并且涉及范围很广的领域。事务管理的实现往往涉及并发和数据一致性方面的问题。作为应用平台的Spring具有在多种环境中配置和使用事务处理的能力也就是说通过使用Spring的事务组件可以把事务处理的工作统一起来并为事务处理提供通用的支持。
JavaEE应用 中的事务处理是一个重要并且涉及范围很广的领域。事务管理的实现往往涉及并发和数据一致性方面的问题。作为应用平台的 Spring具有在多种环境中配置和使用事务处理的能力也就是说通过使用 Spring 的事务组件,可以把事务处理的工作统一起来,并为事务处理提供通用的支持。
在涉及单个数据库局部事务的事务处理中事务的最终实现和数据库的支持是紧密相关的。对局部数据库事务来说一个事务处理的操作单元往往对应着一系列的数据库操作。数据库产品对这些数据库的SQL操作已经提供了原子性的支持对SQL操作而言,它的操作结果有两种: 一种是提交成功,数据库操作成功;另一种是回滚,数据库操作不成功,恢复到操作以前的状态。
在涉及单个数据库局部事务的事务处理中,事务的最终实现和数据库的支持是紧密相关的。对局部数据库事务来说,一个事务处理的操作单元往往对应着一系列的数据库操作。数据库产品对这些数据库的 SQL操作 已经提供了原子性的支持,对 SQL操作 而言,它的操作结果有两种: 一种是提交成功,数据库操作成功;另一种是回滚,数据库操作不成功,恢复到操作以前的状态。
在事务处理中事务处理单元的设计与相应的业务逻辑设计有很紧密的联系。在很多情况下一个业务逻辑处理不会只有一个单独的数据库操作而是有一组数据库操作。在这个处理过程中首先涉及的是事务处理单元划分的问题Spring借助IoC容器的强大配置能力为应用提供了声明式的事务划分方式这种声明式的事务处理为Spring应用使用事务管理提供了统一的方式。有了Spring事务管理的支持只需要通过一些简单的配置应用就能完成复杂的事务处理工作从而为用户使用事务处理提供很大的方便。
## 1 Spring事务处理的设计概览
Spring事务处理模块的类层次结构如下图所示。
在事务处理中事务处理单元的设计与相应的业务逻辑设计有很紧密的联系。在很多情况下一个业务逻辑处理不会只有一个单独的数据库操作而是有一组数据库操作。在这个处理过程中首先涉及的是事务处理单元划分的问题Spring 借助 IoC容器 的强大配置能力,为应用提供了声明式的事务划分方式,这种声明式的事务处理,为 Spring应用 使用事务管理提供了统一的方式。有了 Spring事务管理 的支持,只需要通过一些简单的配置,应用就能完成复杂的事务处理工作,从而为用户使用事务处理提供很大的方便。
## 1 Spring事务处理 的设计概览
Spring事务处理模块 的类层次结构如下图所示。
![avatar](/images/springTransaction/Spring事务处理模块类层次结构.png)
从上图可以看到Spring事务处理模块是通过AOP功能来实现声明式事务处理的比如事务属性的配置和读取事务对象的抽象等。因此在Spring事务处理中可以通过设计一个TransactionProxyFactoryBean来使用AOP功能通过这个TransactionProxyFactoryBean可以生成Proxy代理对象在这个代理对象中通过TransactionInterceptor来完成对代理方法的拦截正是这些AOP的拦截功能将事务处理的功能编织进来。
从上图可以看到Spring事务处理模块 是通过 AOP功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP功能通过这个 TransactionProxyFactoryBean 可以生成 Proxy代理对象在这个代理对象中通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。
对于具体的事务处理实现比如事务的生成、提交、回滚、挂起等由于不同的底层数据库有不同的支持方式因此在Spring事务处理中对主要的事务实现做了一个抽象和适配。适配的具体事务处理器包括对DataSource数据源的事务处理支持对Hibernate数据源的事务处理支持对JDO数据源的事务处理支持对JPA和JTA等数据源的事务处理支持等。这一系列的事务处理支持都是通过设计PlatformTransactionManager、AbstractPlatforTransactionManager以及一系列具体事务处理器来实现的而PlatformTransactionManager又实现了TransactionInterceptor接口通过这样一个接口实现设计就把这一系列的事务处理的实现与前面提到的TransactionProxyFactoryBean结合起来从而形成了一个Spring声明式事务处理的设计体系。
对于具体的事务处理实现,比如事务的生成、提交、回滚、挂起等,由于不同的底层数据库有不同的支持方式,因此,在 Spring事务处理中对主要的事务实现做了一个抽象和适配。适配的具体事务处理器包括 DataSource数据源 的事务处理支持,对 Hibernate数据源 的事务处理支持,对 JDO数据源 的事务处理支持,对 JPA JTA 等数据源的事务处理支持等。这一系列的事务处理支持,都是通过设计 PlatformTransactionManager、AbstractPlatformTransactionManager 以及一系列具体事务处理器来实现的,而 PlatformTransactionManager 又实现了 TransactionInterceptor接口通过这样一个接口实现设计就把这一系列的事务处理的实现与前面提到的 TransactionProxyFactoryBean 结合起来,从而形成了一个 Spring声明式事务处理 的设计体系。
## 2 Spring事务处理的应用场景
Spring作为应用平台或框架的设计出发点是支持POJO的开发这点在实现事务处理的时候也不例外。在Spring中它既支持编程式事务管理方式又支持声明式事务处理方式在使用Spring处理事务的时候声明式事务处理通常比编程式事务管理更方便些。
## 2 Spring事务处理 的应用场景
Spring 作为应用平台或框架的设计出发点是支持 POJO的开发这点在实现事务处理的时候也不例外。在 Spring 中,它既支持编程式事务管理方式,又支持声明式事务处理方式,在使用 Spring 处理事务的时候,声明式事务处理通常比编程式事务管理更方便些。
Spring对应用的支持一方面通过声明式事务处理将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过AOP的方式来完成的。显然Spring已经把那些通用的事务处理过程抽象出来并通过AOP的方式进行封装然后用声明式的使用方式交付给客户使用。这样应用程序可以更简单地管理事务并且只需要关注事务的处理策略。另一方面应用在选择数据源时可能会采取不同的方案当以Spring作为平台时Spring在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring为常用的数据源的事务处理支持提供了一系列的TransactionManager。这些Spring封装好的TransactionManager为应用提供了很大的方便因为在这些具体事务处理过程中已经根据底层的实现封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程这样应用在使用不同的数据源时可以做到事务处理的即开即用。这样的另一个好处是如果应用有其他的数据源事务处理需要Spring也提供了一种一致的方式。这种有机的事务过程抽象和具体的事务处理相结合的设计是我们在日常的开发中非常需要模仿学习的。
Spring 对应用的支持,一方面,通过声明式事务处理,将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过 AOP 的方式来完成的。显然Spring 已经把那些通用的事务处理过程抽象出来,并通过 AOP 的方式进行封装,然后用声明式的使用方式交付给客户使用。这样,应用程序可以更简单地管理事务,并且只需要关注事务的处理策略。另一方面,应用在选择数据源时可能会采取不同的方案,当以 Spring 作为平台时Spring 在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring 为常用的数据源的事务处理支持提供了一系列的 TransactionManager。这些 Spring 封装好的 TransactionManager 为应用提供了很大的方便,因为在这些具体事务处理过程中,已经根据底层的实现,封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程,这样应用在使用不同的数据源时,可以做到事务处理的即开即用。这样的另一个好处是,如果应用有其他的数据源事务处理需要, Spring 也提供了一种一致的方式。这种 有机的事务过程抽象 和 具体的事务处理 相结合的设计,是我们在日常的开发中非常需要模仿学习的。事务处理将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过AOP的方式来完成的。显然Spring已经把那些通用的事务处理过程抽象出来并通过AOP的方式进行封装然后用声明式的使用方式交付给客户使用。这样应用程序可以更简单地管理事务并且只需要关注事务的处理策略。另一方面应用在选择数据源时可能会采取不同的方案当以Spring作为平台时Spring在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring为常用的数据源的事务处理支持提供了一系列的TransactionManager。这些Spring封装好的TransactionManager为应用提供了很大的方便因为在这些具体事务处理过程中已经根据底层的实现封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程这样应用在使用不同的数据源时可以做到事务处理的即开即用。这样的另一个好处是如果应用有其他的数据源事务处理需要Spring也提供了一种一致的方式。这种有机的事务过程抽象和具体的事务处理相结合的设计是我们在日常的开发中非常需要模仿学习的。

@ -1 +1,546 @@
努力编写中...
## 1 事务处理的编程式使用
```java
TransactionDefinition td = new DefaultTransactionDefinition();
// transactionManager 是某一个具体的 PlatformTransactionManager实现类 的对象
TransactionStatus ts = transactionManager.getTransaction(td);
try {
// 这里是需要进行事务处理的方法调用
}
catch (Exception e) {
transactionManager.rollback(ts);
throw e;
}
transactionManager.commit(ts);
```
在使用编程式事务处理的过程中,利用 DefaultTransactionDefinition对象 来持有事务处理属性。同时,在创建事务的过程中得到一个 TransactionStatus对象然后通过直接调用 transactionManager对象 的 commit() 和 rollback()方法 来完成事务处理。在这个编程式使用事务管理的过程中,没有看到框架特性的使用,非常简单和直接,很好地说明了事务管理的基本实现过程,以及在 Spring事务处理实现 中涉及一些主要的类,比如 TransationStatus、TransactionManager 等,对这些类的使用与声明式事务处理的最终实现是一样的。
与编程式使用事务管理不同,在使用声明式事务处理的时候,因为涉及 Spring框架 对事务处理的统一管理,以及对并发事务和事务属性的处理,所以采用的是一个比较复杂的处理过程,但复杂归复杂,这个过程对使用声明式事务处理的应用来说,基本上是不可见的,而是由 Spring框架 来完成的。有了这些背景铺垫和前面对 AOP封装事务处理 的了解,下面来看看 Spring 是如何提供声明式事务处理的Spring 在这个相对较为复杂的过程中封装了什么。这层封装包括在事务处理中事务的创建、提交和回滚等比较核心的操作。
## 2 事务的创建
作为声明式事务处理实现的起始点,需要注意 TransactionInterceptor拦截器 的 invoke()回调 中使用的 createTransactionIfNecessary()方法,这个方法是在 TransactionInterceptor 的基类 TransactionAspectSupport 中实现的。为了了解这个方法的实现,先分析一下 TransactionInterceptor 的基类实现 TransactionAspectSupport并以这个方法的实现为入口了解 Spring 是如何根据当前的事务状态和事务属性配置完成事务创建的。
这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction事务对象 的过程,在 AbstractTransactionManager实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。
![avatar](images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png)
```java
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
/**
* 根据给定的方法和类,在必要时创建事务
*/
@Deprecated
protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
// 根据给定的方法和类获取 TransactionAttribute
// 如果 TransactionAttribute 为空,则该方法是非事务性的
TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
// 使用确定的事务管理器
PlatformTransactionManager tm = determineTransactionManager(txAttr);
return createTransactionIfNecessary(tm, txAttr, methodIdentification(method, targetClass));
}
/**
* 根据给定的 TransactionAttribute 创建事务
*/
@SuppressWarnings("serial")
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
// 如果未指定名称,则使用方法特征作为事务名称
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
// TransactionStatus 封装了事务执行的状态信息
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
// 根据定义好的 事务方法配置信息TransactionAttribute通过
// 平台事务管理器 PlatformTransactionManager 创建事务,同时返回
// TransactionStatus 来记录当前的事务状态,包括已经创建的事务
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
// 准备 TransactionInfoTransactionInfo对象 封装了事务处理的配置信息以及 TransactionStatus
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm,
TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
if (txAttr != null) {
if (logger.isTraceEnabled()) {
logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
// 为 TransactionInfo 设置 TransactionStatusTransactionStatus 持有管理事务处理
// 需要的数据transaction对象
// 如果不兼容的 TX 已经存在,事务管理器将标记错误
txInfo.newTransactionStatus(status);
}
else {
if (logger.isTraceEnabled())
logger.trace("Don't need to create transaction for [" + joinpointIdentification +
"]: This method isn't transactional.");
}
// 这里把当前的 TransactionInfo 与线程绑定,同时在 TransactionInfo 中由一个变量来保存以前
// 的 TransactionInfo这样就持有了一连串与事务处理相关的 TransactionInfo
// 虽然不一定要创建新的事务,但总会在请求事务时创建 TransactionInfo
txInfo.bindToThread();
return txInfo;
}
}
```
在以上的处理过程之后,可以看到,具体的事务创建可以交给事务处理器来完成。在事务的创建过程中,已经为事务的管理做好了准备,包括记录事务处理状态,以及绑定事务信息和线程等。下面到事务处理器中去了解一下更底层的事务创建过程。
createTransactionIfNecessary()方法 通过调用 PlatformTransactionManager 的 getTransaction()方法,生成一个 TransactionStatus对象封装了底层事务对象的创建。可以看到AbstractPlatformTransactionManager 提供了创建事务的模板这个模板会被具体的事务处理器所使用。从下面的代码中可以看到AbstractPlatformTransactionManager 会根据事务属性配置和当前进程绑定的事务信息,对事务是否需要创建,怎样创建 进行一些通用的处理,然后把事务创建的底层工作交给具体的事务处理器完成。尽管具体的事务处理器完成事务创建的过程各不相同,但是不同的事务处理器对事务属性和当前进程事务信息的处理都是相同的,在 **AbstractPlatformTransactionManager** 中完成了该实现,这个实现过程是 Spring 提供统一事务处理的一个重要部分。
```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
//---------------------------------------------------------------------
// 实现了 PlatformTransactionManager接口 的方法
// 这里用了一个 模板方法模式doGetTransaction(), doBegin() 都是交由子类实现的抽象方法
//---------------------------------------------------------------------
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
// doGetTransaction() 是抽象方法Transaction对象 的取得由具体的事务管理器
// 实现比如DataSourceTransactionManager
Object transaction = doGetTransaction();
// 缓存 debug标志以避免重复检查
boolean debugEnabled = logger.isDebugEnabled();
if (definition == null) {
// 如果没有给出事务定义,则使用默认值
definition = new DefaultTransactionDefinition();
}
// 检查当前线程是否已经存在事务,如果已经存在事务,则根据事务属性中定义的 事务传播属性配置
// 来处理事务的产生
if (isExistingTransaction(transaction)) {
// 对当前线程中已经有事务存在的情况进行处理,结果封装在 TransactionStatus 中
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// 检查 definition 中 timeout属性 的设置
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// 当前没有事务存在,这时需要根据事务属性设置来创建事务
// 这里可以看到对事务传播属性配置的处理比如MANDATORY、REQUIRED、REQUIRES_NEW、NESTED等
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 将要返回的 DefaultTransactionStatus对象封装了事务执行情况
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 创建事务由具体的事务管理器来完成DataSourceTransactionManager、HibernateTransactionManager
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException ex) {
resume(null, suspendedResources);
throw ex;
}
catch (Error err) {
resume(null, suspendedResources);
throw err;
}
}
else {
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
}
```
从上面的代码中可以看到AbstractTransactionManager 提供的创建事务的实现模板,在这个模板的基础上,具体的事务处理器需要定义自己的实现来完成底层的事务创建工作,比如需要实现 isExistingTransaction() 和 doBegin()方法。关于这些由具体事务处理器实现的方法会在下面结合具体的事务处理器实现DataSourceTransactionManager、HibernateTransactionManager进行分析。
事务创建的结果是生成一个 TransactionStatus对象 通过这个对象来保存事务处理需要的基本信息,这个对象与前面提到过的 TransactionInfo对象 联系在一起, TransactionStatus 是 TransactionInfo 的一个属性,然后会把 TransactionInfo 保存在 ThreadLocal对象 里,这样当前线程可以通过 ThreadLocal对象 取得 TransactionInfo以及与这个事务对应的 TransactionStatus对象从而把事务的处理信息与调用事务方法的当前线程绑定起来。在 AbstractPlatformTransactionManager 创建事务的过程中,可以看到 TransactionStatus 的创建过程。
```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
/**
* 通过给定的参数,创建一个 TransactionStatus实例
*/
protected DefaultTransactionStatus newTransactionStatus(
TransactionDefinition definition, Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, Object suspendedResources) {
// 这里判断是不是新事务,如果是新事务,需要把事务属性存放到当前线程中
// TransactionSynchronizationManager 维护了一系列的 ThreadLocal变量
// 来保持事务属性,比如,并发事务隔离级别,是否有活跃的事务等
boolean actualNewSynchronization = newSynchronization &&
!TransactionSynchronizationManager.isSynchronizationActive();
// 把结果记录在 DefaultTransactionStatus对象 中并返回
return new DefaultTransactionStatus(
transaction, newTransaction, actualNewSynchronization,
definition.isReadOnly(), debug, suspendedResources);
}
}
```
新事务的创建是比较好理解的,这里需要根据事务属性配置进行创建。所谓创建,首先是把创建工作交给具体的事务处理器来完成,比如 DataSourceTransactionManager把创建的事务对象在 TransactionStatus 中保存下来,然后将其他的事务属性和 线程ThreadLocal变量 进行绑定。
相对于创建全新事务的另一种情况是:在创建当前事务时,线程中已经有事务存在了。这种情况同样需要处理,在声明式事务处理中,在当前线程调用事务方法的时候,就会考虑事务的创建处理,这个处理在方法 handleExistingTransaction() 中完成。这里对现有事务的处理,会涉及事务传播属性的具体处理,比如 PROPAGATION_NOT_SUPPORTED、PROPAGATION_ REQUIRES_ NEW等。
```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
/**
* 为存在的事务创建一个 TransactionStatus实例
*/
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
// PROPAGATION_NEVER 表示 以非事务方式执行,如果当前存在事务,则抛出异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// PROPAGATION_NOT_SUPPORTED 表示 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
// 注意这里的参数transaction 为 nullnewTransaction 为 false这意味着事务方法不需要
// 放在事务环境中执行,同时挂起事务的信息记录也保存在 TransactionStatus 中,这里包括了
// 进程ThreadLocal 对事务信息的记录
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
// PROPAGATION_REQUIRES_NEW 表示 新建事务,如果当前存在事务,把当前事务挂起
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 挂起事务的信息记录保存在 TransactionStatus 中,这里包括了 进程ThreadLocal 对事务信息的记录
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
catch (Error beginErr) {
resumeAfterBeginException(transaction, suspendedResources, beginErr);
throw beginErr;
}
}
// PROPAGATION_NESTED 表示 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,
// 则执行与 PROPAGATION_REQUIRED 类似的操作
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// 在 Spring 管理的事务中 创建事务保存点
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
}
else {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, null);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
}
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
// 这里判断 在当前事务方法中的属性配置 与已有事务的属性配置是否一致,如果不一致,
// 那么不执行事务方法 并抛出异常
if (isValidateExistingTransaction()) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
Constants isoConstants = DefaultTransactionDefinition.constants;
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] specifies isolation level which is incompatible with existing transaction: " +
(currentIsolationLevel != null ?
isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
"(unknown)"));
}
}
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] is not marked as read-only but existing transaction is");
}
}
}
// 返回 DefaultTransactionStatus注意 第三个参数false 代表当前事务方法没有使用新的事务
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
}
```
## 3 事务的挂起
事务的挂起牵涉线程与事务处理信息的保存,下面看一下事务挂起的实现。
```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
/**
* 挂起给定的事务。先挂起事务同步,然后委托给 doSuspend()方法,子类一般会重写该方法。
* 该方法返回的 SuspendedResourcesHolder对象会作为参数传递给 TransactionStatus
*/
protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
try {
Object suspendedResources = null;
// 把挂起事务的处理交给具体事务处理器去完成,如果具体的事务处理器不支持事务挂起,
// 就抛出异常
if (transaction != null) {
suspendedResources = doSuspend(transaction);
}
// 这里在线程中保存与事务处理有关的信息,并重置线程中相关的 ThreadLocal变量
String name = TransactionSynchronizationManager.getCurrentTransactionName();
TransactionSynchronizationManager.setCurrentTransactionName(null);
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
TransactionSynchronizationManager.setActualTransactionActive(false);
return new SuspendedResourcesHolder(
suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
}
// 若doSuspend()方法出现RuntimeException异常或Error错误则初始的事务依然存在
catch (RuntimeException ex) {
doResumeSynchronization(suspendedSynchronizations);
throw ex;
}
catch (Error err) {
doResumeSynchronization(suspendedSynchronizations);
throw err;
}
}
else if (transaction != null) {
Object suspendedResources = doSuspend(transaction);
return new SuspendedResourcesHolder(suspendedResources);
}
else {
return null;
}
}
}
```
基于以上内容,就可以完成声明式事务处理的创建了。声明式事务处理能使事务处理应用的开发变得简单,但是简单的背后,蕴含着平台付出的许多努力。
## 4 事务的提交
下面来看看事务提交是如何实现的。有了前面的对事务创建的分析,下面来分析一下在 Spring 中,声明式事务处理的事务提交是如何完成的。事务提交的调用入口是 TransactionInteceptor 的 invoke()方法,事务提交的具体实现则在其基类 TransactionAspectSupport 的 commitTransactionAfterReturning(TransactionInfo txInfo)方法 中,其中的参数 txInfo 是创建事务时生成的。同时Spring 的事务管理框架生成的 TransactionStatus对象 就包含在 TransactionInfo对象 中。这个 commitTransactionAfterReturning()方法 在 TransactionInteceptor 的实现部分是比较简单的,它通过直接调用事务处理器来完成事务提交。
```java
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
/**
* 在事务方法成功调用后执行,若出现异常,则不执行。如果不创建事务,则不执行任何操作。
*/
protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
if (txInfo != null && txInfo.hasTransaction()) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
}
```
与前面分析事务的创建过程一样,我们需要到事务管理器中去看看事务是如何提交的。同样,在 AbstractPlatformTransactionManager 中也有一个模板方法支持具体的事务管理器对事务提交的实现,这个模板方法的实现与前面我们看到的 getTransaction() 很像。
```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
/**
* 处理实际提交的事务,这是一个模板方法,其中的 doCommit() 是一个交由子类实现的抽象方法
*/
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
// 事务提交的准备工作由具体的事务管理器来完成
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
boolean globalRollbackOnly = false;
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
globalRollbackOnly = status.isGlobalRollbackOnly();
}
// 嵌套事务的处理
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
status.releaseHeldSavepoint();
}
// 如果当前事务是一个新事务,调用具体事务处理器的 doCommit() 实现;否则,
// 不提交,由已经存在的事务来完成提交
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
// 该实现由具体的事务管理器来完成
doCommit(status);
}
// 如果我们有一个全局仅回滚标记,但仍然没有从 commit 中获得相应的异常,
// 则抛出 UnexpectedRollbackException
if (globalRollbackOnly) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
catch (Error err) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, err);
throw err;
}
// 触发器 afterCommit()回调,其中抛出的异常已传播到调用方,但该事务仍被视为已提交
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
}
```
可以看到,事务提交的准备都是由具体的事务处理器来实现的。当然,对这些事务提交的处理,需要通过对 TransactionStatus 保存的事务处理的相关状态进行判断。提交过程涉及 AbstractPlatformTransactionManager 中的 doCommit() 和 prepareForCommit()方法,它们都是抽象方法,都在具体的事务处理器中完成实现,在下面对具体事务处理器的实现原理的分析中,可以看到对这些实现方法的具体分析。
## 5 事务的回滚
```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
/**
* 处理实际的事务回滚
*/
private void processRollback(DefaultTransactionStatus status) {
try {
try {
triggerBeforeCompletion(status);
// 嵌套事务的回滚处理
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
// 当前事务调用方法中,新建事务的回滚处理
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
// 当前事务调用方法中,没有新建事务的回滚处理
else if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
// 由线程中的前一个事务来处理回滚,这里不执行任何操作
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
}
catch (RuntimeException ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
catch (Error err) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw err;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
}
finally {
cleanupAfterCompletion(status);
}
}
}
```
以上对事务的创建、提交和回滚的实现原理进行了分析,这些过程的实现都比较复杂,一方面 这些处理会涉及很多事务属性的处理;另一方面 会涉及事务处理过程中状态的设置,同时在事务处理的过程中,有许多处理也需要根据相应的状态来完成。这样看来,在实现事务处理的基本过程中就会产生许多事务处理的操作分支。
但总的来说,在事务执行的实现过程中,作为执行控制的 TransactionInfo对象 和 TransactionStatus对象 特别值得我们注意,比如它们如何与线程进行绑定,如何记录事务的执行情况等。如果大家在配置事务属性时有什么疑惑,不妨直接看看这些事务属性的处理过程,通过对这些实现原理的了解,可以极大地提高对这些事务处理属性使用的理解程度。

@ -0,0 +1,158 @@
## 1 Spring事务处理的应用场景
下面,我们以 DataSourceTransactionManager事务管理器 为例看一下在具体的事务管理器中如何实现事务创建、提交和回滚这些底层的事务处理操作。DataSourceTransationManager 和其他事务管理器一样,如 JtaTransactionManagerJpaTransactionManager 和 JdoTransactionManager都继承自 AbstractPlatformManager作为一个基类AbstractPlatfromManager 封装了 Spring事务处理 中通用的处理部分,比如事务的创建、提交、回滚,事务状态和信息的处理,与线程的绑定等,有了这些通用处理的支持,对于具体的事务管理器而言,它们只需要处理和具体数据源相关的组件设置就可以了,比如在 HibernateTransactionManager 中,就只需要配置好和 Hibnernate事务处理 相关的接口以及相关的设置。所以,从 PlatformTransactionManager组件 的设计关系上我们也可以看到Spring事务处理 的主要过程是分两个部分完成的,通用的事务处理框架是在 AbstractPlatformManager 中完成,而 Spring 的事务接口与数据源实现的接口,多半是由具体的事务管理器来完成,它们都是作为 AbstractPlatformManager 的子类来是使用的。
可以看到,在 PlatformTransactionManager组件 的设计中 ,通过 PlatformTransactionManager接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。
![avatar](/images/springTransaction/PlatformTransactionManager组件的设计.png)
## 2 DataSourceTransactionManager的实现
我们先看一下 DataSourceTransactionManager在这个事务管理器中它的实现直接与事务处理的底层实现相关。在事务开始的时候会调用 doBegin()方法,首先会得到相对应的 Connection然后可以根据事务设置的需要对 Connection 的相关属性进行配置,比如将 Connection 的 autoCommit功能 关闭,并对像 TimeoutInSeconds 这样的事务处理参数进行设置,最后通过 TransactionSynchronizationManager 来对资源进行绑定。
从下面的代码中可以看到DataSourceTransactionManager 作为 AbstractPlatformTransactionManager 的子类,在 AbstractPlatformTransactionManager 中已经为事务实现设计好了一系列的模板方法,比如 事务的提交、回滚处理等。在 DataSourceTransactionManager 中, 可以看到对模板方法中一些抽象方法的具体实现。例如,由 DataSourceTransactionManager 的 doBegin()方法 实现负责事务的创建工作。具体来说,如果使用 DataSource 创建事务,最终通过设置 Connection 的 autoCommit属性 来对事务处理进行配置。在实现过程中,需要把数据库的 Connection 和当前的线程进行绑定。对于事务的提交和回滚,都是通过直接调用 Connection 的提交和回滚来完成的,在这个实现过程中,如何取得事务处理场景中的 Connection对象也是一个值得注意的地方。
上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit属性调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。
![avatar](/images/springTransaction/PlatformTransactionManager组件的设计.png)
```java
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
/** 持有 javax.sql.DataSource对象 */
private DataSource dataSource;
/**
* 这里是产生 Transaction对象 的地方,为 Transaction 的创建提供服务,对数据库而言,
* 事务工作是由 Connection 来完成的。这里把数据库的 Connection对象 放到了 ConnectionHolder 中,
* 然后封装到一个 DataSourceTransactionObject对象 中,在这个封装过程中增加了许多为事务处理服务的
* 控制数据
*/
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
// 获取与当前线程绑定的 数据库Connection这个 Connection 在第一个事务开始
// 的地方与线程绑定
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
/**
* 判断是否存在活跃的事务,由 ConnectionHolder 的 transactionActive属性 来控制
*/
@Override
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
return (txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive());
}
/**
* 这里是处理事务开始的地方,在这里设置隔离级别,但忽略超时
*/
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (txObject.getConnectionHolder() == null ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = this.dataSource.getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// 这里是 数据库Connection 完成事务处理的重要配置,需要把 autoCommit属性 关掉
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 把当前的 数据库Connection 与线程绑定
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
DataSourceUtils.releaseConnection(con, this.dataSource);
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
/**
* 事务提交的具体实现
*/
@Override
protected void doCommit(DefaultTransactionStatus status) {
// 取得 Connection 以后通过Connection 进行提交
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
/**
* 事务提交的具体实现,通过 Connection对象 的 rollback()方法 实现
*/
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
}
```
上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit属性调用 Connection 的 commit() 和 rollback()方法 来完成的。看到这里,大家一定会觉得非常的熟悉。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。
## 3 小结
总体来说,从声明式事务的整个实现中我们看到,声明式事务处理完全可以看成是一个具体的 Spring AOP应用。从这个角度来看Spring事务处理 的实现本身就为应用开发者提供了一个非常优秀的 AOP应用 参考实例。在 Spring 的声明式事务处理中,采用了 IoC容器 的 Bean配置 为事务方法调用提供事务属性设置,从而为应用对事务处理的使用提供方便。
有了声明式的使用方式,可以把对事务处理的实现与应用代码分离出来。从 Spring实现 的角度来看,声明式事务处理的大致实现过程是这样的:在为事务处理配置好 AOP 的基础设施(比如,对应的 Proxy代理对象 和 事务处理Interceptor拦截器对象)之后,首先需要完成对这些事务属性配置的读取,这些属性的读取处理是在 TransactionInterceptor 中实现的在完成这些事务处理属性的读取之后Spring 为事务处理的具体实现做好了准备。可以看到Spring声明式事务处理 的过程同时也是一个整合事务处理实现到 Spring AOP 和 IoC容器 中去的过程。我们在整个过程中可以看到下面一些要点,在这些要点中,体现了对 Spring框架 的基本特性的灵活使用。
- 如何封装各种不同事务处理环境下的事务处理,具体来说,作为应用平台的 Spring它没法对应用使用什么样的事务处理环境做出限制这样对应用户使用的不同的事务处理器Spring事务处理平台 都需要为用户提供服务。这些事务处理实现包括在应用中常见的 DataSource 的 Connection、Hibermate 的 Transaction等Spring事务处理 通过一种统一的方式把它们封装起来,从而实现一个通用的事务处理过程,实现这部分事务处理对应用透明,使应用即开即用。
- 如何读取事务处理属性值,在事务处理属性正确读取的基础上,结合事务处理代码,从而完成在既定的事务处理配置下,事务处理方法的实现。
- 如何灵活地使用 Spring AOP框架对事务处理进行封装提供给应用即开即用的声明式事务处理功能。
在这个过程中,有几个 Spring事务处理 的核心类是我们需要关注的。其中包括 **TransactionInterceptor**,它是使用 AOP 实现声明式事务处理的拦截器,封装了 Spring 对声明式事务处理实现的基本过程;还包括 TransactionAttributeSource 和 **TransactionAttribute** 这两个类,它们封装了对声明式事务处理属性的识别,以及信息的读入和配置。我们看到的 TransactionAttribute对象可以视为对事务处理属性的数据抽象如果在使用声明式事务处理的时候应用没有配置这些属性Spring 将为用户提供 DefaultTransactionAttribute对象该对象提供了默认的事务处理属性设置。
在事务处理过程中,可以看到 **TransactionInfo****TransactionStatus** 这两个对象它们是存放事务处理信息的主要数据对象它们通过与线程的绑定来实现事务的隔离性。具体来说TransactionInfo对象 本身就像是一个栈,对应着每一次事务方法的调用,它会保存每一次事务方法调用的事务处理信息。值得注意的是,在 TransactionInfo对象 中,它持有 TransactionStatus对象这个 TransactionStatus 是非常重要的。由这个 TransactionStatus 来掌管事务执行的详细信息,包括具体的事务对象、事务执行状态、事务设置状态等。
在事务的创建、启动、提交和回滚的过程中,都需要与这个 TransactionStatus对象 中的数据打交道。在准备完这些与事务管理有关的数据之后,具体的事务处理是由 事务处理器TransactionManager 来完成的。在事务处理器完成事务处理的过程中,与具体事务处理器无关的操作都被封装到 AbstractPlatformTransactionManager 中实现了。这个抽象的事务处理器为不同的具体事务处理器提供了通用的事务处理模板,它封装了在事务处理过程中,与具体事务处理器无关的公共的事务处理部分。我们在具体的事务处理器(比如 DataSourceTransactionManager 和 HibernateTransactionManager)的实现中可以看到,最为底层的事务创建、挂起、提交、回滚操作。
在 Spring 中,也可以通过编程式的方法来使用事务处理器,以帮助我们处理事务。在编程式的事务处理使用中, TransactionDefinition 是定义事务处理属性的类。对于事务处理属性Spring 还提供了一个默认的事务属性 DefaultTransactionDefinition 来供用户使用。这种事务处理方式在实现上看起来比声明式事务处理要简单,但编程式实现事务处理却会造成事务处理与业务代码的紧密耦合,因而不经常被使用。在这里,我们之所以举编程式使用事务处理的例子,是因为通过了解编程式事务处理的使用,可以清楚地了解 Spring 统一实现事务处理的大致过程。
有了这个背景,结合对声明式事务处理实现原理的详细分析,比如在声明式事务处理中,使用 AOP 对事务处理进行封装,对事务属性配置进行的处理,与线程绑定从而处理事务并发,并结合事务处理器的使用等,能够在很大程度上提高我们对整个 Spring事务处理实现 的理解。

@ -1,276 +1,424 @@
## 1 设计原理与基本过程
在使用Spring声明式事务处理的时候一种常用的方法是结合IoC容器和Spring已有的TransactionProxyFactoryBean对事务管理进行配置比如可以在这个TransactionProxyFactoryBean中为事务方法配置传播行为、并发事务隔离级别等事务处理属性从而对声明式事务的处理提供指导。具体来说在对声明式事务处理的原理分析中声明式事务处理的实现大致可以分为以下几个部分:
在使用 Spring声明式事务处理 的时候,一种常用的方法是结合 IoC容器 Spring 已有的 TransactionProxyFactoryBean 对事务管理进行配置,比如,可以在这个 TransactionProxyFactoryBean 中为事务方法配置传播行为、并发事务隔离级别等事务处理属性,从而对声明式事务的处理提供指导。具体来说,在对声明式事务处理的原理分析中,声明式事务处理的实现大致可以分为以下几个部分:
- 读取和处理在IoC容器中配置的事务处理属性并转化为Spring事务处理需要的内部数据结构这里涉及的类是TransactionAttributeSourceAdvisor从名字可以看出它是一个AOP通知器Spring使用这个通知器来完成对事务处理属性值的处理。处理的结果是在IoC容器中配置的事务处理属性信息会被读入并转化成TransactionAttribute表示的数据对象这个数据对象是Spring对事物处理属性值的数据抽象对这些属性的处理是和TransactionProxyFactoryBean拦截下来的事务方法的处理结合起来的。
- Spring事务处理模块实现统一的事务处理过程。这个通用的事务处理过程包含处理事务配置属性以及与线程绑定完成事务处理的过程Spring通过TransactionInfo和TransactionStatus这两个数据对象在事务处理过程中记录和传递相关执行场景。
- 底层的事务处理实现。对于底层的事务操作Spring委托给具体的事务处理器来完成这些具体的事务处理器就是在IoC容器中配置声明式事务处理时配置的PlatformTransactionManager的具体实现比如DataSourceTransactionManager和HibernateTransactionManager等。
- 读取和处理在 IoC容器 中配置的事务处理属性,并转化为 Spring事务处理 需要的内部数据结构,这里涉及的类是 TransactionAttributeSourceAdvisor从名字可以看出它是一个 AOP通知器Spring 使用这个通知器来完成对事务处理属性值的处理。处理的结果是,在 IoC容器 中配置的事务处理属性信息,会被读入并转化成 TransactionAttribute 表示的数据对象,这个数据对象是 Spring 对事物处理属性值的数据抽象,对这些属性的处理是和 TransactionProxyFactoryBean 拦截下来的事务方法的处理结合起来的。
- Spring事务处理模块 实现统一的事务处理过程。这个通用的事务处理过程包含处理事务配置属性以及与线程绑定完成事务处理的过程Spring 通过 TransactionInfo TransactionStatus 这两个数据对象,在事务处理过程中记录和传递相关执行场景。
- 底层的事务处理实现。对于底层的事务操作Spring 委托给具体的事务处理器来完成,这些具体的事务处理器,就是在 IoC容器 中配置声明式事务处理时,配置的 PlatformTransactionManager 的具体实现,比如 DataSourceTransactionManager HibernateTransactionManager 等。
## 2 实现分析
### 2.1 事务处理拦截器的配置
和前面的思路一样从声明式事务处理的基本用法入手来了解它的基本实现原理。在使用声明式事务处理的时候需要在IoC容器中配置TransactionProxyFactoryBean见名知义这是一个FactoryBean有一个getObject()方法。在IoC容器进行注入的时候会创建TransactionInterceptor对象而这个对象会创建一个TransactionAttributePointcut为读取TransactionAttribute做准备。在容器初始化的过程中由于实现了InitializingBean接口因此AbstractSingletonProxyFactoryBean会实现afterPropertiesSet()方法正是在这个方法实例化了一个ProxyFactory建立起Spring AOP的应用在这里会为这个ProxyFactory设置通知、目标对象并最终返回Proxy代理对象。在Proxy代理对象建立起来以后在调用其代理方法的时候会调用相应的TransactionInterceptor拦截器在这个调用中会根据TransactionAttribute配置的事务属性进行配置从而为事务处理做好准备。
和前面的思路一样,从声明式事务处理的基本用法入手,来了解它的基本实现原理。在使用声明式事务处理的时候,需要在 IoC容器 中配置 TransactionProxyFactoryBean见名知义这是一个 FactoryBean有一个 getObject()方法。在 IoC容器 进行注入的时候,会创建 TransactionInterceptor对象而这个对象会创建一个 TransactionAttributePointcut为读取 TransactionAttribute 做准备。在容器初始化的过程中,由于实现了 InitializingBean接口因此 AbstractSingletonProxyFactoryBean 会实现 afterPropertiesSet()方法,正是在这个方法实例化了一个 ProxyFactory建立起 Spring AOP 的应用,在这里,会为这个 ProxyFactory 设置通知、目标对象,并最终返回 Proxy代理对象。在 Proxy代理对象 建立起来以后,在调用其代理方法的时候,会调用相应的 TransactionInterceptor拦截器在这个调用中会根据 TransactionAttribute 配置的事务属性进行配置,从而为事务处理做好准备。
从TransactionProxyFactoryBean入手通过代码实现来了解Spring是如何通过AOP功能来完成事务管理配置的Spring为声明式事务处理的实现所做的一些准备工作包括为AOP配置基础设施这些基础设施包括设置拦截器TransactionInterceptor、通知器DefaultPointcutAdvisor或TransactionAttributeSourceAdvisor。同时在TransactionProxyFactoryBean的实现中 还可以看到注人进来的PlatformTransactionManager和事务处理属性TransactionAttribute等。
TransactionProxyFactoryBean 入手,通过代码实现来了解 Spring 是如何通过 AOP功能 来完成事务管理配置的Spring 为声明式事务处理的实现所做的一些准备工作:包括为 AOP 配置基础设施,这些基础设施包括设置 拦截器TransactionInterceptor、通知器DefaultPointcutAdvisor TransactionAttributeSourceAdvisor。同时 TransactionProxyFactoryBean 的实现中, 还可以看到注人进来的 PlatformTransactionManager 事务处理属性TransactionAttribute 等。
```java
/**
* 代理工厂bean用于简化声明式事务处理,这是标准AOP的一个方便的替代方案
* 代理工厂bean 用于简化声明式事务处理,这是标准 AOP 的一个方便的替代方案
* 使用单独的TransactionInterceptor定义。
*/
@SuppressWarnings("serial")
public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBean
implements BeanFactoryAware {
/** 事务拦截器通过AOP来发挥作用spring在此拦截器中封装了 事务处理实现 */
private final TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
/** 切面 */
private Pointcut pointcut;
/**
* 通过依赖注入的事务属性以properties的形式出现
* 把从beandefinition中读到的事务管理的属性信息注入到transactionInterceptor
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionInterceptor.setTransactionManager(transactionManager);
}
/**
* 创建AOP对事务处理的advisor
* 本方法在IoC容器完成Bean的依赖注入时通过initializeBean方法被调用
*/
@Override
protected Object createMainInterceptor() {
this.transactionInterceptor.afterPropertiesSet();
if (this.pointcut != null) {
// 如果自己定义了切面,就使用默认的通知器,并为其配置事务处理拦截器
return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor);
}
else {
// 如果没定义则使用spring默认的切面使用TransactionAttributeSourceAdvisor
// 作为通知器,并配置拦截器
return new TransactionAttributeSourceAdvisor(this.transactionInterceptor);
}
}
public void setTransactionAttributes(Properties transactionAttributes) {
this.transactionInterceptor.setTransactionAttributes(transactionAttributes);
}
public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
this.transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource);
}
public void setPointcut(Pointcut pointcut) {
this.pointcut = pointcut;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.transactionInterceptor.setBeanFactory(beanFactory);
}
/** 事务拦截器,通过 AOP 来发挥作用Spring 在此拦截器中封装了事务处理实现 */
private final TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
/** 切面 */
private Pointcut pointcut;
/**
* 通过依赖注入的事务属性以 properties的形式 出现
* 把从 beandefinition 中读到的事务管理的属性信息注入到 transactionInterceptor
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionInterceptor.setTransactionManager(transactionManager);
}
/**
* 创建 AOP 对事务处理的 advisor
* 本方法在 IoC容器 完成 Bean的依赖注入时通过 initializeBean()方法 被调用
*/
@Override
protected Object createMainInterceptor() {
this.transactionInterceptor.afterPropertiesSet();
if (this.pointcut != null) {
// 如果自己定义了切面,就使用默认的通知器,并为其配置事务处理拦截器
return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor);
}
else {
// 如果没定义,则使用 Spring默认的切面使用 TransactionAttributeSourceAdvisor
// 作为通知器,并配置拦截器
return new TransactionAttributeSourceAdvisor(this.transactionInterceptor);
}
}
public void setTransactionAttributes(Properties transactionAttributes) {
this.transactionInterceptor.setTransactionAttributes(transactionAttributes);
}
public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
this.transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource);
}
public void setPointcut(Pointcut pointcut) {
this.pointcut = pointcut;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.transactionInterceptor.setBeanFactory(beanFactory);
}
}
```
以上代码完成了AOP配置对于用户来说一个值得关心的问题是Spring的TransactionInterceptor配置是在什么时候被启动并成为Advisor通知器的一部分的呢从对createMainInterceptor()方法的调用分析中可以看到这个createMainInterceptor()方法在IoC容器完成Bean的依赖注入时通过initializeBean()方法被调用,具体的调用过程如下图所示。
以上代码完成了 AOP配置对于用户来说一个值得关心的问题是Spring TransactionInterceptor配置 是在什么时候被启动并成为 Advisor通知器 的一部分的呢?从对 createMainInterceptor()方法 的调用分析中可以看到,这个 createMainInterceptor()方法 IoC容器 完成 Bean的依赖注入时通过 initializeBean()方法 被调用,具体的调用过程如下图所示。
![avatar](/images/springTransaction/createMainInterceptor()方法的调用链.png)
在TransactionProxyFactoryBean的父类AbstractSingletonProxyFactoryBean中的afterPropertiesSet()方法是Spring事务处理完成AOP配置的地方在建立TransactionProxyFactoryBean的事务处理拦截器的时候首先需要对ProxyFactoryBean的目标Bean设置进行检查如果这个目标Bean的设置是正确的就会创建一个ProxyFactory对象从而实现AOP的使用。在afterPropertiesSet()的方法实现中可以看到为ProxyFactory生成代理对象、配置通知器、设置代理接口方法等。
TransactionProxyFactoryBean 的父类 AbstractSingletonProxyFactoryBean 中的 afterPropertiesSet()方法,是 Spring事务处理 完成 AOP配置 的地方,在建立 TransactionProxyFactoryBean 的事务处理拦截器的时候,首先需要对 ProxyFactoryBean 目标Bean 设置进行检查,如果这个 目标Bean 的设置是正确的,就会创建一个 ProxyFactory对象从而实现 AOP 的使用。在 afterPropertiesSet() 的方法实现中,可以看到为 ProxyFactory 生成代理对象、配置通知器、设置代理接口方法等。
```java
public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
implements FactoryBean<Object>, BeanClassLoaderAware, InitializingBean {
private Object target;
private Class<?>[] proxyInterfaces;
private Object[] preInterceptors;
private Object[] postInterceptors;
/** Default is global AdvisorAdapterRegistry */
private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
private transient ClassLoader proxyClassLoader;
private Object proxy;
/**
* 处理完成AOP配置创建ProxyFactory对象为其生成代理对象
* 配置通知器、设置代理接口方法
*/
public void afterPropertiesSet() {
// 校验target目标对象
if (this.target == null) {
throw new IllegalArgumentException("Property 'target' is required");
}
if (this.target instanceof String) {
throw new IllegalArgumentException("'target' needs to be a bean reference, not a bean name as value");
}
if (this.proxyClassLoader == null) {
this.proxyClassLoader = ClassUtils.getDefaultClassLoader();
}
// 使用ProxyFactory完成AOP的基本功能ProxyFactory提供proxy对象
// 并将TransactionIntercepter设为target方法调用的拦截器
ProxyFactory proxyFactory = new ProxyFactory();
if (this.preInterceptors != null) {
for (Object interceptor : this.preInterceptors) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor));
}
}
// 加入Advisor通知器可以加入两种通知器分别是
// DefaultPointcutAdvisor、TransactionAttributeSourceAdvisor
// 这里通过调用TransactionProxyFactoryBean实例的createMainInterceptor()方法
// 来生成需要的Advisors。在ProxyFactory的基类AdvisedSupport中维护了一个持有Advisor
// 的链表LinkedList<Advisor>,通过对这个链表中的元素执行增、删、改等操作,用来管理
// 配置给ProxyFactory的通知器
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor()));
if (this.postInterceptors != null) {
for (Object interceptor : this.postInterceptors) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor));
}
}
proxyFactory.copyFrom(this);
// 这里创建AOP的目标源与在其它地方使用ProxyFactory没什么差别
TargetSource targetSource = createTargetSource(this.target);
proxyFactory.setTargetSource(targetSource);
if (this.proxyInterfaces != null) {
proxyFactory.setInterfaces(this.proxyInterfaces);
}
else if (!isProxyTargetClass()) {
// 需要根据AOP基础设施来确定使用哪个接口作为代理
proxyFactory.setInterfaces(
ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader));
}
// 设置代理对象
this.proxy = proxyFactory.getProxy(this.proxyClassLoader);
}
private Object target;
private Class<?>[] proxyInterfaces;
private Object[] preInterceptors;
private Object[] postInterceptors;
/** Default is global AdvisorAdapterRegistry */
private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
private transient ClassLoader proxyClassLoader;
private Object proxy;
/**
* 处理完成 AOP配置创建 ProxyFactory对象为其生成代理对象
* 配置通知器、设置代理接口方法
*/
public void afterPropertiesSet() {
// 校验target目标对象
if (this.target == null) {
throw new IllegalArgumentException("Property 'target' is required");
}
if (this.target instanceof String) {
throw new IllegalArgumentException("'target' needs to be a bean reference, not a bean name as value");
}
if (this.proxyClassLoader == null) {
this.proxyClassLoader = ClassUtils.getDefaultClassLoader();
}
// 使用 ProxyFactory 完成 AOP的基本功能ProxyFactory 提供 proxy对象
// 并将 TransactionIntercepter 设为 target方法调用的拦截器
ProxyFactory proxyFactory = new ProxyFactory();
if (this.preInterceptors != null) {
for (Object interceptor : this.preInterceptors) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor));
}
}
// 加入 Advisor通知器可以加入两种通知器分别是
// DefaultPointcutAdvisor、TransactionAttributeSourceAdvisor
// 这里通过调用 TransactionProxyFactoryBean实例 的 createMainInterceptor()方法
// 来生成需要的 Advisors。在 ProxyFactory 的基类 AdvisedSupport 中维护了一个持有 Advisor
// 的链表LinkedList<Advisor>,通过对这个链表中的元素执行增、删、改等操作,用来管理
// 配置给 ProxyFactory 的通知器
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor()));
if (this.postInterceptors != null) {
for (Object interceptor : this.postInterceptors) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor));
}
}
proxyFactory.copyFrom(this);
// 这里创建 AOP 的目标源,与在其它地方使用 ProxyFactory 没什么差别
TargetSource targetSource = createTargetSource(this.target);
proxyFactory.setTargetSource(targetSource);
if (this.proxyInterfaces != null) {
proxyFactory.setInterfaces(this.proxyInterfaces);
}
else if (!isProxyTargetClass()) {
// 需要根据 AOP 基础设施来确定使用哪个接口作为代理
proxyFactory.setInterfaces(
ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader));
}
// 设置代理对象
this.proxy = proxyFactory.getProxy(this.proxyClassLoader);
}
}
```
DefaultAopProxyFactory创建AOP Proxy的过程在前面分析AOP的实现原理时已分析过这里就不再重复了。可以看到通过以上的一系列步骤Spring为实现事务处理而设计的拦截器TransctionInterceptor已经设置到ProxyFactory生成的AOP代理对象中去了这里的TransactionInterceptor是作为AOP Advice的拦截器来实现它的功能的。在IoC容器中配置其他与事务处理有关的属性比如比较熟悉的transactionManager和事务处理的属性也同样会被设置到已经定义好的TransactionInterceptor中去。这些属性配置在TransactionInterceptor对事务方法进行拦截时会起作用。在AOP配置完成以后可以看到在Spring声明式事务处理实现中的一些重要的类已经悄然登场比如TransactionAttributeSourceAdvisor和TransactionInterceptor正是这些类通过AOP封装了Spring对事务处理的基本实现。
DefaultAopProxyFactory 创建 AOP Proxy 的过程在前面分析 AOP的实现原理 时已分析过这里就不再重复了。可以看到通过以上的一系列步骤Spring 为实现事务处理而设计的 拦截器TransctionInterceptor 已经设置到 ProxyFactory 生成的 AOP代理对象 中去了,这里的 TransactionInterceptor 是作为 AOP Advice 的拦截器来实现它的功能的。在 IoC容器 中,配置其他与事务处理有关的属性,比如,比较熟悉的 transactionManager 和事务处理的属性,也同样会被设置到已经定义好的 TransactionInterceptor 中去。这些属性配置在 TransactionInterceptor对事务方法进行拦截时会起作用。在 AOP配置 完成以后,可以看到,在 Spring声明式事务处理实现 中的一些重要的类已经悄然登场,比如 TransactionAttributeSourceAdvisor TransactionInterceptor正是这些类通过 AOP 封装了 Spring 对事务处理的基本实现。
### 2.2 事务处理配置的读入
在AOP配置完成的基础上以TransactionAttributeSourceAdvisor的实现为入 口,了解具体的事务属性配置是如何读入的。
AOP配置 完成的基础上,以 TransactionAttributeSourceAdvisor的实现 为入口,了解具体的事务属性配置是如何读入的。
```java
public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor {
/**
* 与其它Advisor一样同样需要定义AOP中用到的Interceptor和Pointcut
*/
private TransactionInterceptor transactionInterceptor;
/**
* 对于切面pointcut这里使用了一个匿名内部类
*/
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
/**
* 通过transactionInterceptor来得到事务的配置属性在对Proxy的方法进行匹配调用时,
* 会使用到这些配置属性
*/
@Override
protected TransactionAttributeSource getTransactionAttributeSource() {
return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null);
}
};
/**
* 与其它 Advisor 一样,同样需要定义 AOP 中用到的 Interceptor 和 Pointcut
*/
private TransactionInterceptor transactionInterceptor;
/**
* 对于 切面Pointcut这里使用了一个匿名内部类
*/
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
/**
* 通过 transactionInterceptor 来得到事务的配置属性,在对 Proxy的方法 进行匹配调用时,
* 会使用到这些配置属性
*/
@Override
protected TransactionAttributeSource getTransactionAttributeSource() {
return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null);
}
};
}
```
在声明式事务处理中通过对目标对象的方法调用进行拦截来实现事务处理的织入这个拦截通过AOP发挥作用。在AOP中对于拦截的启动首先需要对方法调用是否需要拦截进行判断而判断的依据是那些在TransactionProxyFactoryBean中为目标对象设置的事务属性。也就是说需要判断当前的目标方法调用是不是一个配置好的并且需要进行事务处理的方法调用。具体来说这个匹配判断在TransactionAttributeSourcePointcut的matches()方法中完成,该方法实现 首先把事务方法的属性配置读取到TransactionAttributeSource对象中有了这些事务处理的配置以后根据当前方法调用的Method对象和目标对象对是否需要启动事务处理拦截器进行判断。
在声明式事务处理中,通过对目标对象的方法调用进行拦截来实现事务处理的织入,这个拦截通过 AOP 发挥作用。在 AOP 中,对于拦截的启动,首先需要对方法调用是否需要拦截进行判断,而判断的依据是那些在 TransactionProxyFactoryBean 中为目标对象设置的事务属性。也就是说,需要判断当前的目标方法调用是不是一个配置好的并且需要进行事务处理的方法调用。具体来说,这个匹配判断在 TransactionAttributeSourcePointcut matches()方法 中完成,该方法实现 首先把事务方法的属性配置读取到 TransactionAttributeSource对象 中,有了这些事务处理的配置以后,根据当前方法调用的 Method对象 目标对象,对是否需要启动事务处理拦截器进行判断。
```java
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
public boolean matches(Method method, Class targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
public boolean matches(Method method, Class targetClass) {
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
}
```
在Pointcut的matches()判断过程中会用到TransactionAttributeSource对象这个TransactionAttributeSource对象是在对TransactionInterceptor进行依赖注入时就配置好的。它的设置是在TransactionInterceptor的基类TransactionAspectSupport中完成的配置的是一个NameMatchTransactionAttributeSource对象。
Pointcut matches()判断过程 中,会用到 TransactionAttributeSource对象这个 TransactionAttributeSource对象 是在对 TransactionInterceptor 进行依赖注入时就配置好的。它的设置是在 TransactionInterceptor 的基类 TransactionAspectSupport 中完成的,配置的是一个 NameMatchTransactionAttributeSource对象。
```java
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
/**
* 设置事务属性以方法名为key事务属性描述符为value
* 例如key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly"
*/
public void setTransactionAttributes(Properties transactionAttributes) {
// 可以看到这是一个NameMatchTransactionAttributeSource的实现
NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
tas.setProperties(transactionAttributes);
this.transactionAttributeSource = tas;
}
/**
* 设置事务属性,以方法名为 key事务属性描述符为 value
* 例如key = "myMethod", value = "PROPAGATION_REQUIRED,readOnly"
*/
public void setTransactionAttributes(Properties transactionAttributes) {
// 可以看到这是一个 NameMatchTransactionAttributeSource 的实现
NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
tas.setProperties(transactionAttributes);
this.transactionAttributeSource = tas;
}
}
```
在以上的代码实现中可以看到NameMatchTransactionAttributeSource作为TransactionAttributeSource的具体实现是实际完成事务处理属性读入和匹配的地方。在对事务属性TransactionAttributes进行设置时会从事务处理属性配置中读取事务方法名和配置属性在得到配置的事务方法名和属性以后会把它们作为键值对加入到一个nameMap中。
在以上的代码实现中可以看到NameMatchTransactionAttributeSource 作为 TransactionAttributeSource 的具体实现,是实际完成事务处理属性读入和匹配的地方。在对 事务属性TransactionAttributes 进行设置时,会从事务处理属性配置中读取事务方法名和配置属性,在得到配置的事务方法名和属性以后,会把它们作为键值对加入到一个 nameMap 中。
在应用调用目标方法的时候因为这个目标方法已经被TransactionProxyFactoryBean代理所以TransactionProxyFactoryBean需要判断这个调用方法是否是事务方法。这个判断的实现是通过在NameMatchTransactionAttributeSource中能否为这个调用方法返回事务属性来完成的。具体的实现过程是这样的首先以调用方法名为索引在nameMap中查找相应的事务处理属性值如果能够找到那么就说明该调用方法和事务方法是直接对应的如果找不到那么就会遍历整个nameMap对保存在nameMap中的每一个方法名使用PatternMatchUtils的simpleMatch()方法进行命名模式上的匹配。这里使用PatternMatchUtils进行匹配的原因是在设置事务方法的时候可以不需要为事务方法设置一个完整的方法名而可以通过设置方法名的命名模式来完成比如可以通过对通配符*的使用等。所以如果直接通过方法名没能够匹配上而通过方法名的命名模式能够匹配上这个方法也是需要进行事务处理的相对应地它所配置的事务处理属性也会从nameMap中取出来从而触发事务处理拦截器的拦截。
在应用调用目标方法的时候,因为这个目标方法已经被 TransactionProxyFactoryBean 代理,所以 TransactionProxyFactoryBean 需要判断这个调用方法是否是事务方法。这个判断的实现,是通过在 NameMatchTransactionAttributeSource 中能否为这个调用方法返回事务属性来完成的。具体的实现过程是这样的:首先,以调用方法名为索引在 nameMap 中查找相应的事务处理属性值,如果能够找到,那么就说明该调用方法和事务方法是直接对应的,如果找不到,那么就会遍历整个 nameMap对保存在 nameMap 中的每一个方法名,使用 PatternMatchUtils的simpleMatch()方法 进行命名模式上的匹配。这里使用 PatternMatchUtils 进行匹配的原因是,在设置事务方法的时候,可以不需要为事务方法设置一个完整的方法名,而可以通过设置方法名的命名模式来完成,比如可以通过对 通配符* 的使用等。所以,如果直接通过方法名没能够匹配上,而通过方法名的命名模式能够匹配上,这个方法也是需要进行事务处理的,相对应地,它所配置的事务处理属性也会从 nameMap 中取出来,从而触发事务处理拦截器的拦截。
```java
public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable {
/** key是方法名value是事务属性 */
private Map<String, TransactionAttribute> nameMap = new HashMap<String, TransactionAttribute>();
/**
* 将给定属性transactionAttributes解析为名称/属性的map对象。以 方法名称 为键,
* 字符串属性定义 为值可通过TransactionAttributeEditor解析为TransactionAttribute实例。
*/
public void setProperties(Properties transactionAttributes) {
TransactionAttributeEditor tae = new TransactionAttributeEditor();
Enumeration propNames = transactionAttributes.propertyNames();
while (propNames.hasMoreElements()) {
String methodName = (String) propNames.nextElement();
String value = transactionAttributes.getProperty(methodName);
tae.setAsText(value);
TransactionAttribute attr = (TransactionAttribute) tae.getValue();
addTransactionalMethod(methodName, attr);
}
}
/**
* 为事务方法添加属性
*/
public void addTransactionalMethod(String methodName, TransactionAttribute attr) {
if (logger.isDebugEnabled()) {
logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]");
}
this.nameMap.put(methodName, attr);
}
/**
* 对调用的方法进行判断,判断它是否是事务方法,如果是事务方法,则取出相应的事务配置属性
*/
public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
// 直接通过 方法名 匹配
String methodName = method.getName();
TransactionAttribute attr = this.nameMap.get(methodName);
if (attr == null) {
// 查找最具体的名称匹配
String bestNameMatch = null;
for (String mappedName : this.nameMap.keySet()) {
if (isMatch(methodName, mappedName) &&
(bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
attr = this.nameMap.get(mappedName);
bestNameMatch = mappedName;
}
}
}
return attr;
}
/**
* 如果给定的方法名与映射的名称匹配,则返回
*/
protected boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
/** key 是方法名value 是事务属性 */
private Map<String, TransactionAttribute> nameMap = new HashMap<String, TransactionAttribute>();
/**
* 将给定 属性transactionAttributes 解析为 <名称, 属性> 的Map对象。以 方法名称 为 key
* 字符串属性定义 为 value可通过 TransactionAttributeEditor 解析为 TransactionAttribute实例。
*/
public void setProperties(Properties transactionAttributes) {
TransactionAttributeEditor tae = new TransactionAttributeEditor();
Enumeration propNames = transactionAttributes.propertyNames();
while (propNames.hasMoreElements()) {
String methodName = (String) propNames.nextElement();
String value = transactionAttributes.getProperty(methodName);
tae.setAsText(value);
TransactionAttribute attr = (TransactionAttribute) tae.getValue();
addTransactionalMethod(methodName, attr);
}
}
/**
* 为事务方法添加属性
*/
public void addTransactionalMethod(String methodName, TransactionAttribute attr) {
if (logger.isDebugEnabled()) {
logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]");
}
this.nameMap.put(methodName, attr);
}
/**
* 对调用的方法进行判断,判断它是否是事务方法,如果是事务方法,则取出相应的事务配置属性
*/
public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
// 直接通过 方法名 匹配
String methodName = method.getName();
TransactionAttribute attr = this.nameMap.get(methodName);
if (attr == null) {
// 查找最具体的名称匹配
String bestNameMatch = null;
for (String mappedName : this.nameMap.keySet()) {
if (isMatch(methodName, mappedName) &&
(bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
attr = this.nameMap.get(mappedName);
bestNameMatch = mappedName;
}
}
}
return attr;
}
/**
* 如果给定的方法名与映射的名称匹配,则返回
*/
protected boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
}
```
通过以上过程可以得到与目标对象调用方法相关的TransactionAttribute对象在这个对象中封装了事务处理的配置。具体来说在前面的匹配过程中如果匹配返回的结果是null那么说明当前的调用方法不是一个事务方法不需要纳入Spring统一的事务管理中因为它并没有配置在TransactionProxyFactoryBean的事务处理设置中。如果返回的TransactionAttribute对象不是null,那么这个返回的TransactionAttribute对象就已经包含了对事务方法的配置信息对应这个事务方法的具体事务配置也已经读入到TransactionAttribute对象中了为TransactionInterceptor做好了对调用的目标方法添加事务处理的准备。
通过以上过程可以得到与目标对象调用方法相关的 TransactionAttribute对象在这个对象中封装了事务处理的配置。具体来说在前面的匹配过程中如果匹配返回的结果是 null那么说明当前的调用方法不是一个事务方法不需要纳入 Spring 统一的事务管理中,因为它并没有配置在 TransactionProxyFactoryBean 的事务处理设置中。如果返回的 TransactionAttribute对象 不是 null,那么这个返回的 TransactionAttribute对象 就已经包含了对事务方法的配置信息,对应这个事务方法的具体事务配置也已经读入到 TransactionAttribute对象 中了,为 TransactionInterceptor 做好了对调用的目标方法添加事务处理的准备。
### 2.3 事务处理拦截器的设计与实现
在完成以上的准备工作后,经过 TransactionProxyFactoryBean 的 AOP包装 此时如果对目标对象进行方法调用,起作用的对象实际上是一个 Proxy代理对象对目标对象方法的调用不会直接作用在 TransactionProxyFactoryBean 设置的目标对象上,而会被设置的事务处理拦截器拦截。而在 TransactionProxyFactoryBean 的 AOP实现 中,获取 Proxy对象 的过程并不复杂TransactionProxyFactoryBean 作为一个 FactoryBean对这个 Bean 的对象的引用是通过调用其父类 AbstractSingletonProxyFactoryBean 的 getObject()方法 来得到的。
```java
public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
implements FactoryBean<Object>, BeanClassLoaderAware, InitializingBean {
private Object proxy;
// 返回的是一个 proxy代理对象这个 proxy 是 ProxyFactory 生成的 AOP代理
// 已经封装了对事务处理的拦截器配置
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
}
```
InvocationHandler 的实现类中有一个非常重要的方法 invoke(),该方法是 proxy代理对象 的回调方法,在调用 proxy对象 的代理方法时触发这个回调。事务处理拦截器TransactionInterceptor 中实现了 InvocationHandler 的 invoke()方法,其过程是,首先获得调用方法的事务处理配置;在得到事务处理配置以后,会取得配置的 PlatformTransactionManager由这个事务处理器来实现事务的创建、提交、回滚操作。
```java
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 得到代理的目标对象,并将事务属性传递给目标对象
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 在其父类 TransactionAspectSupport 中进行后续的事务处理
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
}
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
new NamedThreadLocal<TransactionInfo>("Current aspect-driven transaction");
protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation)
throws Throwable {
// 获取事务属性,如果属性为空,则该方法是非事务性的
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
// 这里区分不同类型的 PlatformTransactionManager因为他们的调用方式不同
// 对 CallbackPreferringPlatformTransactionManager 来说,需要回调函数
// 来实现事务的创建和提交,而非 CallbackPreferringPlatformTransactionManager
// 则不需要
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 这里创建事务,同时把创建事务过程中得到的信息放到 TransactionInfo 中,
// TransactionInfo 是保存当前事务状态的对象
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 这里的调用使处理沿着拦截器链进行,使最后目标对象的方法得以调用
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 如果在事务处理方法调用中出现了异常,事务如何进行处理需要
// 根据具体情况考虑回滚或提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 这里把 与线程绑定的 TransactionInfo 设置为 oldTransactionInfo
cleanupTransactionInfo(txInfo);
}
// 这里通过事务处理器来对事务进行提交
commitTransactionAfterReturning(txInfo);
return retVal;
} else {
// 使用回调的方式来使用事务处理器
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// RuntimeException 会导致事务回滚
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// 如果正常返回,则提交该事务
return new ThrowableHolder(ex);
}
}
finally {
cleanupTransactionInfo(txInfo);
}
}
});
// Check result: It might indicate a Throwable to rethrow.
if (result instanceof ThrowableHolder) {
throw ((ThrowableHolder) result).getThrowable();
}
else {
return result;
}
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}
/**
* 用于保存事务信息的不透明对象。子类必须将其传递回该类上的方法,但看不到其内部。
*/
protected final class TransactionInfo {
private final PlatformTransactionManager transactionManager;
private final TransactionAttribute transactionAttribute;
private final String joinpointIdentification;
private TransactionStatus transactionStatus;
private TransactionInfo oldTransactionInfo;
public TransactionInfo(PlatformTransactionManager transactionManager,
TransactionAttribute transactionAttribute, String joinpointIdentification) {
this.transactionManager = transactionManager;
this.transactionAttribute = transactionAttribute;
this.joinpointIdentification = joinpointIdentification;
}
}
}
```
以事务提交为例,简要的说明下该过程。在调用代理的事务方法时,因为前面已经完成了一系列 AOP配置对事务方法的调用最终启动
TransactionInterceptor拦截器 的 invoke()方法。在这个方法中,首先会读取该事务方法的事务属性配置,然后根据事务属性配置以及具体事务处理器的配置来决定采用哪一个事务处理器,这个事务处理器实际上是一个 PlatformTransactionManager。在确定好具体的事务处理器之后会根据事务的运行情况和事务配置来决定是不是需要创建新的事务。
对于 Spring 而言,事务的管理实际上是通过一个 TransactionInfo对象 来完成的,在该对象中,封装了事务对象和事务处理的状态信息,这是事务处理的抽象。在这一步完成以后,会对拦截器链进行处理,因为有可能在该事务对象中还配置了除事务处理 AOP 之外的其他拦截器。在结束对拦截器链处理之后,会对 TransactionInfo 中的信息进行更新,以反映最近的事务处理情况,在这个时候,也就完成了事务提交的准备,通过调用 事务处理器PlatformTransactionManager 的 commitTransactionAfterReturning()方法 来完成事务的提交。这个提交的处理过程已经封装在 PlatformTransactionManager 的事务处理器中了,而与具体数据源相关的处理过程,最终委托给相关的具体事务处理器来完成,比如 DataSourceTransactionManager、Hibermate'TransactionManager 等。
在这个 invoke()方法 的实现中,可以看到整个事务处理在 AOP拦截器 中实现的全过程。同时,它也是 Spring 采用 AOP 封装事务处理和实现声明式事务处理的核心部分。这部分实现是一个桥梁,它胶合了具体的事务处理和 Spring AOP框架可以看成是一个 Spring AOP应用在这个桥梁搭建完成以后Spring事务处理 的实现就开始了。

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save