背景介绍:
webx3是一套基于Java Servlet API的通用Web框架。它在阿里巴巴集团内部被广泛使用。自2010年底,向社会开放源码。最新的webx3框架是基于spring扩展的,兼容与spring的容器。
正式介绍原理之前首先介绍下Web开发最基础的知识
HTTP:(Hyper Text Transport Protocol)超文本传输协议,所谓超文本其实就是除了能够传输文本内容,还能够传输带有格式的文本(HTML和XML),以及二进制字节流。而我们日常访问网站可能并不能感受到HTTP的存在,主要是因为浏览器替我们做了发送HTTP请求的操作,对于用户可能是无感知的
TCP:与常见的UDP协议所对应,是一种数据传输协议,从服务器到返回到用户浏览器的数据
Cookie:因为HTTP协议是一种没有状态的协议,所以为了保存一些用户的信息,所以采用了Cookie保存在用户本地,每次在向服务器端发送HTTP请求的时候将Cookie中存储的配置连着HTTP请求一些发送到服务器端
Session:Session的存在也是因为HTTP协议是无状态的协议,与Cookie的主要区别在于Session状态是在服务器端维持的,而Cookie的状态是在本地维持的,一般用Session保存网站用户的登录状态,用Cookie记录一些用户的行为状态
下面简要介绍下发送一次HTTP请求及其服务器的响应的过程:
HTTP请求如下:
请求方法(常用的GET、POST),请求的URL和参数
一些附加的信息(Request Header Fields、像是Cookie等)
HTTP响应如下:
响应的状态码(100-500)通常200为正常响应
响应的附加信息(Response Header Fields,例如Set-Cookie等)
响应的实体(例如HTML,下载的文件)
在Java Web中这些请求和响应都是以字节流形式存在文本字符串。在这个过程中,框架做的事情屏蔽了这些细节,对这些功能进行抽象,主要的两个类就是HTTPServletRequest和HTTPServletResponse,Web中常见的Cookie、下载、重定向、跳转都可以在这两个类里面完成
Web容器的启动过程:
关于Web容器的启动,首先要找到整个Web容器的起点,而web.xml就是容器的起点,在容器加载项目的时候首先会去找该项目下的web.xml文件,简单来说,web.xml就是web容器启动说明书(类似于PC的开机引导程序),由用户指定告诉容器该如何启动容器,在web.xml文件中会配置Servlet的基本参数及其初始化参数。
打开web.xml来看,首先可以看到这样的一段代码,这里定义了一个Listener,从名字就可以看出来就是一个监听者,主要监听着具体的Event(事件),当该Event事件被触发的时候,然后执行该类中的某方法
<!-- 初始化日志系统 --> <listener> <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class> </listener>
打开LogConfiguratorListener类,我们可以看到看到该类主要实现了ServletListener的接口,我们可以尝试着打开ServletListener接口,打开该类可以看到该接口只有两个方法,一个是容器初始化调用,另外一个是容器销毁,而触发的事件是ServletContextEvent由容器触发,每次事件触发的时候便会执行相应的方法
public interface ServletContextListener extends EventListener { public void contextInitialized(ServletContextEvent sce); public void contextDestroyed(ServletContextEvent sce); }
再次看LogConfiguratorListener类看一下当容器启动的时候是如何触发日志系统,简单的解释下,就是在容器启动的时候将web.xml的初始化参数信息加载到servletContext并通过servletEvent类传递到该类里面,从代码中可以看到从初始化参数中获取是否存在用户配置的指定的日志日志及其日志配置文件,然后通过日志框架去找对应的日志系统,没有找到,则使用默认的日志系统logback,然后再找ServletConetxt中的配置文件,如果找到,则使用指定的配置文件对日志系统进行配置,否则使用默认的日志系统配置文件。默认使用的日志框架是slf4j,默认使用的日志系统是logback
public class LogConfiguratorListener implements ServletContextListener { private static final String LOG_CONFIGURATION = "logConfiguration"; private static final String LOG_SYSTEM = "logSystem"; private static final String LOG_PREFIX = "log"; private LogConfigurator[] logConfigurators; public void contextInitialized(ServletContextEvent event) { ServletContext servletContext = event.getServletContext(); // 取得所有以log开头的init params。 Map<String, String> params = getLogInitParams(servletContext); // 从context init-param中取得logSystem的值,可能为null。 String[] logSystems = getLogSystems(params); // 取得指定名称的logConfigurator,如未取得,则抛出异常,listener失败。 logConfigurators = LogConfigurator.getConfigurators(logSystems); for (LogConfigurator logConfigurator : logConfigurators) { String logSystem = logConfigurator.getLogSystem(); // 取得指定logConfigurator的配置文件。 String logConfiguration = getLogConfiguration(params, logSystem); servletContext.log(String.format("Initializing %s system", logSystem)); // 取得log配置文件。 URL logConfigurationResource; try { logConfigurationResource = servletContext.getResource(logConfiguration); } catch (MalformedURLException e) { logConfigurationResource = null; } // 如未找到配置文件,则用默认的值来配置,否则配置之。 if (logConfigurationResource == null) { servletContext.log(String.format("Could not find %s configuration file \"%s\" in webapp context. Using default configurations.", logSystem, logConfiguration)); logConfigurator.configureDefault(); } else { Map<String, String> props = logConfigurator.getDefaultProperties(); initProperties(props); props.putAll(params); logConfigurator.configure(logConfigurationResource, props); } } }
日志系统初始化完成之后,再看整个Webx3容器的初始化过程,web.xml中的配置如下
<!-- 装载/WEB-INF/webx.xml, /WEB-INF/webx-*.xml --> <listener> <listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class> </listener>打开WebxContextLoaderListener类,可以看到代码非常的简单,继承了ContextLoaderListener类,重写了createContextLoader方法,能复用就复用,坚持不重新造轮子,主要使用的还是Spring的ContextLoaderListener进行容器的初始化
public class WebxContextLoaderListener extends ContextLoaderListener { @Override protected final ContextLoader createContextLoader() { return new WebxComponentsLoader() { @Override protected Class<? extends WebxComponentsContext> getDefaultContextClass() { Class<? extends WebxComponentsContext> defaultContextClass = WebxContextLoaderListener.this .getDefaultContextClass(); if (defaultContextClass == null) { defaultContextClass = super.getDefaultContextClass(); } return defaultContextClass; } }; } protected Class<? extends WebxComponentsContext> getDefaultContextClass() { return null; } }
然后打开Spring的原生类ContextLoaderListener,主要做了一件事情就是就是创建了一个ContextLoader然后使用ContextLoader初始化整个容器,由于WebxContextLoaderListener重写了createContextLoader方法,所以实际上创建的contextLoader是WebxCoponentsLoader类,最终初始化容器类的也是这个,从名字就可以看出来功能,组件加载
public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); }从上面可以看到正在初始化容器的类WebxCoponentsLoader类,然后继续跟代码到这里,至今WebxCoponents持有了一份ServletContext的引用,然后就继续调用了Spring的contextLoader继续进行容器初始化流程,依旧坚持不重新造轮子,复用spring的核心类
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) throws IllegalStateException, BeansException { this.servletContext = servletContext; init(); return super.initWebApplicationContext(servletContext); }至于initWebApplicationContext太长,就不将代码全部贴出来了,只贴几个核心的代码段,下面的代码创建了一个WebApplicationContext
this.context = createWebApplicationContext(servletContext);打开createApplicationContext可以看到下面这行代码,从方法名可以看出决定使用哪个容器类,spring原生的实现是xmlWebApplicationContext,主要通过读取xml配置文件建立Spring容器的环境类,而当前运行的类是ContextLoader的实例是WebComponentsLoader,覆盖了该方法,最终获取到的容器类是WebComponentsContext代替了默认的xmlWebApplicationContext
Class<?> contextClass = determineContextClass(sc);
创建完容器还会进行一系列的操作,例如从servletContext读取spring的配置文件位置,然后执行wac.refresh()方法,该方法的具体实现在abstractApplicationContext中,至此体现另一个思想,用接口提供功能描述,用抽象类复用代码,下面来看具体的Web容器初始化过程
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); //主要对容器初始化的相关时间进行记录 // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //定位、载入和注册BeanDefinition // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } } }在这里主要解释下ObtainFreshBeanFactory,也是比较核心的一个方法,主要是通过ResourceLoadingService加载spring配置文件,将配置文件的的数据转换成为BeanDefinition的数据结构,在这里实现了三个流程,寻找容器类配置文件资源、载入资源、解析注册BeanDefinition,使用Map来持有所有的BeanDefinition
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); this.initBeanDefinitionReader(beanDefinitionReader); this.loadBeanDefinitions(beanDefinitionReader); }
在这里会去加载Webx.xml这个根容器配置文件,并通过SAX将xml件解析成DOM对象,然后对DOM对象节点及其属性进行解析,最终封装成BeanDefinition的数据结构,然后xm,下图首先介绍下Spring对XML文件中的元素的解析,然后介绍下SpringExt对XML文件中元素的解析,主要的区别还是在于对非Bean定义而是使用命名空间定义的元素进行解析