SpringMVC解析5-DispatcherServlet逻辑细节
MultipartContent类型的request处理
对于请求的处理,spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request.
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
else {
return this.multipartResolver.resolveMultipart(request);
}
}
// If not returned before: return original request.
return request;
}
每当我们上传文件的时候就是用的这段代码的源码。
根据request信息寻找对应的handler
我们知道dispatcher获取request后会将request交给handlerMapping处理,在Spring加载的过程中,Spring会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings中,按照常理推断,根据request提取对应的Handler,无非就是提取当前实例中的userController,但是userController为继承自AbstractController类型实例,与HandlerExecutionChain并无任何关联,那么这一步是如何封装的呢?
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
return getHandler(request);
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
在系统启动的时候,Spring会将所有的配置的或默认的handlerMapping类型的bean注册到this.handlerMappings变量中,所以这个函数的目的就是遍历所有的handlerMapping,并调用其getHandler方法进行封装处理。我们来看看其getHandler方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根据request获取对应的handler
/*
* 函数中会首先使用getHandlerInternal方法根据request信息获取对应的handler,以我们平时的应用为例
* 此方法就是根据URL找到匹配的Controller并返回,如果没有找到对应的Controller处理器那么程序会尝试
* 去查找配置中默认的处理器。
*/
Object handler = getHandlerInternal(request);
if (handler == null) {
//如果没有request的handler则使用默认的handler
handler = getDefaultHandler();
}
//如果没有默认的handler则无法继续处理,返回null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
/*
* 如果查找的handler为String类型的时候,那就意味着返回的是配置的bean名称,需要根据bean名称查找对应的bean
* 最后还要通过getHandlerExecutionChain方法对返回的Handler进行封装,以满足返回类型的匹配
*/
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
根据request查找对应的Handler,逻辑如下:
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//截取用于匹配的url有效路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//根据路径寻找handler
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
//如果请求的路径仅仅是“/”那么使用RootHandler进行处理
rawHandler = getRootHandler();
}
if (rawHandler == null) {
//无法找到handler则使用默认handler
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
//根据beanName寻找handler
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
//模板方法
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
//直接匹配情况的处理
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
//通配符匹配的处理
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
}
String bestPatternMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestPatternMatch = matchingPatterns.get(0);
}
if (bestPatternMatch != null) {
handler = this.handlerMap.get(bestPatternMatch);
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
根据URL获取对应Handler的匹配规则代码实现起来虽然很长,但是并不难理解,考虑了直接匹配与通配符两种情况。其中主要提及的是buildPathExposingHandler函数,它将Handler封装成了HandlerExecutionChain类型。
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
在函数中看到了通过将Handler以参数形式传入,并构建HandlerExecutionChain类型实例,加入了两个拦截器。链处理机制,是Spring中非常常用的处理方式,是AOP中的重要组成部分,可以方便地对目标对象进行扩展及拦截,这是非常优秀的设计。
加入拦截器到执行链
getHandlerExecutionChain函数最主要的目的是将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
chain.addInterceptors(getAdaptedInterceptors());
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
return chain;
}
没找到对应的Handler的错误处理
每一个请求都应该对应着一个Handler,因为每个请求都会在后台有相应的逻辑处理,而逻辑实现就是在handler中,所以一旦遇到没有找到handler的情况(正常情况下如果没有URL匹配的Handler,开发人员可以设置默认的Handler来处理请求,但是如果默认请求也未设置就会出现Handler为空的情况),就只能通过response向用户返回错误信息。
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
根据当前Handler寻找对应的HandlerAdapter
在WebApplicationContext初始化过程中讨论了HandlerAdapter的初始化,了解了默认情况下普通的Web请求会交给SimpleControllerHandlerAdapter处理,下面我们以SimpleControllerHandlerAdapter为例来分析获取适配器的逻辑。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
通过上面的函数,对于获取适配器的逻辑无非就是遍历所有适配器来选择合适的适配器并返回他,而某个适配器是否适用于当前的Handler逻辑被封装在具体的适配器中。进一步查看SimpleControllerHandlerAdapter中的supports方法。
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
SimpleControllerHandlerAdapter就是用于处理普通的Web请求的,而且对于SpringMVC来说,我们会把逻辑封装至Controller的子类中。例如我们扫描注解@Controller的时候,就是扫描了Controller的子类。
last-modified头处理
我们先了解一个概念:Last-Modified缓存机制。
(1)在客户端第一次输入URL时,服务器端会返回内容和状态码200,表示请求成功,同时会添加一个“Last-Modefied”的响应头,表示此文件在服务器上的最后更新时间,例如:“Last-Modified:Wed 14 Mar 2012 10:22:42 GMT”表示最后更新时间为(2012-03-14 10:22)。
(2)客户端第二次请此URL时,客户端会向雾浮起发送请求头“If-Modified-Since”,询问服务器该事件只有当前请求内容是否改变过,如果服务端内容没有变化,则自动返回HTTP304状态码(只要响应头,内容为空,这样就节省了网络宽带)。
Spring提供的对Last-Modified机制的支持,只需要实现LastModified接口如下实例:
public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified{
private long lastModified;
protected ModelAndView handleRequestInternal(HttpServletRequest req ,HttpServletResponse resp)throws Exception{
//点击后再次请求当前页面
resp.getWriter().write("<a href=''>this</a>");
return null;
}
public long getLastModified(HttpServletRequest request){
if(lastModified == 0L){
//第一次或逻辑有变化的时候,应该重新返回内容最新修改的时间戳
lastModified = System.currentTimeMillis();
}
return lastModified;
}
}
HelloWorldLastModifiedCacheController只需要实现LastModified接口的getLastModified方法,保证当内容发证变化时返回最新的修改时间即可。
HandlerInterceptor的处理
Servlet API定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对它进行前置和后置处理。
SpringMVC允许你通过处理拦截Web请求,进行前置处理和后置处理。处理拦截是在Spring的Web应用程序上下文中配置的,因此它们可以利用各种容器特性,并引用容器中声明的任何bean.处理拦截是针对特殊的处理程序映射进行注册的,因此它只拦截通过这些处理程序映射的请求。每个拦截器都必须实现HandlerInterceptor接口,它包含三个需要你实现的回调方法:preHandler(),postHandler()和afterCompletion()。第一个和第二个方法分别是处理程序请求之前和之后被调用。第二个方法还允许返回modelandview对象,因此可以在它里面操作模型属性。最后一个方法是处理完成之后被调用的。
逻辑处理
对于逻辑处理其实是通过适配器中转调用Handler并返回视图的,对应代码:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
同样,还是以引导示例为基础进行逻辑处理分析,之前分析过,对于普通的web请求,Spring默认使用SimpleControllerHandlerAdapter类进行处理,方法如下:
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
我们的逻辑是实现在handlerRequestInternal函数中,而不是handleRequest函数,所以我们进一步分析:
public abstract class AbstractController extends WebContentGenerator implements Controller {
private boolean synchronizeOnSession = false;
public final void setSynchronizeOnSession(boolean synchronizeOnSession) {
this.synchronizeOnSession = synchronizeOnSession;
}
public final boolean isSynchronizeOnSession() {
return this.synchronizeOnSession;
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
checkRequest(request);
prepareResponse(response);
//如果需要session内的同步执行
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
//调用用户的逻辑
return handleRequestInternal(request, response);
}
}
}
//调用用户的逻辑
return handleRequestInternal(request, response);
}
protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception; }
异常视图的处理
有时候系统运行过程中出现异常,我们并不希望就此中断用户的服务,而是至少告知客户当前系统在处理逻辑的过程中出现了异常,甚至告知他们什么原因导致的。Spring中的异常处理机制会帮我们完成这个工作。其实,这里Spring的主要逻辑就是将逻辑引导至HandlerExceptionResolver类的resolverException方法。
proteced ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
exMv.setViewName(getDefaultViewName(request));
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
根据视图跳转页面
无论是一个系统还是一个站点,最重要的工作都是与用户进行交互,用户操作系统后无论下发的命令成功与否都需要给用户一个反馈,以便于用户进行下一步的谱判断。所以在逻辑处理的最后一定会涉及一个页面跳转的问题。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale); View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
} // Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
解析视图名称
DispatcherServlet会根据ModelAndView选择合适的视图来进行渲染,而这以功能就是在resolveViewName函数中来完成的。
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}
我们以默认的org.Springframework.web.servlet.view.InternalResourceViewResolver为例来分析ViewResolver逻辑的解析过程,其中resolveViewName函数的实现是在其父类AbstractCachingViewResolver中完成的。
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
//不存在缓存的情况下直接创建视图
return createView(viewName, locale);
}
else {
//直接从缓存中抽取
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
在父类UrlBasedViewResolver中重写了createView函数。
protected View createView(String viewName, Locale locale) throws Exception { //如果当前解析器不支持当前解析器如viewName为空等情况
if (!canHandle(viewName, locale)) {
return null;
}
//处理前缀为redirect:xx的情况
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
//处理前缀为"forward:"的情况
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
//添加前缀及后缀
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
//contentType
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}
通读以上代码,我们发现对于InternalResourceViewResolver所提供的解析功能主要考虑到了几个方面的处理。
- 基于效率的考虑,提供了缓存的支持
- 提供了对redirect:xx和forward:xx前缀的支持
- 添加了前缀及后缀,并向View中加入了必须的属性设置
页面跳转
当通过viewName解析到对应的View后,就可以进一步地处理跳转逻辑了。
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, request, response);
}
对于ModelView的使用,可以将一些属性直接放入到其中,然后再页面上直接通过JSTL语法或者原始的request获取。这个一个很方便也很神奇的功能,但是实现却不复杂,无非就是把我们将要用到的属性放入到request中,以便在其他地方可以直接调用,而解析这个属性的工作就是在createMergedOutputModel函数中完成的。
protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
} // Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
} return mergedModel;
}
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
//将model中的数据以属性的方式设置到request中
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response);
//获取关于目标资源的requestDispatcher(通常是JSP页面)
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
exposeForwardRequestAttributes(requestToExpose);
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response);
}
}
SpringMVC解析5-DispatcherServlet逻辑细节的更多相关文章
- springmvc 解析xml数据
springmvc 解析xml数据 http://blog.csdn.net/zhi_jun/article/details/37925475
- SpringMVC解析4-DispatcherServlet逻辑脉络
HttpServlet提供了不同的服务方法,它们是doDelete(),doGet(),doOptions(),doPost(),doPut(),和doTrace(),它会根据不同的请求形式将程序引导 ...
- 【spring源码学习】springMVC之映射,拦截器解析,请求数据注入解析,DispatcherServlet执行过程
[一]springMVC之url和bean映射原理和源码解析 映射基本过程 (1)springMVC配置映射,需要在xml配置文件中配置<mvc:annotation-driven > ...
- SpringMVC 解析(二)DispatcherServlet
在我的关于Tomcat容器介绍的文章中,介绍了Tomcat容器的工作原理,我们知道Tomcat容器在收到请求之后,会把请求处理为Request/Response对象,交给Servlet实例处理.对于S ...
- SpringRMI解析3-RmiServiceExporter逻辑细节
在发布RMI服务的流程中,有几个步骤可能是我们比较关心的. 获取registry 由于底层的封装,获取Registry实例是非常简单的,只需要使用一个函数LocateRegistry.createRe ...
- SpringMVC解析3-DispatcherServlet组件初始化
在spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的 ...
- 1.SpringMVC设计理念与DispatcherServlet
SpringMVC作为Struts2之后异军突起的一个表现层框架,正越来越流行,相信javaee的开发者们就算没使用过SpringMVC,也应该对其略有耳闻.我试图通过对SpringMVC的设计思想和 ...
- SpringMVC解析1-使用示例
Spring MVC分离了控制器.模型对象.分派器以及处理程序对象的角色,这种分离让它们更容易进行定制.Spring的MVC是基于servlet功能实现的,通过实现Servlet接口的Dispatch ...
- Spring之SpringMVC前端控制器DispatcherServlet(源码)分析
1.DispatcherServlet作用说明 DispatcherServlet提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得 ...
随机推荐
- 【leetcode】Spiral Matrix(middle)
Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...
- 【python】SQLAlchemy
来源:廖雪峰 对比:[python]在python中调用mysql 注意连接数据库方式和数据操作方式! 今天发现了个处理数据库的好东西:SQLAlchemy 一般python处理mysql之类的数据库 ...
- MyEclipse/Eclipse中修改包的显示结构
操作如下:
- PHP安全编程:对输出要进行转义
为了区分数据是否已转义,我还是建议定义一个命名机制.对于输出到客户机的转义数据,我使用$html数组进行存储,该数据首先初始化成一个空数组,对所有已过滤和已转义数据进行保存. 1 <?php 2 ...
- 一个简单的代码计算行数demo编写
最近手头的项目基本上已经完结,历经了5个月的开发和迭代,各种的需求调整,想对自己的代码量进行一个客观的计算,于是抽了点时间写下了这个小demo,朋友们有需要的可以看看,很简单. 基本的思想就是:根目录 ...
- [Android Pro] Dangerous permissions and permission groups.
Permission Group Permissions CALENDAR READ_CALENDAR WRITE_CALENDAR CAMERA CAMERA CONTACTS READ_CONTA ...
- in addition to 和 except for
except for 除了...以外(与 except for 连用的整体词与 except for 所跟的词往往不是同类的,是指整体中除去 一个细节.) eg:Your composition is ...
- 点击按钮对两个div的隐藏与显示进行切换
HTML: <button type="button" id="showHidden">点击切换div的隐藏与显示</button> ...
- openURL的使用方法:
openURL的使用方法: view plaincopy toclipboardprint? [[UIApplication sharedApplication] openURL:[NS ...
- kvm 下运行的 WINWS7磁盘空间不足 增加磁盘 实战(这个有问题,还未解决)
创建一个新硬盘: [root@NB vhost]# qemu-img create -f qcow2 add_win_desk.img 5G Formatting encryption=off clu ...