SpringMVC之请求处理流程

我们知道DispatcherServlet就是一个HttpServlet,而HttpServlet的请求就从doGet/doPost开始

DispatcherServlet本身没有实现doGet/doPost,而由他的父类FrameworkServlet实现,源码如下

FrameworkServlet.doGet/doPost

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
} /**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
}

processRequest

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { long startTime = System.currentTimeMillis();
Throwable failureCause = null; //previousLocaleContext获取和当前线程相关的LocaleContext,根据已有请求构造一个新的和当前线程相关的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request); //previousAttributes获取和当前线程绑定的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); //为已有请求构造新的ServletRequestAttributes,加入预绑定属性
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); //异步请求处理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //initContextHolders让新构造的RequestAttributes和ServletRequestAttributes和当前线程绑定,加入到ThreadLocal,完成绑定
initContextHolders(request, localeContext, requestAttributes); try {
//抽象方法doService由FrameworkServlet子类DispatcherServlet重写
doService(request, response);
}catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}finally {
//解除RequestAttributes,ServletRequestAttributes和当前线程的绑定
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
//注册监听事件ServletRequestHandledEvent,在调用上下文的时候产生Event
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

doService

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(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<>();//保存request域中的数据,存一份快照
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
} //设置web应用上下文
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()); //请求刷新时保存属性
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
//Flash attributes 在对请求的重定向生效之前被临时存储(通常是在session)中,并且在重定向之后被立即移除
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
//FlashMap 被用来管理 flash attributes 而 FlashMapManager 则被用来存储,获取和管理 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);//将快照覆盖回去
}
}
}
}

DispatcherServlet.doDispatch

这个方法就是处理请求的核心方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; //异步处理,webflux相关
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try {
ModelAndView mv = null;
Exception dispatchException = null; try {
//将request转换成multipartRequest,并检查是否解析成功(判断是否有文件上传)
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); //根据请求信息获取handler(包含了拦截器),组成HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
} //根据handler获取adapter---命名为ha
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
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;
}
//给视图模型设置viewName
applyDefaultViewName(processedRequest, mv);
//拦截器逻辑
mappedHandler.applyPostHandle(processedRequest, response, mv);
}catch (Exception ex) {
dispatchException = ex;
}catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//处理请求结果,使用了组件LocaleResolver, ViewResolver和ThemeResolver(view#render)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", 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);
}
}
}
}

DispatcherServlet.getHandler

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

这里的this.handlerMappings就是9大组件之一,在web容器启动时方法中初始化.

过滤器和拦截器

  • 过滤器拦截DispatcherServlet,属于web服务器层面
  • 拦截器可以拦截到具体方法,属于springmvc框架

HandlerExecutionChain对象包含处理器+拦截器链

public class HandlerExecutionChain {

   private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

	//真正的处理器
private final Object handler; //配置在springmvc配置文件的拦截器
@Nullable
private HandlerInterceptor[] interceptors; //所有拦截器
@Nullable
private List<HandlerInterceptor> interceptorList; }
AbstractHandlerMapping.getHandler
/**
* 返回请求处理的HandlerExecutionChain,从AbstractHandlerMapping中的adaptedInterceptors和mappedInterceptors属性中获取
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// getHandlerInternal()为抽象方法,具体需子类实现
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
} // 将请求处理器封装为HandlerExectionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); // 对跨域的处理
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
AbstractHandlerMapping.getHandlerExecutionChain
/**
* 构建handler处理器的HandlerExecutionChain,包括拦截器
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//将handler处理成HandlerExecutionChain
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
// 迭代添加拦截器,private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// 如果拦截器是MappedInterceptor,判断是否对该handler进行拦截,是的情况下添加
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else {
// HandlerInterceptor直接添加,即通过HandingMapping属性配置的拦截器
chain.addInterceptor(interceptor);
}
}
return chain;
} // new HandlerExecutionChain(handler),将handler变成HandlerExecutionChain
public HandlerExecutionChain(Object handler, @Nullable HandlerInterceptor... interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
//new一个interceptorList
this.interceptorList = new ArrayList<>();
//将HandlerInterceptor合并到interceptorList
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}
总结一下

就是把处理器+拦截器链组合成HandlerExecutionChain对象,看一下最普通的返回值

DispatcherServlet.getHandlerAdapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
//判断adapter是否适配handler,适配的话就返回,一般情况下handler是HandlerMethod类型
if (adapter.supports(handler)) {
//这里一般返回RequestMappingHandlerAdapter
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); } //this.handlerAdapters
private List<HandlerAdapter> handlerAdapters; //RequestMappingHandlerAdapter的父类AbstractHandlerMethodAdapter的代码实现
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

HandlerAdapter就是适配器对象

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter就是处理@RequestMapping注解的适配器

/**
* Extension of {@link AbstractHandlerMethodAdapter} that supports
* {@link RequestMapping @RequestMapping} annotated {@link HandlerMethod HandlerMethods}.
*
* <p>Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers},
* or alternatively, to re-configure all argument and return value types,
* use {@link #setArgumentResolvers} and {@link #setReturnValueHandlers}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1
* @see HandlerMethodArgumentResolver
* @see HandlerMethodReturnValueHandler
*/
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean { ... }

