什么是Webx
Webx是基于Java Servlet API的通用web框架,它建立在SprintExt框架基础之上,由SprintExt进行模块组装,提供扩展机制,所以Webx继承了Spring的所有功能,但是比Spring具有更强大的扩展能力。
为什么要用Webx
Webx建立在SprintExt框架的基础之上,所以不仅具有Spring的所有特性,而且有比Spring更强大的扩展性;Webx鼓励层次化的设计,SpringExt提供了创建和配置层次化组件的机制,使系统中的每一个部分都可以被替换或增强,而Webx本身也采用了层次化的设计,你可以使用Webx框架的一部分,也可能使用全部Webx框架。
Webx的层次结构
Webx框架可以划分为三个层次:
1、SpringExt
基于Spring,并提供了扩展组件的能力,是Webx框架的基础。
2、Webx Framework
基于Servlet API,提供基础服务,例如:初始化spring、被始化日志、错误处理、接收请求、开发模式等,它只与servlet和spring有关,并不关心如模板渲染、action处理、表单处理等。
3、Webx Turbine
基于Webx Framework,实现具体的网页功能,如模板渲染、action处理、表单处理等。
环境准备
1、eclipse采用Juno64位版本
2、maven采用2.2.1版本
3、为eclipse安装m2eclipse插件
创建WebX项目
1、新建Maven Project
打开eclipse后,进入菜单File->New->Other,在Maven项下选择Maven Project,点击下一步
2、选择project name和workspace弹窗使用默认设置,点下一步
3、选择Archetype弹窗,可以用filter输入框筛选一下,选择archetype-webx-quickstart,其它设置保持默认,点击下一步
4、archetype parameters弹窗中输入Group Id和Artifact Id,点击finished
5、到此一个简单的webx项目已经生成了,来看一下目录结构
2、配置项目
1、webx项目各个目录的功用后面再说,先让这个项目运行起来。在项目名称上右键,选择Run As->Run Configurations,弹出一个弹窗如下
如果Maven Build项下没有子项,则双击之,会出现配置窗口
在这个配置窗口中有三项要填,name输入框为此配置设置一个名字、Base directory选择file system里此项目的路径、Goals输入框填tomcat:run,以上的配置是让此项目运行在tomcat下,如果要项目运行在jetty下的话可以在Goals输入框写入jetty:run,配置输入完成,点击Run
2、所有配置已经完成,可以运行一下试试了,在浏览器里打开http://localhost:8080/firstdemo/
~~~OK,这个简单的webx项目已经运行起来了
webx的页面结构
webx页面由三部分组成:
- layout 对应着templetas文件夹里的layout文件夹,负责页面的布局。
- screen 对应着templates文件夹里的screen文件夹,是页面的主体内容。
- control 对应着templates文件夹里的control文件夹,是页面的通用部分,可以被多个screen或layout引用,例如页面导航、页脚等
![](https://img-my.csdn.net/uploads/201303/27/1364396856_9006.png)
webx执行流程
webx的执行流程是由pipeline控制的,所以流程的定义就在pipeline配置中,下面是推荐的pipeline配置
<services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves"> <!-- 初始化turbine rundata,并在pipelineContext中设置可能会用到的对象(如rundata、utils),以便valve取得。 --> <prepareForTurbine /> <!-- 设置日志系统的上下文,支持把当前请求的详情打印在日志中。 --> <setLoggingContext /> <!-- 分析URL,取得target。 --> <analyzeURL homepage="homepage" /> <!-- 检查csrf token,防止csrf攻击和重复提交。假如request和session中的token不匹配,则出错,或显示expired页面。 --> <checkCsrfToken /> <loop> <choose> <when> <!-- 执行带模板的screen,默认有layout。 --> <pl-conditions:target-extension-condition extension="null, vm, jsp" /> <performAction /> <performTemplateScreen /> <renderTemplate /> </when> <when> <!-- 执行不带模板的screen,默认无layout。 --> <pl-conditions:target-extension-condition extension="do" /> <performAction /> <performScreen /> </when> <otherwise> <!-- 将控制交还给servlet engine。 --> <exit /> </otherwise> </choose> <!-- 假如rundata.setRedirectTarget()被设置,则循环,否则退出循环。 --> <breakUnlessTargetRedirected /> </loop> </services:pipeline>
可以看出webx根据target来判断执行哪一个流程。如果后缀为空或为vm、jsp时,则执行第一个流程,如果后缀是do则执行第二个流程。
如果用户输入一个地址http://localhost:8080的话,webx的执行流程如下:
1、<anaylzeURL>分析url,取得target
由于http://localhost:8080并不包含context path,所以分析得到的target为null,在这种情况下target会取默认值homepage,target并不是模板也不是类,只是一个抽象概念,在后面的步骤中会根据target映射到模板和module类。
2、<choose>分支
根据取得的target可以确定,符合条件extension="null,vm,asp",webs会执行这一流程
3、<performAction>执行action
webx里的action专门用来处理用户提交的表单,由于本次请求没有action参数,所以忽略了这一步骤
4、<performTemplateScreen>查找并执行screen
在这一步,会把target映射成screen的module类,映射的规则如下:如果target是xxx/yyy/zzz,那么webs会按照以下的顺序来screen模块:
screen.xxx.yyy.zzz
screen.xxx.yyy.Default
screen.xxx.Default
screen.Default
本次请求的target是homepage,所以会查找screen.homepage和screen.default两个模块类,如果找到就执行,如果找不到也没关系。
5、<renderTemplates>渲染模板
这一步又分两部分:将target映射成screen模板、将target映射成layout模板
假设target是xxx/yyy/zzz,则webs会查找screen模板/templetes/screen/xxx/yyy/zzz,如果没找到就会报404错误。找到screen模板后,webs会按以下顺序查找layout模板:
/templates/layout/xxx/yyy/zzz
/templates/layout/xxx/yyy/default
/templates/layout/xxx/default
/templates/layout/default
如果没找到layout模板,则直接渲染screen模板,如果找到了layout模板,则将渲染的screen模板嵌入到layout模板中。
6、<breakunlessTargetRedirect>内部重定向
如果设置了内部重定向,则模板渲染完成后会循环loop标签,如果没有定义重定向,则执行完成,退出循环。
至此,一个webx请求的执行流程完成了~
说起LogConfiguratorListener来,还得从配置文件web.xml开始说起,在web.xml配置中,有一项很重要的配置项,那就是日志系统初始化:
- <!-- 初始化日志系统 -->
- <listener>
- <listener-class>com.alibaba.citrus.logconfig.LogConfiguratorListener</listener-class>
- </listener>
在WEB应用被起动的时候,这个listener就会被激活,进行日志系统的初始化工作。
LogConfiguratorListener的作用
正如前面所说的,LogConfiguratorListener就是负责在WEB应用起动的时候初始化日志系统,在做这些工作的时候,它会读取init参数,这些参数也同样配置在web.xml文件中,如下:
- <context-param>
- <param-name>loggingRoot</param-name>
- <param-value>/tmp/logs</param-value>
- </context-param>
- <context-param>
- <param-name>loggingLevel</param-name>
- <param-value>INFO</param-value>
- </context-param>
- <context-param>
- <param-name>loggingCharset</param-name>
- <param-value>UTF-8</param-value>
- </context-param>
- <context-param>
- <param-name>log...</param-name>
- <param-value>...</param-value>
- </context-param>
LogConfiguratorListener实现
LogConfiguratorListener实现了ServletContextListener接口,ServletContextListener是ServletContext的监听接口,当ServletContext被创建时,ServletContextListener的contextInitialized方法会被调用,日志系统的初始化就在这个方法里实现。当应用被关闭时,ServletContextListener的contextDestroyed方法会被调用,在这个方法里会把日志系统关闭。下面是方法的源码:
- 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);
- }
- }
- }
分析一下上面的源码,看看方法contextInitialized都干了哪些事儿呢?
1、取init参数
在web.xml文件里配置的context-param参数中,查找以log开头的参数。
2、根据init参数,生成日志配置器
在步骤1中取到的以log开头的参数中,查找logSystem参数,里面设置了要采用的日志系统名称,如果有多个的话会以逗号或空格分隔。如果web.xml中没有设置logSystem参数,则默认采用slf4j中的日志系统。拿到logSystem名称后,根据名称构造出LogConfigurator。
3、读取每个日志配置器的配置文件,如果配置文件不存在就用默认值来配置
在web.xml文件中,logConfiguration参数配置了日志系统配置文件的路径,如果web.xml文件中没有这个文件,那么采用默认路径"/WEB-INF/日志系统名称.xml“,如果配置文件找到了,用它来配置步骤2中生成的LogConfigurator;如果没有找到配置文件,则还有一个默认的配置文件,名称是”日志系统名称-default.xml"。
WebX是基于Java Servlet API的通用web框架。WebX致力于提供极具扩展性的机制,以满足web应用不断变化的需求,而springext正是webx扩展机制的基石。springext是对spring的扩展,在spring的基础之上提供了扩展能力。
springext能作什么呢?
以ResourceLoadingService为例,它是webs的非常有用的一个service,可以在各种输入源中加载资源文件。
![](https://img-my.csdn.net/uploads/201304/14/1365933560_2420.jpg)
以bean方式装配ResourceLoadingService
如果以Spring Bean方式配置ResourceLoadingService,会是如下这样子:
- <bean id="resourceLoadingService" class="com.alibaba…ResourceLoadingService">
- <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" />
这样的配置简单易行,体现了spring的IoC原则,但是它具有以下缺点:
1、没有检验机制,即使是spring配置写错了,也要等到运行时才会发现。
2、无法了解更多约束,即使是查看了api文档,也未必能知道哪些参数是必填的,哪些参数是互斥的。
3、由于spring配置依赖于服务的实现,所以当服务的实现改变时,spring配置会失败。
以spring scheme 方式装配ResourceLoadingService
Spring 2.0以后,开始支持了xml schema来定义配置,用Spring Schema来配置ResourceLoadingService会变成下面的样子:
- <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>
相对于spring bean定义方式,spring schema方式有以下优点:
1、更易读,配置描述的是服务提供的内容,没有了像bean、property之类的编程术语。
2、具有了检验机制,任何支持xml schema的xml编辑器都可以告诉你配置是否有错误。
3、包含了更多约束,xml schema可以告诉你哪些参数是可选的,哪些参数是必填的。
4、最重要的一点,服务的实现对装配者来说是透明的,只要xml schema不变,即使服务实现发生了变化,也不用修改xml配置。
最后一点实现是通过schema解释器来实现的,服务提供都会负责提供这个解释器,它的作用就是把xml schema配置翻译成spring bean配置。
将具体的实现交给服务提供者,使服务使用者可以用它所能理解的语言来装配服务,这就是spring schema的核心价值。
spring schema有一个很大的缺点:无法扩展。假设你想在上面的例子中添加一个新的资源加载器database-loader,由于在设计schema时没有考虑这种情况,加上去之后会报错,除非你修改schema,而这显然违反了OCP原则。
以springext schema方式装配ResourceLoadingService
SpringExt扩展了Spring,使Spring Schema具有了扩展能力,如果用SpringExt来定义相同的配置会是下面的样子:
- <resource-loading id="resourceLoadingService"
- xmlns="http://www.alibaba.com/schema/services"
- xmlns:loaders="http:www.alibaba.com/schema/services/resource-loading/loaders">
- <resource pattern="/file">
- <loaders:file-loader basedir="${user.home}" />
- </resource>
- <resource>
- <loaders:webapp-loader />
- </resource>
- </resource-loading>
如果要添加一种新的ResourceLoader--DatabaseResourceLoader,只需要两步:
1、配置maven依赖
2、在配置文件中添加配置项如下:
<resource>
<loaders:database-loader connection="jdbc:mysql:mydb" />
</resource>