Spring MVC源码剖析系列(1) - DispatcherServlet工作原理之初始化

最近接手一个项目,用的是SpringMVC框架。循例想剖析一下它的源码,以求更深刻地理解MVC的实现。 DispatcherServlet是SpringMVC最核心的类,就拿它开刀:) 在web.xml中定义DispatcherServlet和它的Mapping: DispatcherServlet继承于FrameworkServlet,而FrameworkServlet又继承于HttpServletBean, HttpServletBean再继承于HttpServlet: DispatcherServlet有一段静态代码块值得关注: 它会到ClassPath下加载一个叫DispatcherServlet.properties的文件的内容,在SpringMVC的jar包里面有这个文件: 你可以理解为SpringMVC为你预先定义好一些后面有可能会用到的类。 Servlet容器(如Jetty)会调用Servlet的init方法,在这里,HttpServletBean重写了init()方法如下: 上面方法中值得注意的是调用了initServletBean方法,这个方法在FrameworkServlet中被重写了: 我们看到这里将会通过调用initWebApplicationContext方法来初始化一个webApplicationContext,这个非常重要,MVC的所有类都是在这个Context里面被加载进来的,我们继续往下看是如何初始化这个Context的: 我们看到它先尝试去找一个rootContext,这个rootContext有点tricky,如果你在web.xml里面指定以下listener: 那它ContextLoaderListener就会先去初始化一个Context(通过它的contextInitialized方法),并把这个context存储在ServletContext的attribute里面,名字为: 轮到FrameworkServlet初始化Context的时候,就会把它从ServletContext中取出来,作为parent context传给FrameworkServlet要初始化的Context,让它能够拿到之前已经加载的Spring Bean: 在创建Context的时候,先去拿到Context的class对象, 因为我们并没有在web.xml里面给DispatcherServlet配置任何参数,所以就会用默认的Context Class: XmlWebApplicationContext, 是从xml文件来构建这个context。…

Jetty源码剖析系列(6) - Connector与ServerSocket

本文主要从源代码的角度来分析Jetty的Connector如何通过ServerSocket来绑定和监听网络地址和端口的过程。 Jetty的Connector实现类是ServerConnector,循例从它的doStart()方法开始(至于是如何到达这个方法,请移步本系列的前几篇): 它的doStart方法先调用父类AbstractNetworkConnector的doStart方法: 在父类的doStart方法里面会调用子类实现的open方法,此处是ServerConnector的open方法: 到这里已经可以看到Jetty的Connector与Java NIO的关系了,将会调用openAcceptChannel方法来构建一个NIO的ServerSocketChannel: 从上图的代码我们可以看到,Jetty默认用Java NIO来实现了它的网络框架,另外一点就是,如果你用java来实现一套网络框架,无非就是BIO和NIO两种,返朴归真。 先常规调用ServerSocketChannel的静态方法open方法来打开一个ServerSocketChannel: 因为Java的NIO是基于Selector来实现的,所以在这里会通过SelectorProvider类的静态方法provider来加载Selector的实现类: 它先尝试从System Property里加载Selector的实现类并实例化: 如果没有的话,就尝试通过SPI的方式来加载Selector的实现类并实例化: 如果还是没有的话,就用Default的Selector,这个是根据底层操作系统来决定,比如,在Windows系统里将会是WindowsSelectorProvider这个类: WindowsSelectorProvider继承于SelectorProviderImpl这个抽象类, 它的openServerSocketChannel方法将会返回一个新建出来的ServerSocketChannelImpl对象: 到此我们再回过头看ServerConnector的openAcceptChannel方法, 上图通过ServerSocketChannel.open()方法拿到的就是一个ServerSocketChannelImpl实例对象, 我们接着往下看, 如果你没在启动参数里加--host参数,那它就会去指定所谓的全零地址: 那这个所谓的anyLocalAddress又是怎么来的呢? 这里impl是InetAddressImpl,它是在加载InetAddress类的时候,通过它的类静态块初始化的: 而InetAddressImplFactory会通过一个本地方法isIPv6Supported来判断底层操作系统是否支持IPv6,来加载InetAddressImpl的实现类: 在我的windows系统里,是支持IPv6的,所以返回的InetAddressImpl应该是Inet6AddressImpl对象,再来看它的anyLocalAddress方法:…

Jetty源码剖析系列(5) - Server与Handler

先上一张我读Jetty代码总结的一张图: 具体分析TO BE CONTINUED:)…

Jetty源码剖析系列(4)-Connector如何接收处理网络请求

