Webx3原理分析

WebX3原理分析

1 前言

抽空总结了Webx3框架,如有错误,欢迎指正!

2 背景知识

2.1 Maven

Maven主要解决了以下两个问题:

(1)、它为项目构建引入了一个统一的接口,抽象了构建的生命周期,并为生命周期中的绝大部分任务提供了实现的插件。你不需要去关心这个生命周期里发生的事情,只需要把代码放在指定的位置,执行一条命令,整个构建过程就完成了。

(2)、其次,它为Java世界里的依赖引入了经纬度(groupId和artifactId),不再需要下载指定版本的jar包、拷贝到lib目录这些事情,只需要定义我的项目依赖了哪些构件(artifact),Maven会自动帮你管理依赖,并将jar包下载到正确的位置上。

2.2 Servlet、HttpServlet及其Servlet生命周期

Servlet是java用于开发动态web资源的技术。是java web的基础。本质上,Servlet只是一个接口,或者说API,真正工作的是实现该接口的一个Java类的实例,由ServletContainer(常见的容器有Tomcat、Jboss)容器负责实例化。当请求到来时,容器将调用该实例的service方法。

HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。

public interface Servlet {

    public void init(ServletConfig config) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}

Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
(1)Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第4步,否则,执行第2步

(2)装载并创建该Servlet的一个实例对象

(3)调用Servlet实例对象的init()方法

(4)创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对
象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去

(5)WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法

2.3 DTD和Schema

DTD和Schema是两种常用的xml文件的约束文件,都是用来约定XML文件,目的是会提示使用者让使用者按照规定的格式去书写XML,验证XML文件有效性。主要的区别有一下几点:
(1)Schema遵循XML语法,学习成本低,不需要再学习新的语法;DTD和XML语法使用的是自己特殊的语法
(2)DTD只能指定元素含有文本,不定定义元素文本的类型,如字符型、整型、日期型、自定义类型等。Schema在这方面比DTD强大。
(3)Schema对命名空间的支持,如果出现两个相同的元素定义,使用命名空间可以完美的避免重复元素定义,而DTD不支持命名空间

下面就同一个XML文件,分别使用DTD和Schema约束,W3C官方给出的示例:

<?xml version="1.0"?>
<note>
    <to>George</to>
    <from>John</from>
    <heading>Reminder</heading>
    <body>Don‘t forget the meeting!</body>
</note>

DTD约束:

