DispatcherServlet inheritance structure
HttpServlet .java << HttpServletBean.java << FrameworkServlet.java << DispatcherServlet.java
When the request comes in, it is processed by doGet/doPost
FrameworkServlet.java
.doGet(request,response)/doPost(request,response)
.processRequest(request,response)
Abstract doService(Request, Response) subclass implementation
DispatcherServlet.
.doService(request,response)
**.doDispatch(Request, Response)** core method, which handles the subsequent logic of the request
The general flow for SpringMVC to handle requests
DispatcherServlet. DoDispatch () method of the core steps:
- Call getHandler to get the HandlerExecutionChain (Handler + interceptor) that can handle the current request
- GetHandlerAdapter (mappedHandler.gethandler ()) gets the adapter capable of executing the handler
- Perform front intercept mappedHandler. ApplyPreHandle (), positive sequence perform interceptor preHandle () method
- The adapter calls Handler to perform ha.handle(always returns a ModelAndView object)
- ApplyDefaultViewName () is called to process the resulting view object
- Perform rear intercept mappedHandler. ApplyPostHandle ((), flashback perform interceptor postHandle () method
- Call the processDispatchResult() method to complete the view jump
- The interceptor’s afterCompletion() method is eventually executed
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// Check if it is a file upload request
processedRequest = checkMultipart(request);
// Omit some code
/ /...
/* 1. Get the Controller that handles the current request. Instead of returning the Controller directly, return the HandlerExecutionChain request Handler object that encapsulates Handler and Inteceptor */
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// If handler is empty, 404 is returned
noHandlerFound(processedRequest, response);
return;
}
// 2. Get the HandlerAdapter that processes the request
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// Omit some code
/ /...
// 3. Execute the front interceptor
if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
}
// 4. The actual processor processes the request and returns the result View object (ModelAndView)
mv = ha.handle(processedRequest, response,mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 5. Result view object processing
applyDefaultViewName(processedRequest, mv);
// 6. Execute the rear interceptor
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);
}
// 7. Jump to page and render view
processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
}catch (Exception ex) {
// The afterCompletion method of the HandlerInterceptor will eventually be called
triggerAfterCompletion(processedRequest, response, mappedHandler,ex);
}catch (Throwable err) {
// The afterCompletion method of the HandlerInterceptor will eventually be called
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); }}}}Copy the code
GetHandler method resolution
- Traverse two hanlderMappings: BeanNameUrlHandlerMapping RqeustMappingHandlerMapping
- Get the corresponding handler from the request link
getHandlerAdapter
- Walk through the three HandlerAdapters: HttpRequestHandlerAdapter SimpleControllerHandlerAdapter, RequestMappingHandlerAdapter whether implementation types implement corresponding HttpRequest (inheritance) / Controller (inherited) / @requestMapping
Nine components
-
MultipartResolver MultipartResolver
-
LocaleResolver Internationalization parser
-
ThemeResolver Topic resolver
-
List handlerMappings Processor mapper component
-
List handlerAdapters Processor adapter components
-
The List handlerExceptionResolvers parser component
-
RequestToViewNameTranslator viewNameTranslator default view name converter component
-
FlashMapManager (Redirection properties)
-
List viewResolvers viewResolvers
The nine components define interfaces, which define specifications. Concrete subclasses are used to implement concrete business logic
Custom implementation MVC
Custom MVC implements the front-end request processing flow.
The annotations involved are: @controller, @requestMapping, and @interceptor
@controller indicates a request handler and is added to the IOC container
@requestMapping Specifies the requested mapping path
The @interceptor specifies a class as an Interceptor that will be added to the IOC container
Where the @Controller, @interceptor annotation class, will be added to the IOC container, implementation in ioc-AOP project judgment processing added to the container. See the custom implementation IOC-AOP project for details.
The @requestmapping annotation is used when initializing the Servlet to identify the relevant RequestMapping path, and is used to determine which class and function the front-end request path needs to execute
Implementation steps
- Define class: DispatcherServlet, inheriting HttpServlet
- Rewrite the init (ServletConfig config)
- Get the context and check if it has been initialized
- Context is not initialized. Create a new one. Already initialized, we use the context
- Call the refresh method of the Context to initialize the configuration of the MVN (that is, perform the ioc initialization process)
- Call onRefresh method to initialize MVC (including all processors, interceptors)
- The first time a request is received, the initialization method init() is executed
- Perform the requested
- GetHandlerMapping () to get the processor mapper
- GetRequestArgs (), gets an array of all request parameters
- DoHandler (), the execution handler
DispatcherServlet
The logic for the context of the WebApplicationContext is implemented in the project for custom IOC-AOP. Since this is the process for implementing MVC, the implementation details of WebApplicationContext are not covered here.
public class DispatcherServlet extends HttpServlet {
WebApplicationContext context;
/**
* 处理器映射器
*/
List<HandlerMapping> handlerMappings = new ArrayList<>();
/** * interceptor mapper */
List<HandlerInterceptorMapping> handlerInterceptorMappings = new ArrayList<>();
/** * URI interceptor cache * * Key is the uri of the request * value is the chain of interceptors containing the execution order of multiple interceptors */
Map<String, HandlerInterceptorChain> handlerInterceptorCache = new HashMap<>();
Class[] servletInnerParamClasses = new Class[]{
HttpSession.class,
HttpServletRequest.class,
HttpServletResponse.class
};
/** * initialize servlet * <p> * 1. Obtain the context and check whether it has been initialized * 2. Create a new one if the context is not initialized. Context * 3. Call the context refresh method to initialize the configuration of the MVN (that is, perform the ioc initialization process) * 4. Call the onRefresh method to initialize MVC (including all processors, interceptors) * *@param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
ServletContext servletContext = config.getServletContext();
try {
Object context = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
// Get the springMVC configuration path
String configLocation = config.getInitParameter(ContextLoader.CONFIG_LOCATION_PARAM);
if (null == configLocation || configLocation.length() == 0) {
configLocation = "applicationContext.xml";
}
if (null == context) {
this.context = new XmlWebApplicationContext(servletContext, configLocation);
} else {
this.context = (XmlWebApplicationContext) context;
this.context.setConfigLocation(configLocation);
}
// Start a refresh, which initializes all bean configuration information
this.context.refresh();
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
this.onRefresh();
} catch(Exception e) { e.printStackTrace(); }}@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
executePost(req, resp);
}
/** * Execute the request * 1. Get the processor mapper * 2. Get all the request parameter arrays * 3. Execute processor * *@param request
* @param response
*/
private void executePost(HttpServletRequest request, HttpServletResponse response) {
// Query the corresponding handler based on the requested URL
HandlerMapping handlerMapping = getHandlerMapping(request);
if (null == handlerMapping) {
response.setStatus(404);
return;
}
try {
// Get the request parameters
Object[] args = getRequestArgs(request, response, handlerMapping);
/ / execution
this.doHandler(request, response, handlerMapping, args);
} catch (IllegalAccessException e) {
e.printStackTrace();
response.setStatus(400);
} catch (InvocationTargetException e) {
response.setStatus(400); e.printStackTrace(); }}/** * get all parameters of the request **@param request
* @param response
* @param handlerMapping
* @return* /
private Object[] getRequestArgs(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping) {
Map<String, Integer> paramsIndexMapping = handlerMapping.getParamsIndexMapping();
/* Iterates over the parameters of the request, recording the parameter values of the same type and name to the corresponding subscript store */
Object[] args = new Object[paramsIndexMapping.size()];
Map<String, String[]> requestParameterMap = request.getParameterMap();
String paramStr = null;
for (Map.Entry<String, String[]> param : requestParameterMap.entrySet()) {
String key = param.getKey();
if(! paramsIndexMapping.containsKey(key)) {continue;
}
String[] value = param.getValue();
if (value.length > 1) {
// Multiple values of the same type, use, concatenate. Name = 1 & name = 2 - > 1, 2
StringJoiner stringJoiner = null;
for (String s : value) {
if (null == stringJoiner) {
stringJoiner = new StringJoiner(s);
} else {
stringJoiner.add(s);
}
}
paramStr = stringJoiner.toString();
if(! paramsIndexMapping.containsKey(key)) {continue; }}else {
paramStr = value[0];
}
Integer index = paramsIndexMapping.get(key);
args[index] = paramStr;
}
/* Handle three special arguments: HttpServletRequest, HttpServletResponse, and HttpSession */
for (Class servletInnerParamClass : servletInnerParamClasses) {
if(! paramsIndexMapping.containsKey(servletInnerParamClass.getSimpleName())) {continue;
}
if (servletInnerParamClass == HttpServletRequest.class) {
args[paramsIndexMapping.get(servletInnerParamClass.getSimpleName())] = request;
} else if (servletInnerParamClass == HttpServletResponse.class) {
args[paramsIndexMapping.get(servletInnerParamClass.getSimpleName())] = response;
} else if(servletInnerParamClass == HttpSession.class) { HttpSession session = request.getSession(); args[paramsIndexMapping.get(servletInnerParamClass.getSimpleName())] = session; }}return args;
}
/** * Formally executes the request * 1. Obtains the interceptor first * 2. Executes the preintercept logic * 3. Execute handler * 4. Execute post-intercept logic * *@param request
* @param response
* @param handlerMapping
* @param args
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private void doHandler(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping, Object[] args) throws InvocationTargetException, IllegalAccessException {
/* Get interceptor chain */
HandlerInterceptorChain interceptorChain = getInterceptorChain(request, response, handlerMapping);
Method method = handlerMapping.getMethod();
Object handler = handlerMapping.getHandler();
/*
执行前置拦截
*/
if (null! = interceptorChain && ! interceptorChain.doPreHandle(request, response, handlerMapping.getHandler(), method)) {return;
}
/* Execute the controller logic */
method.invoke(handler, args);
/* Perform a post-intercept */
if (null != interceptorChain) {
interceptorChain.doPostHandle(request, response, handlerMapping.getHandler(), method);
}
}
/** * get interceptor chain * 1. Get interceptor chain from cache * 2. Add to uri interceptor cache * 4. Return interceptor chain * *@param request
* @param response
* @param handlerMapping
* @return* /
private HandlerInterceptorChain getInterceptorChain(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping) {
String requestURI = request.getRequestURI();
// Get the URI interceptor chain from the cache
if (handlerInterceptorCache.containsKey(requestURI)) {
return handlerInterceptorCache.get(requestURI);
}
List<HandlerInterceptor> matchInterceptList = new ArrayList<>();
// If there is no interceptor chain in the cache, it calculates the interceptors that match
for (HandlerInterceptorMapping interceptorMapping : handlerInterceptorMappings) {
String[] interceptUris = interceptorMapping.getInterceptUri();
String[] excludeUris = interceptorMapping.getExcludeUri();
// Determine whether interception is required
for (String uri : interceptUris) {
boolean intercept = true;
// Check whether the interception URI is in the configuration
Pattern pattern = Pattern.compile(uri);
Matcher matcher = pattern.matcher(requestURI);
if(! matcher.matches()) {continue;
}
// Determine if this URI is excluded
for (String excludeUri : excludeUris) {
Pattern excludePattern = Pattern.compile(excludeUri);
Matcher excludeMatcher = excludePattern.matcher(requestURI);
if(! excludeMatcher.matches()) {continue;
}
intercept = false;
}
if (intercept) {
matchInterceptList.add(interceptorMapping.getInterceptor());
}
}
}
HandlerInterceptorChain chain = null;
if(! matchInterceptList.isEmpty()) {Key = uri, value = HandlerInterceptorChain
chain = new HandlerInterceptorChain(matchInterceptList);
}
// Add cache
handlerInterceptorCache.put(requestURI, chain);
return chain;
}
/** * get processor mapper **@param request
* @return* /
private HandlerMapping getHandlerMapping(HttpServletRequest request) {
if (handlerMappings.isEmpty()) {
return null;
}
String requestURI = request.getRequestURI();
// Iterate through all handlerMapping
// Match handlerMapping with the same URL
for (HandlerMapping handlerMapping : handlerMappings) {
Pattern urlPattern = handlerMapping.getPattern();
// Regex matches
Matcher matcher = urlPattern.matcher(requestURI);
if(! matcher.matches()) {continue;
}
return handlerMapping;
}
return null;
}
private void onRefresh(a) {
initHandlerMapping();
initHandlerInterceptions();
}
/** * Initializes all interceptors */
private void initHandlerInterceptions(a) {
ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory();
if (beanFactory instanceofDefaultListableBeanFactory) { DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory; List<String> beanDefinitionNames = listableBeanFactory.getBeanDefinitionNames();for (String beanDefinitionName : beanDefinitionNames) {
Object bean = this.context.getBean(beanDefinitionName); Class<? > beanClass = bean.getClass();// Not adding an Interceptor annotation or implementing a HandlerInterceptor interface is not an Interceptor
if(! beanClass.isAnnotationPresent(Interceptor.class) || ! (beaninstanceof HandlerInterceptor)) {
continue;
}
Interceptor interceptor = beanClass.getAnnotation(Interceptor.class);
String[] interceptUri = interceptor.interceptUri();
String[] excludeUri = interceptor.excludeUri();
handlerInterceptorMappings.add(newHandlerInterceptorMapping(interceptUri, excludeUri, (HandlerInterceptor) bean)); }}}/** * Build the HandlerMapping handler to map the url to the method */
private void initHandlerMapping(a) {
ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory();
if (beanFactory instanceof DefaultListableBeanFactory) {
DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory;
List<String> beanDefinitionNames = listableBeanFactory.getBeanDefinitionNames();
processHandlerMappings(beanDefinitionNames);
}
}
private void processHandlerMappings(List<String> beanDefinitionNames) {
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = this.context.getBean(beanDefinitionName); processHandlerMapping(bean); }}/** * handles the mapping between bean url and method **@param bean
*/
private void processHandlerMapping(Object bean) {
// Check if there is an @controller annotationClass<? > beanClass = bean.getClass();if(! beanClass.isAnnotationPresent(Controller.class)) {return;
}
// Get RequestMapping annotation information on the bean
String baseUrl = "";
if (beanClass.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = beanClass.getAnnotation(RequestMapping.class);
baseUrl = standardizedUrl(requestMapping.value());
}
// Iterate through all the bean methods to see if there is a RequestMapping annotation.
Method[] methods = beanClass.getMethods();
for (Method method : methods) {
if(! method.isAnnotationPresent(RequestMapping.class)) {// No comments
continue;
}
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String methodUrl = standardizedUrl(requestMapping.value());
String url = baseUrl + methodUrl;
// Create a mapping container
HandlerMapping handlerMapping = new HandlerMapping(bean, method, Pattern.compile(url));
// Calculate method parameter position
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; Class<? > parameterType = parameter.getType();// Check whether the parameter type is inside the servlet
boolean isInnerParamType = false;
for (Class servletInnerParamClass : servletInnerParamClasses) {
if (parameterType == servletInnerParamClass) {
isInnerParamType = true;
handlerMapping.getParamsIndexMapping().put(servletInnerParamClass.getSimpleName(), i);
break; }}if(! isInnerParamType) {// It is not a parameter of type servletInnerParamClasseshandlerMapping.getParamsIndexMapping().put(parameter.getName(), i); }}// Save the mappinghandlerMappings.add(handlerMapping); }}/** * standardize url **@param url
* @return* /
private String standardizedUrl(String url) {
if (!"".equals(url) && ! url.startsWith("/")) {
url += "/";
}
returnurl; }}Copy the code
HandlerInterceptor Interceptor interface
/** * interceptor interface */
public interface HandlerInterceptor {
/** * request preintercept **@param request
* @param response
* @paramHandler Intercepted controller *@paramMethod The method that is intercepted@return* /
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
return true;
}
/** * request post-intercept **@param request
* @param response
* @paramHandler Intercepted controller *@paramMethod Intercepted method */
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {}}Copy the code
The interceptor executes the HandlerInterceptorChain
/** * Interceptor chain is used to execute all interceptors corresponding to the URI * * pre-intercept, preHandle() method of the interceptor list * post-intercept, preHandle() method of the interceptor list */
public class HandlerInterceptorChain {
private List<HandlerInterceptor> interceptorList;
public HandlerInterceptorChain(List<HandlerInterceptor> interceptorList) {
this.interceptorList = interceptorList;
}
/** * Execute intercepting logic **@param request
* @param response
* @param handler
* @param handler
* @return method
*/
public boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
for (HandlerInterceptor handlerInterceptor : interceptorList) {
if(! handlerInterceptor.preHandle(request, response, handler, method)) {return false; }}return true;
}
/** * perform interception logic in reverse order **@param request
* @param response
* @param handler
* @param method
*/
public void doPostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
for (int i = interceptorList.size() - 1; i >= 0; i--) { interceptorList.get(i).postHandle(request, response, handler, method); }}}Copy the code
HandlerInterceptorMapping processor interceptor mapper
/** * processor interceptor mapper */
public class HandlerInterceptorMapping {
private String[] interceptUri;
private String[] excludeUri;
private HandlerInterceptor interceptor;
public HandlerInterceptorMapping(String[] interceptUri, String[] excludeUri, HandlerInterceptor interceptor) {
this.interceptUri = interceptUri;
this.excludeUri = excludeUri;
this.interceptor = interceptor; }}Copy the code
Processor mapper HandlerMapping
/** * Processor mapper * mapping information between URL and method */
public class HandlerMapping {
/** * The controller class to map */
private Object handler;
/** * mapping method */
private Method method;
/** * the url of the regular expression */
private Pattern pattern;
/** * the coordinate position of the method parameter */
private Map<String, Integer> paramsIndexMapping;
public HandlerMapping(Object controller, Method method, Pattern pattern) {
this.pattern = pattern;
this.handler = controller;
this.method = method;
this.paramsIndexMapping = newHashMap<>(); }}Copy the code
The Interceptor annotation @interceptor
/** * interceptor annotations * Annotated classes represent interceptors that will be added to the IOC container. * /
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Interceptor {
// Intercepted URI
String[] interceptUri() default {};
// UrIs that need not be intercepted
String[] excludeUri() default {};
}
Copy the code
Handler annotation @Controller
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
Copy the code
The processor mapper annotation @requestMapping
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value(a) default "";
}
Copy the code
use
Configure web. XML
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>com.otoomo.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Copy the code
Configuration of MVC. XML
<beans>
<component-scan base-package="com.xxx.controller"/>
</beans>
Copy the code
Define handler
@Controller
@RequestMapping("/transfer")
public class TransferController {
@RequestMapping
public void transfer(HttpServletRequest request, HttpServletResponse response, String fromCardNo, String toCardNo, String money) throws IOException {
/ /... The business logic}}Copy the code
Defining interceptors
@Interceptor( interceptUri = {"/transfer"} )
public class SecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
System.out.println("SecurityInterceptor preHandle......");
/ /... The business logic
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
System.out.println("SecurityInterceptor postHandle......"); }}Copy the code