书接上文,上文分析到ServerConnector的doStart方法会根据CPU的数量协调出一定数量的Acceptor,然后再把Acceptor交给线程池去执行: AbstractConnector.doStart(): Acceptor实现了Runnable接口,进去看它的run方法: 我们注意到它在while循环里面调用了accept方法,这个方法就是接收网络请求的入口了: 从上图的accept方法我们可以看到,它实际上就是调用了ServerSocketChannel的accept方法,我们终于回归到了NIO的基本,注意ServerSocketChannel的accept方法是阻塞的,直到接收到一个建立连接的请求,它就会返回一个SocketChannel,然后调用accepted方法,继续进入accepted方法看看: 先把SocketChannel设置为nonBlocking的,然后从SocketChannel里拿出Socket设置一些TCP属性: 比如会把该socket设置为TCP_NODELAY,这个是要disable Nagle's algorithm。设置完Socket后,就会调用SelectorManager的accept方法: 到这里,我们看到了ManagedSelector这个类,这个类是干嘛的呢?而SelectManager又是哪来的?我们回过头来看一下ServerConnector的构造方法: 可以看到_manager是在ServerConnector的构造方法里通过调用newSelectorManager方法得到的,而newSelectorManager方法里实际就是直接new出来: 再看回ServerConnector的构造方法里,_manager会被加进ServerConnector的managedBean里面,在调用ServerConnector的doStart方法时,它的managedBean也会被启动(被调用start方法,进而调用它的doStart方法), 那我们就再进去看一下SelectorManager的doStart方法: 它先是通过newSelector方法创建ManagedSelector,ManagedSelector数目是跟CPU数目协调出来的, 然后再把ManagedSelector加进SelectManager的managedBean里面,同样在启动SelectManager的doStart方法时,ManagedSelector的doStart方法也会被启动: ManagedSelector的doStart先通过SelectorManager的newSelector方法创建出一个Selector,这个selector就是Java NIO里面的Selector: 然后SelectorManager会execute_…

Jetty源码剖析系列(3)-Connector如何接收处理网络请求

在本系列的第一篇提到,Jetty由Connector组件负责接收网络请求,如下图: ServerConnector是Jetty Connector的实现类,我们直接看它的doStart方法: 它先是调用了父类AbstractNetworkConnector的doStart方法: 这个父类的doStart方法先是调用了open方法,注意这里实际调用的是ServerConnector实现的open方法: 它先是调用openAcceptChannel方法来创建一个NIO的ServerSocketChannel: 上面的就是一个经典的NIO ServerSocketChannel创建过程:先是ServerSocketChannel.open(),然后再把ServerSocketChannel的ServerSocket绑定到相应的InetSocketAddress,熟悉的配方,熟悉的味道:) 再回到ServerConnector的open方法,它拿到这个ServerSocketChannel后,会调用它的configureBlocking方法,把它设置为阻塞的,这里其实是为了设置它的ServerSocket在调用accept方法的时是阻塞模式(即调用accept方法就会进入线程阻塞直到有网络连接进来)。 我们再回到AbstractNetworkConnector的doStart方法,当它执行完open方法打开ServerSocketChannel后,接着会调用它的父类AbstractConnector的doStart方法: 这个方法很重要,到这里就开始了本文真正要关注的地方:Connector是如何接收处理网络请求的。我们来看其中的这段代码: 我们先看一下_acceptors(AbstractConnector的构造方法里初始化的)是什么: 我们可以看到它其实是个acceptor(我翻译为网络请求接收者)线程数组,这个数组的长度定得有点讲究,如果你没有指定acceptor的数目,acceptors这个值传进来的时候会默认为-1,那么就会将主机的CPU数量除以8取整跟4比较取最小的再跟1比较取最大的,如果acceptors的数目比JVM CPU的数目还多的话,它就会通过打一句log来提醒你Acceptor的数目不应大于JVM CPU的数目。 TO BE CONTINUE.....…

Scalable IO in Java解读

大神Doug Lea的Scalable IO in Java,篇幅虽短,但大家之作,高屋建瓴,有如内功心法。每当我在对NIO的一些东西不甚了解的时候,翻出它来仔细研读,总能找到我想要的答案。 当今基于TCPIP的网络应用服务(分布式?),大多数都有着类似的结构(流程): 从底层IO读取网络字节请求 把读取的网络字节请求进行解码,封装成为业务请求对象 对解码封装后的业务请求对象进行业务处理 将业务逻辑处理完后的响应进行编码为底层IO可传输的字节响应 利用底层IO发送已编码的字节响应 整体流程如上图所示,但具体每一步所用到的技术手段有可能都不一样,比如解码协议是自定义的还是业界流行的?(比如是用XML还是JSON,或者Protobuf?),如果是XML解析,那又是用什么包来进行解析?Web页面如何生成?而处理过程因具体业务而不同。 经典(传统)的网络服务设计如上图所示,对每个请求都会产生一个新的线程来进行处理,这种设计的缺点是,线程的创建本身是系统资源的一个开销,如果并发请求达到一定数量,响应将会变慢,甚至有可能因为系统资源不足而造成系统崩溃。 上图是一个经典的Blocking IO ServerSocket代码示例,它实现的其实就是图3所描述的。 基于传统的网络服务设计(每个请求一个线程处理)的缺点,Doug Lea描述了高扩展性系统的目标: * 在请求激增的负载下,…

旧文一篇-我的2015之跳槽