<!ELEMENT note (to, from, heading, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

Schema约束:

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3school.com.cn" xmlns="http://www.w3school.com.cn" elementFormDefault="qualified">

<xs:element name="note">
<xs:complexType>
    <xs:sequence>
        <xs:element name="to" type="xs:string"/>
        <xs:element name="from" type="xs:string"/>
        <xs:element name="heading" type="xs:string"/>
        <xs:element name="body" type="xs:string/>
   </xs:seuence>
</xs:complexType>
</xs:element>
</xs:schema>

2.4 日志框架和日志系统

(1)日志系统

最开始出现的是log4j,也是应用最广泛的日志系统,成为了目前java日志系统事实上的标准

JUL(java.util.logging.*),JDK 1.4 自带的日志系统;JUL 并没有明显的优势来战胜Log4j,反而造成了标准的混乱——采用不同日志系统的应用程序无法和谐共存;

Logback,是较新的日志系统,它是 Log4j 的作者吸取多年的经验教训以后重新做出的一套系统;它的使用更方便,功能更强,而且性能也更高;Logback 不能单独使用,必须配合日志框架 SLF4J 来使用;

(2)日志框架

JUL 诞生以后,为了克服多种日志系统并存所带来的混乱,就出现了“日志框架”。日志框架本身不提供记录日志的功能,
它只提供了日志调用的接口。日志框架依赖于实际的日志系统如Logback、Log4j或 JUL 来产生真实的日志;使用日志框架的好处是:应用的部署者可以决定使用哪一种日志系统(Logback、Log4j 还是 JUL),或者在多种日志系统之间切换,而不需要更改应用的代码;

JCL(Jakarta Commons Logging),这是目前最流行的一个日志框架,由 Apache Jakarta 社区提供;一些框架和老应用(如 Spring 等)都依赖于 JCL;

SLF4J,这是一个最新的日志框架,由 Log4j 的作者推出;SLF4J 提供了新的 API,特别用来配合Logback的新功能。但 SLF4J同样兼容 Log4j;

3 Webx3基础知识

3.1 WebX3层次化设计

Webx框架可以划分成三个大层次,如下:

SpringExt:底层基于Spring开源框架,对其的拓展多数都是派生Spring原生类避免重复造轮子,兼容开源框架的所有功能,提供拓展组件的能力

Webx Framework:基于Servlet API,提供基础的服务,例如:初始化Spring、初始化日志、接收请求、错误处理、开发模式等。Webx Framework只和servlet及spring相关 ——它不关心Web框架中常见的一些服务,例如Action处理、表单处理、模板渲染等,Webx3接收完请求之后,处理完之后将请求转发到Turbine的Pipeline进行处理相应的功能

Webx Turbine:基于Webx Framework,实现具体的网页功能,例如:Action处理、表单处理、模板渲染等。

3.2 SpringExt

3.2.1 Spring2.0装配Bean方式,对于习惯于使用spring的开发人员是熟悉且习以为常的

<bean id="resourceLoadingService" class="com.alibaba...ResourceLoadingServiceImpl">
    <property name="mappings">
    <map>
        <entry key="/file" value-ref="fileLoader" />
        <entry key="/webroot" value-ref="webappLoader" />
    </map>
    </property>
</bean>
<bean id="fileLoader" class="com.alibaba...FileResourceLoader">
    <property name="basedir" value="${user.home}" />
</bean>
<bean id="webappLoader" class=" com.alibaba...WebappResourceLoader" />

存在的问题:

(1)没有检验机制,错误必须等到运行时才会被发现。装配者仅从spring配置文件中,无法直观地了解这个配置文件有没有写对?例如:应该从constructor args注入却配成了从properties注入;写错了property的名称;注入了错误的类型等等。

(2)无法了解更多约束条件。即使装配者查看API源码,也未必能了解到某些约束条件,例如:哪些properties是必须填写的,哪些是可选的,哪些是互斥的?

(3)当服务的实现被改变时,Spring配置文件可能会失败。因为Spring配置文件是直接依赖于服务的实现,而不是接口的。接口相对稳定,而实现是可被改变的。另一方面,这个问题也会阻碍服务提供者改进他们的服务实现。

3.2.2 Webx3,因为Spring 2.0支持用XML Schema来定义配置文件。同样的功能,用Spring Schema来定义,可能变成下面的样子:

<resource-loading id="resourceLoadingService" xmlns="http://www.alibaba.com/schema/services/resource-loading">
    <resource pattern="/file">
        <file-loader basedir="${user.home}" />
    </resource>
    <resource pattern="/webroot">
        <webapp-loader />
    </resource>

</resource-loading>

解决了spring2.0之前的以下问题:

(1)这个配置文件比起前面的Spring Beans风格的配置文件简单易读得多。因为在这个spring配置文件里,它所用的“语言”是“领域相关”的,也就是说,和ResourceLoadingService所提供的服务内容相关,而不是使用像bean、property这样的编程术语。这样自然易读得多。(最简单的往往是最容易维护的,并不认为这种方式更容易维护,代码可读性和可维护性降低,尤其在debug时需要先查具体的实现类

(2)它是可验证的。你不需要等到运行时就能验证其正确性。任何一个支持XML Schema的标准XML编辑器,包括Eclipse自带的XML编辑器,都可以告诉你配置的对错,解决了spring bean2.0运行时才会报错的问题

(3)包含更多约束条件。例如,XML Schema可以告诉你,哪些参数是可选的,哪些是必须填的;参数的类型是什么等等。

(4)服务的实现细节对装配者隐藏。当服务实现改变时,只要XML Schema是不变的,那么Spring的配置就不会受到影响。

为了实现服务实现对具体装配者隐藏且自定义的xml配置最终必须转换成spring容器所需呀的bean,做出了一下的逻辑更改

(1)每一个元素节点对对应一个解释器(xml schema nameSpace命名空间),编写对应的解释器对自定义元素节点解析成spring所对应的beanDefinition,最终注册在spring容器中

(2)通过在编写定义自定义元素节点,预留拓展点接口,自定义如果需要拓展新的内容,只需要实现拓展点接口,添加相应的实现,便可以对原有组件进行拓展

4 Webx3启动

4.1 Webx应用启动入口

应用的web.xml类似于操作系统的引导程序。是一切Java Web应用的起点。简单来说,就是你写给Web容器的启动说明书。容器就按照你写的这份说明书来启动。web.xml定义于应用的初始化参数,在容器启动后统一存放在ServletContext中,作为应用的全局配置,后续spring容器启动主要依赖的也是该配置。

4.1.1 日志初始化

LogConfiguratorListener通过实现ServletContextListener接口监听应用启动ServletContextEvent事件,并从ServletContextEvent读取全局配置ServletContext读取日志相关的配置,进行日志系统的初始化

 <listener>
    <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class>
</listener>

日志系统初始化相关逻辑,主要就是读取相关的日志配置,通过默认的slf4j去查询相应的日志系统的配置文件(目前仅支持log4j和logback的日志系统初始化组件),如果ServletContext配置了日志参数就寻找相应的配置并使用该配置的参数初始化,否则使用默认的配置,如果依赖logback.jar就寻找WEB-INF下的logback.xml,如果依赖log4j的jar就寻找WEB-INF下的log4j.xml配置进行日志系统的初始化

public class LogConfiguratorListener implements ServletContextListener {

     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);

            // 取得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);
        }
    }
}
}

