java this context_java web中各种context的关系

网上博客中看到一句话,很形容的描绘了web程序和上下文的关系,这里引用一下来说明:如果对“上下文”不太了解的,我这边说下,程序里面所谓的“上下文”就是程序的执行环境,打个比方:你有家吧?如果家都没有就别学编程了,租的也行啊!你就相当于web程序,家就相当于web程序的上下文,你可以在家里放东西,也可以取东西,你的衣食住行都依赖这个家,这个家就是你生活的上下文环境。

Spring启动过程

第一步:

首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;

第二步:

其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以【WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE】为属性Key,将其存储到ServletContext中,便于获取;

第三步:

再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用【WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE】先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文(有个parent属性作为对Spring的ApplicationContext的引用)。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。

通过上面这个Spring的启动过程,我们可以清楚的了解ServletContext、WebApplicationContext、XmlWebApplicationContext、以及DispatcherServlet上下文之间的关系,并且会将WebApplicationContext放在ServletContext中。

format,png

XmlWebApplicationContext体系结构.png

1. ServletContext:

首先说说ServletContext这个web应用级的上下文。web容器(比如tomcat、jboss、weblogic等)启动的时候,它会为每个web应用程序创建一个ServletContext对象 它代表当前web应用的上下文(注意:是每个web应用有且仅创建一个ServletContext,一个web应用,就是你一个web工程)。一个web中的所有servlet共享一个ServletContext对象,所以可以通过ServletContext对象来实现Servlet之间的通讯。在一个继承自HttpServlet对象的类中,可以通过this.getServletContext来获取。

2. WebApplicationContext:

通过源码详细说明一下 第二步 的过程,web.xml(上图)中我们配置了ContextLoaderListener,该listener实现了ServletContextListener的contextInitialized方法用来监听Servlet初始化事件:

contextConfigLocation

classpath:spring/SpringApplicationContext.xml

org.springframework.web.context.ContextLoaderListener

由下面的源码可以发现初始化的是WebApplicationContext的IoC容器,它是一个接口类,其默认实现是XmlWebApplicationContext。

public class ContextLoaderListener extends ContextLoader implementsServletContextListener {publicContextLoaderListener() {

}publicContextLoaderListener(WebApplicationContext context) {super(context);

}

@Overridepublic voidcontextInitialized(ServletContextEvent event) {

initWebApplicationContext(event.getServletContext());

}

@Overridepublic voidcontextDestroyed(ServletContextEvent event) {

closeWebApplicationContext(event.getServletContext());

ContextCleanupListener.cleanupAttributes(event.getServletContext());

}

}

initWebApplicationContext(event.getServletContext()),进行了创建根上下文,并将该上下文以key-value的方式存储到ServletContext中。

publicWebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw newIllegalStateException("Cannot initialize context because there is already a root application context present - " +

"check whether you have multiple ContextLoader* definitions in your web.xml!");

}

Log logger= LogFactory.getLog(ContextLoader.class);

servletContext.log("Initializing Spring root WebApplicationContext");if(logger.isInfoEnabled()) {

logger.info("Root WebApplicationContext: initialization started");

}long startTime =System.currentTimeMillis();try{//Store context in local instance variable, to guarantee that//it is available on ServletContext shutdown.

if (this.context == null) {this.context =createWebApplicationContext(servletContext);

}if (this.context instanceofConfigurableWebApplicationContext) {

ConfigurableWebApplicationContext cwac= (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {//The context has not yet been refreshed -> provide services such as//setting the parent context, setting the application context id, etc

if (cwac.getParent() == null) {//The context instance was injected without an explicit parent ->//determine parent for root web application context, if any.

ApplicationContext parent =loadParentContext(servletContext);

cwac.setParent(parent);

}

configureAndRefreshWebApplicationContext(cwac, servletContext);

}

}

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.context);

ClassLoader ccl=Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {

currentContext= this.context;

}else if (ccl != null) {

currentContextPerThread.put(ccl,this.context);

}if(logger.isDebugEnabled()) {

logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE+ "]");

}if(logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() -startTime;

logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");

}return this.context;

}catch(RuntimeException ex) {

logger.error("Context initialization failed", ex);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throwex;

}catch(Error err) {

logger.error("Context initialization failed", err);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throwerr;

}

}