2015年年底我从HSBC跳槽出来后,在我原来的博客写下了这篇文章,当时被黑客派收录,但后来我的VPS被黑了后,博客也挂了,也没有存档,所以 我也早就忘了我还写过这篇文章。 后来我进爱立信广研的时候,这篇文章不知道被哪位同事在黑客派看到然后发到广研的微信大群里面,结果让我好生尴尬。 时光荏苒,岁月如梭,光阴似箭,白驹过隙,斗转星移,潜龙勿用,亢龙有悔,How time flies..... 又逢岁末,自然不能免俗地想来做个年终总结,今天又恰好是我在爱立信广州研发中心工作了整整一个月,就应景总结一下这次的跳槽历程。 其实我想离开 HSBC 的心思已经酝酿了大半年,终于到了九月份的时候决定踏出这一步,在 51Job 上更新了一下尘封了两年多的简历,不知道是今年广州 IT 环境不错还是怎么的,陆续收到了不少面试的通知,有猎头,也有 HR。以下就按时间回顾一下。 爱立信广州研发中心 这是我这次面试的第一家,也是兜兜转转后最终选择的 offer,从这点上来看,男人还是看重第一次嘛。 其实最初来找到我的是一家外包公司,叫瞬联科技,它在帮爱立信招一个职位,本来我对外包是抗拒的,…

Spring Boot源码剖析系列(1) - 启动

写好一个启动类,Application,然后通过Spring Boot将它启动: 顺流而下, SpringApplication.run(): 从上面的run方法看到,启动类将会作为SpringApplication的构造函数的参数,而main方法的参数将会作为run方法的参数。 我们再来看SpringApplication的构造函数: initialize方法,一看名字就知道不一般,我们来仔细分析一下初始化了么。它首先是把启动类加到sources里, 接着判断是否是为web environment,调用了deduceWebEnvironment方法: 它是到classpath里面是否有javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext这两个类,只有这两个类都存在,才会认为这是web environment。 接着就是去查找initializer和listener,分别是接口ApplicationContextInitializer和ApplicationListener的实现类,都是通过调用getSpringFactoriesInstances方法: 而getSpringFactoriesInstance方法又是通过调用org.springframework.core.io.support.SpringFactoriesLoader的loadFactoryNames方法: loadFactoryNames方法是到classpath里查找出含有META-INF/spring.factories这个文件的jar包,然后将这个文件加载进来,读取里面的内容: 这一步其实跟我们常见的SPI方式是一样的。在spring-boot这个jar里,就有META-INF这个目录及spring.factories这个文件,该文件截取如下: 找到ApplicationContextInitializer和ApplicationListener的实现类后,就会通过调用SpringApplication的createSpringFactoriesInstances方法来创建它们的实例: 然后再通过set方法将initializer和listener注册上:…

Spring Boot学习笔记(1)

1.如果想用Maven来build一个executable的基于SpringBoot的jar包,可以用Spring Boot Maven Plugin。只需要将以下配置加到pom.xml的plugins里面: 这个配置将会把在Maven package时创建的jar或者war重新打包,在target目录下,你会发现除了有两个jar包,其中一个带着original后缀,这个是由maven package创建的,而没带original后缀的就是由Spring Boot Maven Plugin重新打包的: $ mvn package $ ls target/*.jar target/myproject-1.0.0.jar target/myproject-1.0.0.jar.original 2.如果没在Spring Boot Maven Plugin指定<execution/>,你可以自己执行这个plugin: mvn package spring-boot:repackage 一旦在你的pom.xml里面添加了spring-boot-maven-plugin,…

Jetty源码剖析系列(2)-web.xml的解析与执行

当用Jetty来启动一个web项目的时候, Jetty会去读取war包的WEB-INFO目录里面的web.xml文件,然后解析这个web.xml。那这一步是什么时候进行和整个过程是怎样的?我们一起来分析一下。 以用Jetty Runner来启动一个war包为例, 在org.eclipse.jetty.runner.Runner类的configure方法里,我们看到它先构建出一个WebAppContext: 上面的代码中我们要注意webapp.setConfigurationClasses(_plusConfigurationClasses)这一句,我们先来看看_plusConfigurationClasses是什么东西: 我们可以看到就是一个String数组,里面包含了各种Configuration类的类名,这里我们要注意的是WebXmlConfiguration这个类,一看名字就知道是跟web.xml有关的:)。我们再来看setConfigurationClasses这个方法做了什么: 可以看到会把_plusConfigurationClasses里面的所有类都通过反射创建出一个实例对象来,其中包括了WebXmlConfiguration。 接下来我们要跳去看WebAppContext的doStart方法————至于是怎样调用到这个方法,我要另起一篇文章来剖析,简单说一下,就是,WebAppContext是实现了Handler接口,同时也实现了LifeCycle接口,而每个Handler又是被当成org.eclipse.jetty.server.Server(Jetty HTTP Servlet Server)的内部管理的一个Bean,当Server start的时候,这些Bean也会被start,…