4.1.2 springExt容器初始化

这里是整个Webx应用的重要入口,WebxContextLoaderListener同样实现了ServletContextListener接口,监听应用的启动事件ServeltContextEvent,在此启动SpringExt容器,加载Webx环境

<listener>
    <listener-class>com.alibaba.citrus.webx.context.WebxContextLoaderListener</listener-class>
</listener> 

打开WebxContextLoaderListener看一下源码,可以看到Webx3并没有重复造轮子,而是派生了Spring的容器监听类ContextLoaderListener,重写了createContextLoader方法,实现里面可以看到返回的WebxCoponenentsLoader类,并重写了该类的getDefaultContextClass,返回的类为springExt的容器类WebxComponentsContext,代替spring原有的默认的xmlApplicationContext容器类,前者完全兼容后者

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;
}
}   

因为WebxContextLoaderListener派生了ContextLoaderListener,所以还是要从ContextLoaderListener分析Webx3的启动流程,看具体的实现逻辑创建了ContextLoader,因为WebxContextListener重写了createContextLoader,所以返回的结果是被重写了的WebxComponenetsContextLoader,并使用该类进行initWebApplicationContext初始化容器

public class ContextLoaderListener implements ServletContextListener {

    private ContextLoader contextLoader;

    /**
    * Initialize the root web application context.
    */
    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = createContextLoader();
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }
}

上面源码跟到了WebxComponentsLoader,并没有什么新发现,主要是继续调用Spring容器的ContextLoader类的initWebApplicationContext方法继续执行容器初始化流程

public class WebxComponentsLoader extends ContextLoader {

    @Override
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext){
        this.servletContext = servletContext;
        init();
        return super.initWebApplicationContext(servletContext);
    }
}   

在init方法中重要的一行代码如下,新建一个WebApplicationContext,由于之前WebxComponentsContext重写了getDefaultContextClass方法,所以此处拿到的容器类是WebxComponentsContext代替Spring原有的xmlApplicationContext容器类

this.context = createWebApplicationContext(servletContext);

继续跟代码可以看到容器启动的方法,整个容器的初始化过程,主要都是spring初始化的内容,不着重介绍,具体内容spring技术内幕里面介绍的比较完全

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();

    // Prepare the bean factory for use in this context.
    prepareBeanFactory(beanFactory);
    try {
        // Allows post-processing of the bean factory in context subclasses.
        //被WebxComponentsContext所覆盖
        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.
        //被WebxComponentsContext所覆盖
        finishRefresh();
    }
}
}

在上面可以看到被覆盖的方法postProcessBeanFactory,WebxComponentContext调用父类的其实是WebxComponentsLoader.postProcessBeanFactory的方法

public class WebxComponentContext extends WebxApplicationContext {
    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        super.postProcessBeanFactory(beanFactory);
        beanFactory.registerResolvableDependency(WebxComponent.class, component);
    }
}

WebxComponentsContext还覆盖了父类的一个方法如下,原有的spring容器指定了配置文件的位置为WEB-INF下的applicationCotext.xml,现在寻找的配置项为WEB-INF/webx.xml