在initWebApplicationContext()方法中主要体现了WebApplicationContext实例的创建过程。首先,验证WebApplicationContext的存在性,通过查看ServletContext实例中是否有对应key的属性验证WebApplicationContext是否已经创建过实例。如果没有通过,createWebApplicationContext()方法来创建实例,并存放至ServletContext中。

protectedWebApplicationContext createWebApplicationContext(ServletContext sc) {

Class> contextClass =determineContextClass(sc);if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +

"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");

}return(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

}

在createWebApplicationContext()方法中,通过BeanUtils.instanceClass()方法创建实例,而WebApplicationContext的实现类名称则通过determineContextClass()方法获得。

protected Class>determineContextClass(ServletContext servletContext) {

String contextClassName=servletContext.getInitParameter(CONTEXT_CLASS_PARAM);if (contextClassName != null) {try{returnClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());

}catch(ClassNotFoundException ex) {throw newApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);

}

}else{

contextClassName= defaultStrategies.getProperty(WebApplicationContext.class.getName());try{return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());

}catch(ClassNotFoundException ex) {throw newApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);

}

}

}

determineContextClass()方法,通过defaultStrategies.getProperty()方法获得实现类的名称,而defaultStrategies是在ContextLoader类的静态代码块中赋值的。具体的途径,则是读取ContextLoader类的同目录下的ContextLoader.properties属性文件来确定的。

也就是说,在初始化的过程中,程序会首先读取ContextLoader类的同目录下的属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个类通过反射进行实例的创建。

综上所述:

LoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置了这个监听器,启动容器时,就会默认执行它实现的contextInitialized()方法初始化WebApplicationContext实例,并放入到ServletContext中。由于在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。

3. DispatcherServlet

format,png

DispatcherServlet结构图.png

详解第三步,contextLoaderListener监听器初始化完毕后,开始初始化DispatcherServlet,下面为初始化方法的源码:

protected voidinitStrategies(ApplicationContext context) {

initMultipartResolver(context);

initLocaleResolver(context);

initThemeResolver(context);

initHandlerMappings(context);

initHandlerAdapters(context);

initHandlerExceptionResolvers(context);

initRequestToViewNameTranslator(context);

initViewResolvers(context);

initFlashMapManager(context);

}

需要做的八件事情如下所述:

initMultipartResolver:初始化MultipartResolver,用于处理文件上传服务,如果有文件上传,那么就会将当前的HttpServletRequest包装成DefaultMultipartHttpServletRequest,并且将每个上传的内容封装成CommonsMultipartFile对象。需要在dispatcherServlet-servlet.xml中配置文件上传解析器。

initLocaleResolver:用于处理应用的国际化问题,本地化解析策略。

initThemeResolver:用于定义一个主题。

initHandlerMapping:用于定义请求映射关系。

initHandlerAdapters:用于根据Handler的类型定义不同的处理规则。

initHandlerExceptionResolvers:当Handler处理出错后,会通过此将错误日志记录在log文件中,默认实现类是SimpleMappingExceptionResolver。

initRequestToViewNameTranslators:将指定的ViewName按照定义的RequestToViewNameTranslators替换成想要的格式。

initViewResolvers:用于将View解析成页面。

initFlashMapManager:用于生成FlashMap管理器。

通过查看DispatcherServlet(源码内容太多就不往上放了),DispatcherServlet继承自FrameworkServlet,而FrameworkServlet是继承自HttpServletBean的,HttpServletBean又继承了HttpServlet。这是因为DispatcherServlet本身就得是一个Servlet,且含有doGet()和doPost()方法,Web容器才可以调用它,所以它的顶级父类为含有这俩方法的HttpServlet。具体的Web请求,会经过FrameServlet的processRequest方法简单处理后,紧接着调用DispatcherServlet的doService方法,而在这个方法中封装了最终调用处理器的方法doDispatch。这也意味着,DispatcherServlet的最主要的核心功能由doService和doDispatch实现的。

DispatcherServlet类的方法大致可分为三种:

初始化相关处理类的方法。

响应Http请求的方法。

执行处理请求逻辑的方法。

