使用过HttpServlet的都应该用过其doGet和doPost方法,接下来看看DispatcherServlet对这两个方法的实现(源码在DispatcherServlet的父类FrameworkServlet中):

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
} @Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
}

方法里又将逻辑交由processRequest(request, response)方法处理,跟进源码:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { // 记录当前时间,用于计算请求的处理时间
long startTime = System.currentTimeMillis();
Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
} if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
} publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

从源码可以看出在该方法中对请求进行处理,处理细节在doService方法中实现,同时在处理请求前后也做了准备及处理工作:

1. 提取LocaleContext及RequestAttributes两个属性保证可以在当前请求后还能恢复;

2. 根据当前的request创建对应的LocaleContext及RequestAttributes,并绑定到当前线程;

3. 委托给doService方法进一步处理;

4. 请求结束后恢复线程到原始状态;

5. 请求处理结束后发布事件通知。

跟进doService方法源码:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
} // Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
} // Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}

从上面源码可以看出doService方法里也是做了许多准备工作,可以看出Spring将localeResolvder、themeResolver等设置在request中,最后传入doDispatch方法,跟进源码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try {
ModelAndView mv = null;
Exception dispatchException = null; try {
// 如果request是MultipartContent类型的话则转为MultipartHttpServletRequest类型
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // 根据request寻找对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
// 找不到Handler则返回错误信息
noHandlerFound(processedRequest, response);
return;
} // 根据handler找对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 如果当前handler支持last-modified头处理
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
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;
} // 激活handler并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
根据request信息寻找对应的Handler

先来看看Spring中一个简单的映射处理器配置:

<bean id="simpleUrlMapping"
class="org.Springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/test.html">controller</prop>
</props>
</property>
</bean>

在Spring的加载中,会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings中,根据request提取对应的Handler,也就是提取当前实例的controller,这里的controller是继承自AbstractController类型实例,看看这步是如何封装的,跟进getHandler方法源码:

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会将映射类型的bean注册到this.handlerMappings变量中,此方法的目的就是遍历所有的HandlerMapping,并调用其getHandler方法进行封装处理,跟进SimpleUrlHandlerMapping的getHandler方法:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 根据request获取对应的handler
Object handler = getHandlerInternal(request);
if (handler == null) {
// 如果没有则使用默认的handler
handler = getDefaultHandler();
}
if (handler == null) {
return null;
} if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}

上面源码应该很清晰,根据request获取对应的Handler,如果没有的话则使用默认的,当查找到的controller为String类型时,就意味着返回的是配置的bean名称,需要根据bean名称查找对应的bean,最后通过getHandlerExecutionChain方法对返回的Handler进行封装,以满足返回类型的匹配。
接着跟进getHandlerInternal方法源码来看看怎样根据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) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// 根据beanName获取对应的bean
if (rawHandler instanceof String) {
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 {
// 直接匹配情况的处理
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);
}
// 通配符匹配的处理
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;
}

这里考虑了直接匹配和通配符两种情况,其中在buildPathExposingHandler方法里将Handler封装成了HandlerExecutionChain类型。看buildPathExposingHandler方法源码:

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类型实例,加入了两个拦截器,这里也是链式处理方式。

根据当前Handler寻找对应的HandlerAdapter

在默认情况下普通web请求会由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;
}
}
}

通过该方法可以看出对于获取适配器的逻辑就是遍历所有适配器来选择合适的并返回它,而某个适配器是否适用于当前的Handler逻辑被封装在具体的适配器中,看SimpleControllerHandlerAdapter中的supports方法,

public boolean supports(Object handler) {
return (handler instanceof Controller);
}

SimpleControllerHandlerAdapter就是用于处理普通web请求的,对于SpringMVC来说,一般是把逻辑封装到Controller的子类中。

继续返回到doDispatcher方法中的激活handler并返回视图的代码,
// 激活handler并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

对于普通的Web请求,Spring默认是使用SimpleControllerHandlerAdapter类进行处理的, 进入SimpleControllerHandlerAdapter类的handle方法如下:

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}

