本文档分为如下板块:
- 源码之旅1-IOC源码角度
- 源码之旅2-IOC线程方法调用链角度
- 源码之旅3-mybatis源码
- 研究一下一级缓存失效原因
- 源码之旅4-springMVC
源码之旅1-IOC源码角度
springIOC核心类AbstractApplicationContext继承关系
1、以如下main启动dubbo服务
public class SearchDubboServerStart {
public static ClassPathXmlApplicationContext context;
public static void main(String args[]) {
context = new ClassPathXmlApplicationContext("classpath:db-config/db-core.xml","classpath*:spring-config/**/applicationContext-*.xml");
context.start();
System.out.println(" .::::.");
System.out.println(" .::::::::.");
System.out.println(" :::::::::::");
System.out.println(" ..:::::::::::'");
System.out.println(" '::::::::::::'");
System.out.println(" .::::::::::");
System.out.println(" '::::::::::::::..");
System.out.println(" ..::::::::::::.");
System.out.println(" ``::::::::::::::::");
System.out.println(" ::::``:::::::::' .:::.");
System.out.println(" ::::' ':::::' .::::::::.");
System.out.println(" .::::' :::: .:::::::'::::.");
System.out.println(" .::' :::::.:::::::::' ':::::.");
System.out.println(" .::' ::::::::::::::' ``::::.");
System.out.println(" ...::: ::::::::::::' ``::.");
System.out.println(" ```` ':. ':::::::::' ::::..");
System.out.println(" '.:::::' ':'````..");
System.out.println("<<<<<启><动><成><功>>>>>>");
synchronized (SearchDubboServerStart.class) {
while (true) {
try {
SearchDubboServerStart.class.wait();
} catch (Throwable e) {
}
}
}
}
}
2.从这里进入new ClassPathXmlApplicationContext("classpath:db-config/db-core.xml","classpath*:spring-config/**/applicationContext-*.xml")进去
进入另一个构造
读取到配置文件了,并将其赋值给成员变量给存起来,然后进入进入核心方法了 refresh()
3.这个refresh()是ClasspathXmpApplicationContext的上级抽象类AbstractApplicationContext的IOC核心方法,在方法的以下关键行来几个断点一个一个玩。
3.1 断点1:非核心、略过
3.2 断点2:获取beanFactory.进入->
这里定制一个的beanfactory,然后进入核心loadBeanDefiniton(beanFactory).
上面的XmlBeanDefinitonReader构造传入了空的beanFactory
看到配置文件了,这里再进入就是DOM4J 解析xml配置并根据读取到的每个bean 的class名反射创建bean 然后将这些bean装入beanFacatory。再回到obtaiFreshBeanFactory这个方法可以看到beanfactory已经装满一个大map,key为全路径类名,value为改类实例化时需要的一些属性。同时将这些bean的name装入一个list集合。这时切换到debug控制台看到的日志就是读取各种XML配置文件。
3.3断点3:对beanfactor的一些属性进行设置,非核心。
3.4断点4:beanfactory后置处理器,会继续扩充一些beandefinition如扫描到的mybatis接口searchMapper等到大map中,仍然没有实例化。
3.5断点5.6.7.8:非核心,debug控制台也不会有日志输出
3.6断点8:finishBeanFactoryInitialization(beanFactory),传入beanfactory,根据里面的beandefinition大map先实例化单例bean。beanfactory会有一个属性来记录这些创建好了的单例bean。mybatis接口哪些代理对象还没有生成?
3.7:断点9:finishRefresh()。发布事件。查看日志,会将dubbo服务暴露发布到注册中心。
源码之旅2-IOC线程方法调用链角度
用spring test来启动。原理一样,都是读取配置文件实例化bean,只是不是ClasspathXmlApplicationContext来读取,而是DefualtTestContext来加载配置文件的,但是它同前者一样都会进入AbstractApplicationContext这个抽象类的refresh()方法。这个方法是IOC的核心方法。
由下往上看,可以看到
SpringJUnit4ClassRunner->调用了getApplicationContext->loadContext->refresh核心方法->finshBeanFactoryInitialization完成beanfactory的初始化->。。读取XML配置到beanfactory的beandefinitionMap属性->preInstantiateSinglethons首先进入实例化单例bean方法->createBean->然后populatebean进行属性注入解决bean之间的依赖
在解决依赖时会去扫描类上是否有@Autowire等注解与否,如果有则要实例化该autowire的对象,这时如果没有这个对象则要createBean,如果autowire的对象就是Dao层的接口,这时就要checkDaoConfig调用DaoSupport.initDao()来创建xxMapper接口的代理对象并注入。
checkDaoConfig调用DaoSupport.initDao()
源码之旅3-mybatis源码
接着IOC来-》》》》》》》》》》ICO完毕后会有AOP相关操作,这里没有就AOP就开始直接调用业务接口在调用DAO层searchMapper.selectFavAndFootCommodityId(map)
dao层为JDK动态代理,invoke调用目标对象方法
进入mapperMethod.excute(this.sqlSession,agrs)。因为mapper接口返回为一个列表,故路由到excuseForMany(sqlSession,args)
mapper接口方法是否有RowBounds逻辑分页参数(SQL并不会拼接offset与limit,而是查出所有到应用中在截取,较多数据传输,实际应用应该很少用这种方式)
进去DefualtSqlSession.selectList(statement,parameter,Rowbound.DEFUALT) ,statement就是XML的namespace+sqlid
继续进入,这里的入参换成了根据namespace+sqlid查出的MappedStatement
这里的boundsql封装了XML的SQL语句
这里的cache对应XML的二级缓存开启注解<cache/>,如果二级缓存为null及不开启或开启但获取到的数据列表是null都需要查数据库,该类持有BaseExecutor的引用。
从本地缓存读取,如果未读到会queryFromDatabase查询数据库
真正的数据库查询,并将结果放入一级缓存。创建statementHandler同事会创建parameterHandler(设置prepareStatment参数)resultSetHandler(处理prepareStatment执行后的结果),都可以在创建时注册拦截器。全局setting配置插件(实现mybatis拦截器接口)。 typeHandler(设置参数与结果集处理时需要的类型处理器)
进入可看到原生JDBC prepareStatement执行查询操作。然后处理结果集。
线程方法调用链如下:
研究一下一级缓存失效原因:
如上连续查询两次,结果发现打印了两次SQL语句?mybatis一级缓存失效了?一级缓存是session级别的缓存即同一个sqlSession再次相同查询只要期间未增删改就能利用上缓存。观擦日志:查询完就立即关闭了无事务的sqlSession,再次获取的sqlSession不是之前的那个。如下图,两次查询的JDBC连接都是没被spring 管理,查完后return到连接池了。两次sqlSession不是同一个。
我们在serverImpl的方法上加个只读事务试试
首次查询看到JDBC连接被spring管理,执行完后释放sqlSession但连接不return到连接池。再次查询看不到看不SQL语句,使用的是同一个sqlSession.整个事务方法结束我们才能看到事务提交 及return JDBC连接到连接池。
插曲-深入事务:
事务是AOP的一种典型应用,是对XXserviceImpl方法的增强,前增加了开启事务,后增加提交事务逻辑。而事务的开启关闭需要数据库连接对象,故有事务的方法会一开始占用数据库连接直到方法结束才提交事务,归还连接。故应将非数据库操作移出到事务方法外避免占着数据库连接坐一些非数据库操作。如果是有增删改会对响应影响行加行锁。
源码之旅4-springMVC
依赖从断点走一波,先到前端控制器打几个断点,仅首次访问时初始化DispatherServlet,会进入onfresh然后进入initStategies方法里初始化前端控制器的以下几个属性如handlerMapping,handlerAdapter,mutipartResolver,viewResolver等,再次请求不在进行初始化,直接进入请求分发方法doDispatch(HttpServletRequest request, HttpServletResponse response)。
请求分发核心方法,重点的ha.handle(req,res,handler)
以下是我收集的两张原理图,handler就是我们的controller
工作流程:
1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
7. ViewResolver 结合Model和View,来渲染视图
8. 将渲染结果返回给客户端