核心方法 doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throwsException {

HttpServletRequest processedRequest=request;

HandlerExecutionChain mappedHandler= null;boolean multipartRequestParsed = false;

WebAsyncManager asyncManager=WebAsyncUtils.getAsyncManager(request);try{try{

ModelAndView mv= null;

Object dispatchException= null;try{

processedRequest= this.checkMultipart(request);

multipartRequestParsed= processedRequest !=request;

mappedHandler= this.getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {this.noHandlerFound(processedRequest, response);return;

}

HandlerAdapter ha= this.getHandlerAdapter(mappedHandler.getHandler());

String method=request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified =ha.getLastModified(request, mappedHandler.getHandler());if (this.logger.isDebugEnabled()) {this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " +lastModified);

}if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) &&isGet) {return;

}

}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;

}

mv=ha.handle(processedRequest, response, mappedHandler.getHandler());if(asyncManager.isConcurrentHandlingStarted()) {return;

}this.applyDefaultViewName(processedRequest, mv);

mappedHandler.applyPostHandle(processedRequest, response, mv);

}catch(Exception var20) {

dispatchException=var20;

}catch(Throwable var21) {

dispatchException= new NestedServletException("Handler dispatch failed", var21);

}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

}catch(Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);

}catch(Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));

}

}finally{if(asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {

mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

}

}else if(multipartRequestParsed) {this.cleanupMultipart(processedRequest);

}

}

}

DispatcherServlet处理该类请求的步骤:

在接收到请求后,通过几级Servlet类型的父类的处理,先调用doService在request中设置一些必需的参数。最终会调用DispatcherServlet的doDispatch方法。

在doDispatch方法中,首先检测request是否包含多媒体类型(如File文件上传),然后将检测后的request转换为processedRequest对象。之后检测processedRequest对象是否为原始request(如果是,即原来的request不包含多媒体信息),然后将boolean结果赋给multipartRequestParsed变量(若multipartRequestParsed为true,在最后会清除processedRequest对象中的多媒体信息)。

十分重要的一步,就是通过调用处理器映射器查找Handler。调用getHandler来获取相关的处理器对象。在getHandler方法中,利用处理器映射器HandlerMapping通过request来获取一个包含Handler处理器本身和其前后拦截器interceptor的处理器执行链HandlerExecutionChain对象。

通过HandlerExecutionChain对象获取具体的Handler处理器对象,此时使用getHandlerAdapter方法获取可以处理类型的处理器适配器HandlerAdapter对象。

调用HandlerAdapter对象的handle方法,将可能带有多媒体信息的processRequest对象,原始request对象,以及Handler处理器本身作为参数传入,handle方法会根据这些参数去执行开发者自己开发的Handler的相关

请求处理逻辑,并返回含有反馈信息和结果视图信息的ModelAndView对象。

获得ModelAndView对象后,会进行视图渲染,将model数据填充到request域。在processDispatchResult方法中会对ModelAndView对象进行处理。而在processDispatchResult方法中包含一个render方法,其参数为ModelAndView对象以及request和response对象。在render方法中,通过resolveViewName会获取到实际需要使用的视图View对象,这个对象的具体类型是由XXX决定的。然后就会执行具体的View对象的render方法来完成数据的显示过程。这里举一个视图类型的例子,他在render方法中具体执行了以下逻辑来绑定结果数据和视图:

protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest request) throwsException{//遍历model里面的数据,填充到request域

for (Map.Entryentry : model.entrySet()){

String modeName=entry.getKey();

Object modelValue=entry.getValue();if(modelValue != null){

request.setAttribute(modelName, modelValue);if(logger.isDebugEnabled()){

logger.debug("Added model object '"

+ modelName + "' of type [" +modelValue.getClass.getName()+"] to request in view with name '" + getBeanName() + "'");

}else{

request.removeAttribute(modelName);if(logger.isDebugEnabled()){

logger.debug("Remove modek object '" +modelName +

"' from request in view with name '" +getBeanName() + "'");

}

}

}

}

}

可以看到,在这里会把ModelAndView中model的数据遍历出来,分为key和value,并且将数据设置在request的attribute域中。之后加载页面时就可以使用标签在request域中获取返回参数了。

format,png

DispatcherServlet初始化时序图.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值