之前举例的controller的逻辑是写在handleRequestInternal方法中而不是handleRequest方法中的,看看该方法中的处理逻辑:

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception { // Delegate to WebContentGenerator for checking and preparing.
checkAndPrepare(request, response, this instanceof LastModified); // 如果要在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);
}
根据视图跳转页面

使用过SpringMVC的都知道请求经过控制器、适配器等处理后最后还要经过视图解析器的解析渲染跳转,跟进DispatcherServlet类的render方法:

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

跟进resolveViewName(viewName, locale)方法,源码如下:

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

当通过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, getRequestToExpose(request), response);
}

对于ModelView的使用,可以将一些属性放入其中,再在页面上通过JSTL等方式获取,解析这些属性的工作就是在上面源码中的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 { // 将model中的数据以属性方式设置到request中
exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any.
exposeHelpers(request); // Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, 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(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
} else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}

【Spring】DispatcherServlet源码分析的更多相关文章

  1. Spring Boot源码分析-启动过程

    Spring Boot作为目前最流行的Java开发框架,秉承"约定优于配置"原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目. 本文基于Spring ...

  2. 精尽Spring MVC源码分析 - 寻找遗失的 web.xml

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - WebApplicationContext 容器的初始化

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - 一个请求的旅行过程

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  10. 精尽Spring MVC源码分析 - RequestToViewNameTranslator 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. SpringMVC+ZTree实现树形菜单权限配置

    计划在开源项目里加入权限配置的功能,打算加入zTree实现树形结构. Team的Github开源项目链接:https://github.com/u014427391/jeeplatform 欢迎sta ...

  2. [.Net跨平台]部署DTCMS到Jexus遇到的问题及解决思路---部署

    上一篇我们环境已经准备完成,此时可以部署了,我们就以dtcms作为例子,http://bbs.dtcms.net/forum.php?mod=viewthread&tid=2420&e ...

  3. #ifdef #else #endif #if #ifndef 的用法

    预编译就是在对源文件进行处理之前(如在语法扫描和分析之前),先处理预处理部分,精简代码,然后再进行编译. 预处理命令有:#include 文件包含.#define 宏定义.以及要重点讲的#if.#if ...

  4. http中的get和post(二)

    博客园精华区有篇文章< GET 和 POST 有什么区别?及为什么网上的多数答案都是错的 >,文中和回复多是对以下两个问题进行了深究: 长度限制 Url 是否隐藏数据 在我看来这两者都不是 ...

  5. iOS知识点集合--更改(2)

    3.nsmutablearray *a 如果直接赋值 a = @[@"d",@""]; 这个时候a 是不可变的 字典也是如此 2.如果接口调用错误的话 打印re ...

  6. 快速自检电脑是否被黑客入侵过(Linux版)

    之前写了一篇快速自检电脑是否被黑客入侵过(Windows版), 这次就来写写Linux版本的. 前言 严谨地说, Linux只是一个内核, GNU Linux才算完整的操作系统, 但在本文里还是用通俗 ...

  7. 每周.NET前沿技术文章摘要(2017-05-10)

    汇总国内外.NET社区相关文章,覆盖.NET ,ASP.NET和Docker容器三个方面的内容: .NET Debugging .NET core with SOS everywhere 链接:htt ...

  8. PE格式第三讲扩展,VA,RVA,FA(RAW),模块地址的概念

    PE格式第三讲扩展,VA,RVA,FA的概念 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶VA概念 VA (vi ...

  9. nova创建虚拟机源码分析系列之五 nova源码分发实现

    前面讲了很多nova restful的功能,无非是为本篇博文分析做铺垫.本节说明nova创建虚拟机的请求发送到openstack之后,nova是如何处理该条URL的请求,分析到处理的类. nova对于 ...

  10. Qt数据库集成应用封装

    平时的大大小小的项目中,基本上都需要与数据库打交道,会遇到各种各样的应用场景,本人主要遇到四种场景1:数据库自动重连,例如mysql数据库中经常遇到服务器挂掉的情况,需要自动检测并重新连接数据库.2: ...