抽空总结了Webx3框架,如有错误,欢迎指正!
Maven主要解决了以下两个问题:
(1)、它为项目构建引入了一个统一的接口,抽象了构建的生命周期,并为生命周期中的绝大部分任务提供了实现的插件。你不需要去关心这个生命周期里发生的事情,只需要把代码放在指定的位置,执行一条命令,整个构建过程就完成了。
(2)、其次,它为Java世界里的依赖引入了经纬度(groupId和artifactId),不再需要下载指定版本的jar包、拷贝到lib目录这些事情,只需要定义我的项目依赖了哪些构件(artifact),Maven会自动帮你管理依赖,并将jar包下载到正确的位置上。
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()方法
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>
(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;
Webx框架可以划分成三个大层次,如下:
SpringExt:底层基于Spring开源框架,对其的拓展多数都是派生Spring原生类避免重复造轮子,兼容开源框架的所有功能,提供拓展组件的能力
Webx Framework:基于Servlet API,提供基础的服务,例如:初始化Spring、初始化日志、接收请求、错误处理、开发模式等。Webx Framework只和servlet及spring相关 ——它不关心Web框架中常见的一些服务,例如Action处理、表单处理、模板渲染等,Webx3接收完请求之后,处理完之后将请求转发到Turbine的Pipeline进行处理相应的功能
Webx Turbine:基于Webx Framework,实现具体的网页功能,例如:Action处理、表单处理、模板渲染等。
<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配置文件是直接依赖于服务的实现,而不是接口的。接口相对稳定,而实现是可被改变的。另一方面,这个问题也会阻碍服务提供者改进他们的服务实现。
<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)通过在编写定义自定义元素节点,预留拓展点接口,自定义如果需要拓展新的内容,只需要实现拓展点接口,添加相应的实现,便可以对原有组件进行拓展
应用的web.xml类似于操作系统的引导程序。是一切Java Web应用的起点。简单来说,就是你写给Web容器的启动说明书。容器就按照你写的这份说明书来启动。web.xml定义于应用的初始化参数,在容器启动后统一存放在ServletContext中,作为应用的全局配置,后续spring容器启动主要依赖的也是该配置。
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);
}
}
}
}
这里是整个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");
}
}
因为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();
}
}