HandlerExecutionChain.applyPreHandle

执行拦截器逻辑

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
//执行拦截器preHandle方法
if (!interceptor.preHandle(request, response, this.handler)) {
//拦截后的方法afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}

HandlerAdapter.handle

使用适配器执行业务处理,返回视图模型

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { return handleInternal(request, response, (HandlerMethod) handler);
}
RequestMappingHandlerAdapter.handleInternal
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav;
checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
//核心方法
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
} if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
} return mav;
}

RequestMappingHandlerAdapter.invokeHandlerMethod

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//核心方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
} return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}

invocableMethod.invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//这里就在处理方法,得到返回值
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest); if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
} mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
invokeForRequest();

这个方法就是在处理@RequestMapper注解的方法,并通过反射得到返回值

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取方法参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//利用反射执行方法,得到返回值
return doInvoke(args);
}

doInvoke(Object... args)

@Nullable
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
//这里getBridgedMethod()就是Method,得到Method然后反射执行方法,得到返回值
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(formatInvokeError(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}

这里得到返回值后,springmvc会进行一系列处理,最后用IO write出去

HandlerExecutionChain.applyPostHandle

执行拦截器中postHandle方法

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception { HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}

processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception { boolean errorView = false;
//异常处理
if (exception != null) {
//ModelAndViewDefiningException类型,会携带对应的ModelAndView
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//由对应的处理器handler进行异常处理,返回ModelAndView。其中使用了HandlerExceptionResolver。
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
} // Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
} if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
} if (mappedHandler != null) {
//这里执行拦截器的afterCompletion方法
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
mappedHandler.triggerAfterCompletion
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception { HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}

HandlerMapping

public interface HandlerMapping {
// 返回请求的一个处理程序handler和拦截器interceptors
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

HandlerMapping作用是将请求映射到处理程序,以及预处理和处理后的拦截器列表,映射是基于一些标准的,其中的细节因不同的实现而不相同。这是官方文档上一段描述,该接口只有一个方法getHandler(request),返回一个HandlerExecutionChain对象

如果配置了这个标签,Spring MVC会默认添加RequestMappingHandlerMapping和RequestMappingHandlerAdapter

<mvc:annotation-driven />

Spring MVC会加载在当前系统中所有实现了HandlerMapping接口的bean,再进行按优先级排序。

  • SimpleUrlHandlerMapping 支持映射bean实例和映射bean名称,需要手工维护urlmap,通过key指定访问路径,

  • BeanNameUrlHandlerMapping 支持映射bean的name属性值,扫描以”/“开头的beanname,通过id指定访问路径

  • RequestMappingHandlerMapping 支持@Controller和@RequestMapping注解,通过注解定义访问路径

AbstractHandlerMapping

模板类

protected void initApplicationContext() throws BeansException {
// 提供给子类去重写的,不过Spring并未去实现,提供扩展
extendInterceptors(this.interceptors);
// 加载拦截器
detectMappedInterceptors(this.adaptedInterceptors);
// 归并拦截器
initInterceptors();
} /**
* 空实现
*/
protected void extendInterceptors(List<Object> interceptors) {
} /**
* 从上下文中加载MappedInterceptor类型的拦截器,比如我们在配置文件中使用
* <mvc:interceptors></mvc:interceptors>
* 标签配置的拦截器
*/
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
} /**
* 合并拦截器,即将<mvc:interceptors></mvc:interceptors>中的拦截器与HandlerMapping中通过属性interceptors设置的拦截器进行合并
*/
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
// 适配后加入adaptedInterceptors
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
} /**
* 适配HandlerInterceptor和WebRequestInterceptor
*/
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
if (interceptor instanceof HandlerInterceptor) {
return (HandlerInterceptor) interceptor;
} else if (interceptor instanceof WebRequestInterceptor) {
return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
} else {
throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
}
} /**
* 返回请求处理的HandlerExecutionChain,从AbstractHandlerMapping中的adaptedInterceptors和mappedInterceptors属性中获取
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// getHandlerInternal()为抽象方法,具体需子类实现
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
} // 将请求处理器封装为HandlerExectionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
// 对跨域的处理
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
} /**
* 钩子函数,需子类实现
*/
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception; /**
* 构建handler处理器的HandlerExecutionChain,包括拦截器
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
// 迭代添加拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// 如果拦截器是MappedInterceptor,判断是否对该handler进行拦截,是的情况下添加
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
} else { // HandlerInterceptor直接添加,即通过HandingMapping属性配置的拦截器
chain.addInterceptor(interceptor);
}
}
return chain;
}

RequestMappingHandlerMapping

处理注解@RequestMapping及@Controller

  • 实现InitializingBean接口,增加了bean初始化的能力,也就是说在bean初始化时可以做一些控制
  • 实现EmbeddedValueResolverAware接口,即增加了读取属性文件的能力

继承自AbstractHandlerMethodMapping

//RequestMappingHandlerMapping
@Override
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager()); super.afterPropertiesSet();
} //AbstractHandlerMethodMapping
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
//获取上下文中所有bean的name,不包含父容器
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
//日志记录HandlerMethods的总数量
handlerMethodsInitialized(getHandlerMethods());
} protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
//根据name找出bean的类型
beanType = obtainApplicationContext().getType(beanName);
} catch (Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
//处理Controller和RequestMapping
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
} //RequestMappingHandlerMapping
@Override
protected boolean isHandler(Class<?> beanType) {
//获取@Controller和@RequestMapping
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
} //整个controller类的解析过程
protected void detectHandlerMethods(Object handler) {
//根据name找出bean的类型
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) {
//获取真实的controller,如果是代理类获取父类
Class<?> userType = ClassUtils.getUserClass(handlerType);
//对真实的controller所有的方法进行解析和处理 key为方法对象,T为注解封装后的对象RequestMappingInfo
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 调用子类RequestMappingHandlerMapping的getMappingForMethod方法进行处理,即根据RequestMapping注解信息创建匹配条件RequestMappingInfo对象
return getMappingForMethod(method, userType);
}catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
//找出controller中可外部调用的方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//注册处理方法
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
} //requestMapping封装成RequestMappingInfo对象
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);//解析方法上的requestMapping
if (info != null) {
//解析方法所在类上的requestMapping
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);//合并类和方法上的路径,比如Controller类上有@RequestMapping("/demo"),方法的@RequestMapping("/demo1"),结果为"/demo/demo1"
}
String prefix = getPathPrefix(handlerType);//合并前缀
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
} private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
//找到方法上的RequestMapping注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
//获取自定义的类型条件(自定义的RequestMapping注解)
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
} protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) { //获取RequestMapping注解的属性,封装成RequestMappingInfo对象
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
} //再次封装成对应的对象 面向对象编程 每一个属性都存在多个值得情况需要排重封装
@Override
public RequestMappingInfo build() {
ContentNegotiationManager manager = this.options.getContentNegotiationManager(); PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions()); return new RequestMappingInfo(this.mappingName, patternsCondition,
new RequestMethodsRequestCondition(this.methods),
new ParamsRequestCondition(this.params),
new HeadersRequestCondition(this.headers),
new ConsumesRequestCondition(this.consumes, this.headers),
new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition);
}

registerHandlerMethod

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
//mapping是RequestMappingInfo对象 handler是controller类的beanName method为接口方法
public void register(T mapping, Object handler, Method method) {
...
this.readWriteLock.writeLock().lock();
try {
//beanName和method封装成HandlerMethod对象
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//验证RequestMappingInfo是否有对应不同的method,有则抛出异常
validateMethodMapping(handlerMethod, mapping);
//RequestMappingInfo和handlerMethod绑定
this.mappingLookup.put(mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping);//可以配置多个url
for (String url : directUrls) {
//url和RequestMappingInfo绑定 可以根据url找到RequestMappingInfo,再找到handlerMethod
this.urlLookup.add(url, mapping);
} String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);//方法名和Method绑定
} CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
//将RequestMappingInfo url handlerMethod绑定到MappingRegistration对象 放入map
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}

HandlerAdapters

Spring MVC为我们提供了多种处理用户的处理器(Handler),Spring实现的处理器类型有Servlet、Controller、HttpRequestHandler以及注解类型的处理器,即我们可以通过实现这些接口或者注解我们的类来使用这些处理器,那么针对不同类型的处理器,如何将用户请求转发到相应类型的处理器方法中的呢,这就需求Spring MVC的处理器适配器来完成适配操作,这就是处理器适配器要完成的工作。

  • SimpleServletHandlerAdapter 适配Servlet处理器

  • HttpRerquestHandlerAdapter 适配HttpRequestHandler处理器

  • RequestMappingHandlerAdapter 适配注解处理器

  • SimpleControllerHandlerAdapter 适配Controller处理器

Spring MVC默认使用的处理器适配器为:HttpRequestHandlerAdapter、SimpleServletHandlerAdapter、RequestMappingHandlerAdapter三种。

RequestMappingHandlerAdapter

通过继承抽象类AbstractHandlerMethodAdapter实现了HandlerAdapter接口

请求适配给@RequestMapping类型的Handler处理。

采用反射机制调用url请求对应的Controller中的方法(这其中还包括参数处理),返回执行结果值,完成HandlerAdapter的使命

getLastModified直接返回-1

@Override
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
return -1;
} //通过父类调用
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav;
checkRequest(request); // 判断当前是否需要支持在同一个session中只能线性地处理请求
if (this.synchronizeOnSession) {
// 获取当前请求的session对象
HttpSession session = request.getSession(false);
if (session != null) {
// 为当前session生成一个唯一的可以用于锁定的key
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// 对HandlerMethod进行参数等的适配处理,并调用目标handler
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// 如果当前不存在session,则直接对HandlerMethod进行适配
mav = invokeHandlerMethod(request, response, handlerMethod);
}
} else {
// 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配
mav = invokeHandlerMethod(request, response, handlerMethod);
} // 判断当前请求头中是否包含Cache-Control请求头,如果不包含,则对当前response进行处理,为其设置过期时间
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
// 如果当前SessionAttribute中存在配置的attributes,则为其设置过期时间。
// 这里SessionAttribute主要是通过@SessionAttribute注解生成的
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
// 如果当前不存在SessionAttributes,则判断当前是否存在Cache-Control设置,
// 如果存在,则按照该设置进行response处理,如果不存在,则设置response中的
// Cache的过期时间为-1,即立即失效
prepareResponse(response);
}
}
return mav;
} //核心处理流程
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中配置的InitBinder,用于进行参数的绑定
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // 获取容器中全局配置的ModelAttribute和当前HandlerMethod所对应的Controller
// 中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象,该对象用于对当前request的整体调用流程进行了封装
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) {
// 设置当前容器中配置的所有ArgumentResolver
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
} if (this.returnValueHandlers != null) {
// 设置当前容器中配置的所有ReturnValueHandler
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
} // 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中
invocableMethod.setDataBinderFactory(binderFactory); // 设置ParameterNameDiscoverer,该对象将按照一定的规则获取当前参数的名称
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); // 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法,
// 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); // 获取当前的AsyncWebRequest,这里AsyncWebRequest的主要作用是用于判断目标
// handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的一种,
// 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中
// 封装的业务逻辑放到一个线程池中进行调用,待该调用有返回结果之后再返回到response中。
// 这种处理的优点在于用于请求分发的线程能够解放出来,从而处理更多的请求,只有待目标任务
// 完成之后才会回来将该异步任务的结果返回。
AsyncWebRequest asyncWebRequest = WebAsyncUtils
.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout); // 封装异步任务的线程池,request和interceptors到WebAsyncManager中
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); // 这里就是用于判断当前请求是否有异步任务结果的,如果存在,则对异步任务结果进行封装
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer)
asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
} // 封装异步任务的处理结果,虽然封装的是一个HandlerMethod,但只是Spring简单的封装
// 的一个Callable对象,该对象中直接将调用结果返回了。这样封装的目的在于能够统一的
// 进行右面的ServletInvocableHandlerMethod.invokeAndHandle()方法的调用
invocableMethod = invocableMethod.wrapConcurrentResult(result);
} // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
} // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向,
// 还会判断是否需要将FlashAttributes封装到新的请求中
return getModelAndView(mavContainer, modelFactory, webRequest);
} finally {
// 调用request destruction callbacks和对SessionAttributes进行处理
webRequest.requestCompleted();
}
}
  • 获取当前容器中使用@InitBinder注解注册的属性转换器;
  • 获取当前容器中使用@ModelAttribute标注但没有使用@RequestMapping标注的方法,并且在调用目标方法之前调用这些方法;
  • 判断目标handler返回值是否使用了WebAsyncTask或DefferredResult封装,如果封装了,则按照异步任务的方式进行执行;
  • 处理请求参数,调用目标方法和处理返回值。

总结一下

  • 所有请求都会经过DispatcherServlet.doDispatch处理
  • 将请求的内容和拦截器链处理成HandlerExecutionChain,一般情况下handler是HandlerMethod类型的,
  • 寻找处理器对应的合适的适配器,一般情况下就是RequestMappingHandlerAdapter,用于处理@RequestMapping注解的适配器
  • 执行拦截器链的preHandle方法
  • 使用找到的适配器利用反射去执行方法,获取返回值,并响应
  • 执行拦截器链的postHandle方法
  • 执行拦截器链的afterCompletion方法

执行结果如下

/testJson----------------------preHandle
testJson
/testJson----------------------postHandle
/testJson----------------------afterCompletion

04-SpringMVC之请求处理流程的更多相关文章

  1. SpringMVC的请求处理流程

  2. springmvc源码分析系列-请求处理流程

    接上一篇-springmvc源码分析开头片 上一节主要说了一下springmvc与struts2的作为MVC中的C(controller)控制层的一些区别及两者在作为控制层方面的一些优缺点.今天就结合 ...

  3. Asp.Net构架(Http请求处理流程) - Part.1

    引言 我查阅过不少Asp.Net的书籍,发现大多数作者都是站在一个比较高的层次上讲解Asp.Net.他们耐心.细致地告诉你如何一步步拖放控件.设置控件属性.编写CodeBehind代码,以实现某个特定 ...

  4. Spring MVC源码(二) ----- DispatcherServlet 请求处理流程 面试必问

    前端控制器 前端控制器,即所谓的Front Controller,体现的是设计模式中的前端控制器模式.前端控制器处理所有从用户过来的请求.所有用户的请求都要通过前端控制器.SpringMVC框架和其他 ...

  5. 从源码角度了解SpringMVC的执行流程

    目录 从源码角度了解SpringMVC的执行流程 SpringMVC介绍 源码分析思路 源码解读 几个关键接口和类 前端控制器 DispatcherServlet 结语 从源码角度了解SpringMV ...

  6. Webflux请求处理流程

    spring mvc处理流程 在了解SpringMvc的请求流程源码之后,理解WebFlux就容易的多,毕竟WebFlux处理流程是模仿Servlet另起炉灶的. 下面是spring mvc的请求处理 ...

  7. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  8. ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程

    好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人>: --> 开篇:上一篇 ...

  9. SpringMVC处理请求流程

    SpringMVC核心处理流程: 1.DispatcherServlet前端控制器接收发过来的请求,交给HandlerMapping处理器映射器 2.HandlerMapping处理器映射器,根据请求 ...

随机推荐

  1. X000101

    P3879 [TJOI2010]阅读理解 考虑用 Trie 解决 #include<stdio.h> #include<bitset> #include<string.h ...

  2. DP 专练

    A - 跳蚤电话 观察性质,可以发现每次连边的点一定是有祖先关系的,可以直接挂上去一个,也可以是在中间边上插入一个点. 所以我很自然的想到去计算树上的点的加入顺序,因为一但加入顺序确定,每一次的操作也 ...

  3. 使用ajax上传文件

    1. XMLHttpRequest(原生ajax) [](javascript:void(0) <input class="file" type="file&quo ...

  4. NSURL组成部分详解

    手思中有这么一段代码,初看下,让人摸不着头脑 //功能:UIWebView响应长按事件 -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithR ...

  5. ImageMagick转换图片格式

    /usr/bin/convert data/manager/tongji/Html/WebData/images/code0/xingfumima0_1000_0.jpg -colorspace cm ...

  6. java_JDBC,连接数据库方式,RestSet结果集,Statement,PreparedStatement,事务,批处理,数据库连接池(c3p0和Druid)、Apache-DBUtils、

    一.JDBC的概述 1.JDBC为访问不同的数据薛是供了统一的接口,为使用者屏蔽了细节问题.2. Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作 ...

  7. Linux vi 命令 – 文本编辑器

    vi命令是linux系统字符界面下的最常用的文本编辑器. vi编辑器是所有linux的标准编辑器,用于编辑任何ASCⅡ文本,对于编辑源程序尤其有用.iv编辑器功能非常强大,可以对文本进行创建,查找,替 ...

  8. NVDA、争渡读屏语音开放API接口

    什么是读屏软件? 读屏软件是一种专为视力障碍人士设计的,能够辅助视障人士操作计算机的工具,它可以将屏幕上显示的内容用语音朗读出来,这样视障人士就可以正常使用电脑了. 知名的屏幕阅读软件国内有争渡读屏. ...

  9. nginx负载均衡中利用redis解决session一致性问题

    关于session一致性的现象及原因不是本小作文的重点,可以另行找杜丽娘O(∩_∩)O哈哈~重点是利用redis集中存储共享session的实际操作. 一.业务场景:nginx/tomcat/redi ...

  10. SpringBoot外部配置属性注入

    一.命令行参数配置 Spring Boot可以是基于jar包运行的,打成jar包的程序可以直接通过下面命令运行: java -jar xx.jar 那么就可以通过命令行改变相关配置参数.例如默认tom ...