public class WebxApplicationContext extends ResourceLoadingXmlWebApplicationContext {
    @Override
    protected String[] getDefaultConfigLocations() {
        if (getNamespace() != null) {
        return new String[] { WEBX_COMPONENT_CONFIGURATION_LOCATION_PATTERN.replace("*", getNamespace()) };
        } else {
            return new String[] { WEBX_CONFIGURATION_LOCATION };
        }
    }
}

WebxComponentsContext重写的第二个方法是finishRefresh(),优先执行spring主容器的创建,再执行各个子模块的容器初始化工作

@Override
protected void finishRefresh() {
    super.finishRefresh();
    getLoader().finishRefresh();
}

调用WebxComponentsLoader的finishRefresh对所有子容器进行初始化工作,父容器(WebxComponentsContext,通过webx.xml建立)若干子容器(即组件,WebxComponentContext,通过webx-*.xml建立),至此所有组件都初始化完成

public class WebxComponentsLoader extends ContextLoader {
    /**
    * 初始化所有components。
    */
    public void finishRefresh() {
        components.getWebxRootController().onFinishedProcessContext();

        for (WebxComponent component : components) {
            logInBothServletAndLoggingSystem("Initializing Spring sub WebApplicationContext: " + component.getName());

            WebxComponentContext wcc = (WebxComponentContext) component.getApplicationContext();
            WebxController controller = component.getWebxController();

            wcc.refresh();
         controller.onFinishedProcessContext();
    }

    logInBothServletAndLoggingSystem("WebxComponents: initialization completed");
    }
}

5 Webx3运行

5.1 Webx 请求入口

因为WebxFramework基于Servelt API,通过Filter可以拦截浏览器的所有请求,拦截到请求之后,便可以进入Webx3框架内部的执行链

<filter>
    <filter-name>webx</filter-name>
    <filter-class>com.alibaba.citrus.webx.servlet.WebxFrameworkFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>webx</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

通过下面的类,我们便可以找到了所有请求的入口,进而得到应用的父容器,并将请求交给根容器的WebxRootController的Service方法处理

public class WebxFrameworkFilter extends FilterBean {
    protected void doFilter(){
        getWebxComponents().getWebxRootController().service(request, response, chain);
    }
}

在WebxRootController中主要将request和response封装成requesContext,并将请求继续转发请求到handlerRequest()

public final void service(HttpServletRequest request, HttpServletResponse response, FilterChain chain){
RequestContext requestContext = null;

requestContext = assertNotNull(getRequestContext(request, response), "could not get requestContext");

if (isRequestPassedThru(request) || !handleRequest(requestContext)) {
    giveUpControl(requestContext, chain);
}

}

继续跟源码handlerRequest(),在此方法中首先根据请求查询相应的path,再根据path查找相应的组件容器,将请求转发给子容器的控制器,如果未指定的情况下,默认子容器的控制器为WebxController,请求由WebxRootController转发给WebxController

@Override
protected boolean handleRequest(RequestContext requestContext) throws Exception {
    HttpServletRequest request = requestContext.getRequest();

     String path = ServletUtil.getResourcePath(request);

    // 再根据path查找component
    WebxComponent component = getComponents().findMatchedComponent(path);
    boolean served = false;

    if (component != null) {
        try {
            WebxUtil.setCurrentComponent(request, component);
            served = component.getWebxController().service(requestContext);
        }finally {
            WebxUtil.setCurrentComponent(request, null);
        }
    }
    return served;
}

请求目前是转发到WebxContrller,在此主要是调用Pipeline实例执行整个流程的valve(类似于filter执行链,但是比filter功能更加强大,支持循环重复执行),整个框架的的约定优于配置都在此体现。一个请求进入,首先会进行url分析,得到相应的路径,检查csrfToken、权限的valve,再根据url的名字和后缀,分别执行不同的valve,最后进行模板渲染,返回Http请求结果。

public class WebxControllerImpl extends AbstractWebxController {
    private Pipeline pipeline;

    @Override
    public void onRefreshContext() throws BeansException {
        super.onRefreshContext();
        initPipeline();
    }

    private void initPipeline() {
        pipeline = getWebxConfiguration().getPipeline();
        log.debug("Using Pipeline: {}", pipeline);
    }

    public boolean service(RequestContext requestContext) throws Exception {
        PipelineInvocationHandle handle = pipeline.newInvocation();

        handle.invoke();    

        // 假如pipeline被中断,则视作请求未被处理。filter将转入chain中继续处理请求。
        return !handle.isBroken();
    }
}

你可能感兴趣的:(java)