I am an undergraduate graduate, 21 graduates, one year of work experience, resume professional skills are as follows, based on the resume, and review the knowledge learned to prepare for interviews.
Recorded date: 2022.1.4
Most of the knowledge points are only introduced in general, and the specific content is reviewed in detail according to the recommended blog links.
Framework Principles - SpringMVC
concept
introduce
SpringMVC Meaning
SpringMVC is a Java-based lightweight Web framework that implements the Web MVC design pattern, requests driven types, and decouples responsibilities at the Web tier using the idea of the MVC architecture pattern. Request-driven refers to the use of a request-response model, the framework is designed to help us simplify our development, and SpringMVC is designed to simplify our daily Web development.
Spring MVC is part of Spring and belongs to the Web layer in the three-tier Spring architecture. It belongs to the Web module in the Spring framework.
MVC Meaning
The full name of MVC is Model View Controller, which is an abbreviation for Model-View-Controller and a software design paradigm. It organizes code in a way that separates business logic, data and interface display, aggregates many business logic into a single component, and does not need to rewrite business logic to reduce encoding time while improving and personalizing the interface and user interaction.
MVC starts with desktop programs, M refers to the business model, V refers to the user interface, and C is the controller.
In the web page, they are represented as follows:
- V is the View view, which refers to the interface that users see and interact with. For example, a web interface consisting of html elements or a client interface for software. One of the benefits of MVC is that it can handle many different views for applications. There is no real processing happening in the view, it is just a way to output data and allow user manipulation.
- M is model A model is a model that represents business rules. Of the three parts of MVC, the model has the most processing tasks. The data returned by the model is neutral, and the model is independent of the data format. Such a model can provide data for multiple views. Since the code applied to the model can be reused by multiple views only once, the code repetition is reduced.
- C is a controller controller that accepts input from the user and invokes models and views to fulfill the user's needs. The controller itself does not output anything or do any processing. It simply receives the request and decides which model component to call to process it, then decides which view to display the returned data.
Purpose of use
The idea is to separate the implementation codes of M and V so that the same program can use different representations. For example, how the contents of the Windows System Resource Manager folder are displayed, the details are displayed on the left and the medium icons on the right. The contents of the files have not changed. What is changed is how they are displayed. No matter what type of display the user uses, the contents of the file remain unchanged, separating M from V.
Running process (engraved in DNA)
The workflow of SpringMVC is introduced first, then the source code is parsed step by step.
From the graph in Spring MVC in Silicon Valley:
We can divide the process into these eleven steps:
- The user sends a request to the front-end controller Dispatcher Servlet.
- Dispatcher Servlet receives a request to call the HandlerMapping processor mapper.
- The processor mapper finds the specific processor (which can be found based on xml configuration and annotations), generates the processor object and the processor interceptor (if any) and returns it to the Dispatcher Servlet.
- Dispatcher Servlet calls the HandlerAdapter processor adapter.
- The HandlerAdapter adapts to call specific processors (Controller s, also known as back-end controllers).
- Controller execution completes and returns to ModelAndView.
- HandlerAdapter returns the Controller execution result ModelAndView to Dispatcher Servlet.
- Dispatcher Servlet passes ModelAndView to ViewReslover View Parser.
- The ViewReslover parses and returns the specific View.
- The Dispatcher Servlet renders the view based on the View (that is, fills the view with model data).
- Dispatcher Servlet responds to the user.
The above process is not very memorable, so we can summarize the main process:
- First our client sends the request to Dispatcher Servlet.
- Later interactions occur within the execution chain process:
- Dispatcher Servlet and HandlerMapping;
- Dispatcher Servlet and HandlerAdapter and Handler;
- Dispatcher Servlet and ViewReslover;
- Finally, the Dispatcher Servlet responds to the result.
The specific process we will learn from the source code below.
SpringMVC Component
DispatcherServlet
As a front-end controller, Dispatcher Servlet is the center of the entire process control. Its function is to receive requests, respond to results, and control the execution of other components, to unify dispatch, to reduce the coupling between components, and to improve the scalability of each component.
Source Code Interpretation
Org. Springframework. Web. Servlet. The Dispatcher Servlet class, let's first look at its class diagram:
We found that it is also a Servlet, because it inherits from the HttpServlet and responds to HTTP requests using the Servlet API.
When we first learned the SSM framework, we knew we had to work on the web. Configure Dispatcher Servlet in xml. When this Servlet is configured, when the web container loads, the Servlet container controls its life cycle.
Container Initialization
If you don't understand WebApplicationContext, ServletContext, and so on, first check the blog links: Relationship and working mechanism between Dispacher Servlet and WebApplicationContext and ServletContext in Spring
When tomcat starts, it initializes an IOC container for the web, which is used to initialize and inject various objects that we need to run the web.
Web. Configuration in xml:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
ContextLoaderListener Initialization Container (Pre)
Let's first look at its class diagram:
org.springframework.web.context.ContextLoaderListener is a listener that implements the ServletContextListener interface, which is used to listen for Servlets. When tomcat starts, it initializes a Servlet container so that ContextLoaderListener listens for initialization of Servlets so that we can also do some initialization in ContextLoaderListener after Servlet initialization.
The initialization flowchart of SpringMVC parent container is given:
Looking at the source code for the ServletContextListener below, it is also relatively simple. The ContextLoaderListener implements the ServletContextListener interface, so there are two methods, contextInitialized and contextDestroyed. The method contextInitialized is called when the web container is initialized and the method contextDestroyed when the web container is destroyed.
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() {} public ContextLoaderListener(WebApplicationContext context) { super(context); } // Initialize the startup context, also known as the Root context, which is the WebApplicationContext, where the implementation class is the XmlWebApplicationContext @Override public void contextInitialized(ServletContextEvent event) { //Implemented in parent ContextLoader initWebApplicationContext(event.getServletContext()); } @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
The default implementation of ContextLoaderListener's method contextInitialized() is implemented in his parent ContextLoader's initWebApplicationContext() method, which means to initialize the web application context. His main process is to create an IOC container and store the created IOC container in the servletContext, whose core implementation is as follows:
//Initialize WebApplicationContext, IOC container public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { //Determine if a WebApplicationContext already exists in the web container, or throw an exception if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException("..."); } // ...log try { //Create WebApplicationContext if (this.context == null) { //The final result is the XmlWebApplicationContext this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; //Determine whether the application context is active if (!cwac.isActive()) { //Determine if there is already a parent container if (cwac.getParent() == null) { //Get Parent Container ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } //Set and refresh the WebApplicationContext container configureAndRefreshWebApplicationContext(cwac, servletContext); } } //Set the initialized WebApplicationContext to the servletContext servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } // ...log //Initialize WebApplicationContext complete and return return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } }
Specific details of the code implementation are ignored, you can refer to the blog: springMVC Source Analysis - Container Initialization (1) ContextLoaderListener
Initialize Dispatcher Servlet (primary)
The initialization flow chart is given as follows:
The initialization section starts with the init() method of HttpServletBean with the following code:
@Override public final void init() throws ServletException { // Set Servlet initialization parameters to this component, such as contextAttribute, contextClass, namespace, contextConfigLocation... PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Hook method left for subclass expansion initServletBean(); }
It calls its own hook method, initServletBean(), which is implemented by a subclass, FrameworkServlet, which is used to initialize the Web context by doing two main things:
- Initialize the web context.
- Provides initialization extension points to subclasses.
The code is as follows:
@Override protected final void initServletBean() throws ServletException { // ... Logging try { // Initialize web context this.webApplicationContext = initWebApplicationContext(); // Hook method left for subclass expansion initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } // ... Logging }
Let's take a look at the initWebApplicationContext() method that initializes the web context, and you can see from the initWebApplicationContext() method that basically if the ContextLoaderListener loads the context, it acts as the root context (the parent container of the Dispatcher Servlet).
protected WebApplicationContext initWebApplicationContext() { //Root context (loaded by ContextLoaderListener) WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // 1. In the context of creating the Servlet injection wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { //2. Find a context that is already bound wac = findWebApplicationContext(); } if (wac == null) { //3. If no context is found and the father is specified as ContextLoaderListener wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { synchronized (this.onRefreshMonitor) { //4. Refresh context (perform some initialization) onRefresh(wac); } } if (this.publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
Finally, the onRefresh() method is called to perform some initialization of the container, which is implemented by subclasses to extend.
Dispatcher Servlet inherits the FrameworkServlet and implements the onRefresh() method to provide some front-end Controller-related configuration.
@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } // Specific initialization logic protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
As you can see from the code above, Dispatcher Servlet will start with the configuration of the Web-tier beans we need, such as HandlerMapping, HandlerAdapter, etc., and if we don't, it will also provide us with the default configuration.
The entire Dispatcher Servlet initialization process\specifically does the following two things:
- Initializes the Web context used by Spring Web MVC and may specify the parent container as (ContextLoaderListener loads the root context).
- Initialize policies used by Dispatcher Servlets, such as HandlerMapping, HandlerAdapter, and so on.
The default configuration provided in SpringMVC is Dispatcher Servlet. In properties, the policy used when no configuration is specified in the Spring configuration file is configured as follows:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\ org.springframework.web.servlet.function.support.RouterFunctionMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ org.springframework.web.servlet.function.support.HandlerFunctionAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
Declare that my version is spring-webmvc-5.2.5.RELEASE.
From the configuration above, we can see that Dispatcher Servlets automatically register these special beans at startup without us registering. If we do, the default will not be registered.
Components of Dispatcher Servlet
From Dispatcher Servlet. Properrties can see that there are many special beans, so let's look at what the main special beans are in Spring Web MVC.
Dispatcher Servlet uses WebApplicationContext as its default context, so let's see what special beans are in that context:
- Controller: Processor/Page Controller, which does C in MVC, but the control logic is transferred to the front-end controller for processing requests.
- HandlerMapping: Requests a mapping to the processor, if the mapping successfully returns a HandlerExecutionChain object (containing a Handler Processor (Page Controller) object and multiple HandlerInterceptor interceptors) objects; If BeanNameUrlHandlerMapping maps URL s to Bean names, the beans that map successfully are the processors here.
- **HandlerAdapter:**HandlerAdapter packages processors as adapters to support multiple types of processors, i.e. applications of adapter design patterns, making it easy to support many types of processors; For example, the SimpleController Handler Adapter adapts beans that implement the Controller interface and functions without the handleRequest method of the processor.
- **ViewResolver:**ViewResolver resolves the logical view name to a specific View, which makes it easy to change other view technologies through this strategy mode; For example, InternalResourceViewResolver maps the logical view name to a jsp view.
- **LocalResover:**Local Resolution, because Spring supports internationalization, LocalResover parses client Locale information to facilitate internationalization;
- **ThemeResovler:**Theme parsing, which enables multiple styles of a page, often similar to software skin effects.
- **MultipartResolver:**File Upload Resolution, used to support file upload.
- **HandlerExceptionResolver:** Processor exception resolution allows you to map exceptions to the corresponding Unified Error Interface to display a user-friendly interface (instead of giving users specific error information).
- **RequestToViewNameTranslator:** Automatically maps request URL s to logical view names when the processor does not return information such as logical view names.
- **FlashMapManager:** Policy interface for managing FlashMap, which stores the output of one request as input to another request, usually for redirecting scenes, which is described later.
From the source code we can see:
// File Upload Component /** MultipartResolver used by this servlet */ private MultipartResolver multipartResolver; // Resource Location Component /** LocaleResolver used by this servlet */ private LocaleResolver localeResolver; // Theme Resolution Component /** ThemeResolver used by this servlet */ private ThemeResolver themeResolver; // Processor Mapper Component Collection /** List of HandlerMappings used by this servlet */ private List<HandlerMapping> handlerMappings; // Processor Adapter Component Collection /** List of HandlerAdapters used by this servlet */ private List<HandlerAdapter> handlerAdapters; // Exception Handling Parser Collection /** List of HandlerExceptionResolvers used by this servlet */ private List<HandlerExceptionResolver> handlerExceptionResolvers; // View Name Resolver /** RequestToViewNameTranslator used by this servlet */ private RequestToViewNameTranslator viewNameTranslator; // Redirection and FlashMap storage components /** FlashMapManager used by this servlet */ private FlashMapManager flashMapManager; // View Resolution Component Collection /** List of ViewResolvers used by this servlet */ private List<ViewResolver> viewResolvers;
Below we will briefly introduce several components, its concept and initialization process, the specific role we can talk about the source code of the entire execution chain in a few moments.
MultipartResolver
MultipartResolver is used to handle file uploads and a multipart parser needs to be added.
Need to be configured in the configuration file:
<!-- File Upload,id Fixed name --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> <!-- Maximum File Size size Limit to 10 m --> <property name="maxInMemorySize" value="4096" /> <!-- Maximum memory block used during file upload --> <property name="defaultEncoding" value="UTF-8"></property> </bean>
Initialization
Take a look at the initMultipartResolver(context) method just now:
private void initMultipartResolver(ApplicationContext context) { try { this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); // ...log } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; if (logger.isTraceEnabled()) { logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared"); } } }
initMultipartResolver is the bean that gets CommonsMultipartResolver and assigns it to Dispatcher Servlet#multipartResolver.
HandlerMapping
The HandlerMapping interface, which implements different mappings by extending the processor mapper.
It is used to map handlers based on the requested url.
Handler Mapping is responsible for finding handlers as processors based on user requests. SpringMVC provides different mappers to implement different mapping methods, such as profile, interface, annotation, and so on.
Dispatcher Servlet selects Handler Mapping by priority, and if the current Handler Mapping can return an available handler, the currently returned handler will be used to process the request.
Initialization
Let's take a look at the initHandlerMappings() method we just used:
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // Default to true, obtained from all IOC containers if (this.detectAllHandlerMappings) { Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { // Get handlerMapping from the current IOC container by name through getBean try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { } } // The localization still not found requires a default handler Mappings to be generated for the servlet. // Default value set to Dispatcher Servlet. Properties if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); // ...... } }
Parameter this.detectAllHandlerMappings defaults to true and can be modified by configuration:
<init-param> <param-name>detectAllHandlerMappings</init-param> <param-value>false</param-value> </init-param>
If the above parameters are set, SpringMVC will look for a bean named "handlerMapping" and act as the only handlerMapping. Otherwise, all handler Mappings under the WebApplicationContext will be loaded. If not, call getDefaultStrategies to create.
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { String key = strategyInterface.getName(); // This is org.springframework.web.servlet.HandlerMapping String value = defaultStrategies.getProperty(key); // Dispatcher Servlet. Default configuration in properties if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); for (String className : classNames) { try { Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } ...... } return strategies; } else { return new LinkedList<>(); } }
Default defaultStrategies is configured by reading the Dispatcher Servlet. Properrties to configure, read the configuration code as follows:
private static final Properties defaultStrategies; // Default configuration properties static { try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } ...... }
As mentioned earlier in the Default Configuration, Dispatcher Servlet. In properties, specify the following configuration for HandlerMapping:
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
HandlerAdapters
HandlerAdapters interface, which extends the processor adapter to achieve different adaptions.
Its function is summarized in one sentence by calling specific methods to handle requests from users. When handler Mapping gets the controller to execute the request, Dispatcher Servltet calls the corresponding Handler Adapter to handle it based on the controller type corresponding to the controller.
Initialization
As with the code logic above, you can also set the detectAllHandlerAdapters value to false to load a bean named HandlerAdapter, which allows users to load custom HandlerAdapters. Take a look at the Spring configuration file Dispatcher Servlet. HandlerAdapter specified in properties.
The initialization source is as follows:
private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException ex) { } } if (this.handlerAdapters == null) { this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); // ...log } }
We will not look at the other initializations that follow.
SpringMVC Processing Request Link Resolution
[External chain picture transfer failed, source station may have anti-theft chain mechanism, it is recommended to save the picture and upload it directly (img-VxufKn18-164177192) (C:\UsersjiangcxAppDataRoamingTyporatypora-user-imagesimage-20264.png)]
doService() is an entry function that accepts Servlet s as SpringMVC and calls the doDispatch () implementation. The core logic of the function doDispatch() is as follows:
- Find handlerMapping based on the path properties contained in request.
- Once a handler is found, it looks for an adapter based on the handler, where the purpose of the adapter is to mask the implementation differences of the handler and provide consistent invoke calls.
- Once an adapter is found, the handler interface is invoked to make the most core business requests.
Show a general flowchart of doDispatch():
FrameworkServlet#processRequest
First, let's look at the entry to a request, where the FrameworkServlet gives the processRequest(request, response) method the ability to replicate all doGet/doPost, and so on.
Let's start by parsing the **processRequest()** method, FrameworkServlet#processRequest, which is an implementation of the FrameworkServlet. In fact, it also provides some template implementations that eventually open up to subclasses in template design patterns, which are abundant in Spring source code. Here we focus on what the FrameworkServlet does for us:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); // Record thrown exceptions Throwable failureCause = null; //Get the previous ocaleContext context (as may have been set in Filter) LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); // Create a Local context with the current request and continue processing later LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // The build logic here notes that if previousAttributes are null or the ServletRequestAttributes type, then new ServletRequestAttributes(request, response); // If not null, keep previous binding results and do not do duplicate binding (respect original) ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // Get the asynchronous manager. This is the first time you get it, new WebAsyncManager(), and put it in the attr of the request WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); //Note here that the RequestBindingInterceptor is registered for asynchronous context constancy (for binding current request, response, local, etc.) asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //It's clear that request is bound to the Local context, RequestContext initContextHolders(request, localeContext, requestAttributes); try { //Template design pattern: the actual logic is implemented by the subclass Dispatcher Servlet doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { //Now that all processing is done, the view is rendered //When the doService() method completes, the context is reset, that is, unbound resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } //Key: Whether the execution succeeds or fails, an event is posted saying that I have handled the request (if there is a need to listen, you can listen on the event, and every request will have one) publishRequestHandledEvent(request, response, startTime, failureCause); } }
The publishRequestHandledEvent() is used to publish events after request processing has completed:
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, @Nullable Throwable failureCause) { //Publishing of this event is handled when publishEvents are set to true and webApplicationContext is not empty if (this.publishEvents && this.webApplicationContext != null) { // Calculate the time taken to process the request long processingTime = System.currentTimeMillis() - startTime; this.webApplicationContext.publishEvent( //ServletRequestHandledEvent This event: currently only published here new ServletRequestHandledEvent(this, request.getRequestURI(), request.getRemoteAddr(), request.getMethod(), getServletConfig().getServletName(), WebUtils.getSessionId(request), getUsernameForRequest(request), processingTime, failureCause, response.getStatus())); } }
If we want, we can listen for this ServletRequestHandledEvent event:
// A listener dedicated to listening for ServletRequestHandledEvent time @Slf4j @Component public class ServletReqestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> { @Override public void onApplicationEvent(ServletRequestHandledEvent event) { // url=[/test/hello]; client=[127.0.0.1]; method=[GET]; servlet=[dispatcher]; session=[null]; user=[null]; time=[143ms]; status=[OK] log.info(event.getDescription()); log.info("The return status code is:" + event.getStatusCode()); //The return status code is:200 log.info("The exception information is:" + event.getFailureCause()); //The exception information is:null log.info("Request processing time is:" + event.getProcessingTimeMillis()); //Request processing time is 143 log.info("The event source is:" + event.getSource()); //Event source is: org.springframework.web.servlet.DispatcherServlet@3e7fadbb } }
DispatcherServlet#doService
The subclass DispatcherServlet implements the doService() method:
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { // If the request is an include request (request contains), save a snapshot version of the data in the request field // After the doDispatch finishes, the snapshot version of the data will be overwritten with the new request Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); 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)); } } } // Clearly, putting some of the common objects in the request field makes it easy for the Handler to get them anywhere request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); //This is a web subcontainer request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // If redirected, place more if (this.flashMapManager != null) { 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 { // The most important way for Dispatcher Servlet to distribute requests to you, find handler handling, and so on doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { //If it is an include request, the data snapshot above will be repositioned in the request if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
DispatcherServlet#doDispatch
First find the HandlerMethod (with the Method reflection property, that is, the method in the corresponding Controller) based on the requested path, then match the interceptor corresponding to the path, and construct a HandlerExecutionChain object with the HandlerMethod and interceptor.
The HandlerExecutionChain object is obtained through a method provided by the HandlerMapping interface.
With the HandlerExecutionChain, the ModelAndView object is processed through the HandlerAdapter object, and when handles are inside the HandlerMethod, the HandlerMethodArgumentResolver implementation classes handle the parameters of the HandlerMethod (very important), and the HandlerMethodReturnValueHandler implementation classes handle the return values. The final return value is processed as a ModelAndView object, during which exceptions are handled by the HandlerExceptionResolver interface implementation class.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // One thing to note about using processedRequest here is that when processing uploads, processedRequest will no longer point to the same object as request HttpServletRequest processedRequest = request; // Exception Chain Processor HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { //The checkMultipart method is important to determine if it is an upload requirement //A request for file upload is considered if the request is a POST request and the Context-Type in the request header begins with a multipart/ processedRequest = checkMultipart(request); // Mark it: is it a request for file upload multipartRequestParsed = (processedRequest != request); // Find a processor, if no corresponding processing class is found, this usually returns 404, which throws an exception if the throwExceptionIfNoHandlerFound property value is true mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Find a suitable Handler Adapter based on the actual handler, the method detailed logic is the same as getHandler, so it is no longer explained HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // If it is a GET request, return directly if the content has not changed 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; } } // Execute the collection of interceptors in the processor chain if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // The logic to actually execute the controller s method we wrote ourselves. Return a ModelAndView // This is also a complex process (serialization, data binding, etc.) mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // If the asynchronous start occurs, it will return directly here and no longer execute interceptors such as PostHandle if (asyncManager.isConcurrentHandlingStarted()) { return; } //Meaning: If we don't set viewName, we use the default. Otherwise, use our own applyDefaultViewName(processedRequest, mv); // Execute all interceptor postHandle methods and give him the mv // Here's a little detail: the interceptor is in reverse order at this time mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { // These two catcher s do nothing but log exceptions dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } //This method is important, as its name implies, because it handles results, renders views, handles exceptions, and so on. 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); } } } }
checkMultipart
This method is used to determine whether the upload requirement is an upload requirement, and if the upload request is processed, the returned request will no longer point to the same object as the processedRequest for the original request parameter.
The implementation of checkMultipart is as follows:
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // MultipartResolver &&file upload request configured to continue executing internal code block if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { // If the request is already a MultipartHttpServletRequest then output the log and end if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { logger.debug("..."); } else if (hasMultipartException(request) ) { // Determining if there is a MultipartException typically does not logger.debug("..."); } else { try { // It is important to note that, regardless of the implementation of multipartResolver, there is a new implementation class for MultipartHttpServletRequest inside, so it no longer points to the original request, so it is important to note that return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("...", ex); } else { throw ex; } } } } // If the previously returned code does not execute, the original request is returned, and nothing is executed return request; }
Note here: org.springframework.web.multipart.support.MultipartFilter, if on the web. Configuring this filter in XML prejudices whether a request for file upload is in the filter and converts the request to the MultipartHttpServletRequest type. The default MultipartResolver used in this filter is Standard Servlet MultipartResolver.
There is a property called resolveLazily in CommonsMultipartResolver.
private boolean resolveLazily = false;
This property value represents whether the file upload is delayed for parsing and defaults to false. The final return is a class of DefaultMultipartHttpServletRequest. An important method here is parseRequest, which parses file upload requests. At the bottom is the commons-fileupload suite, but Spring, after acquiring the FileItem, wraps it up again to facilitate the integration of the Spring framework.
getHandler
The main action getHandler() does is loop through handler Mappings, matching one return from within.
SpringMVC loads three request processing mapping classes by default: RequestMappingHandlerMapping, SimpleUrlHandlerMapping, and BeanNameUrlHandlerMapping. The three classes have a common parent: AbstractHandlerMapping.
getHandler() returns the HandlerExecutionChain, which contains the handler and interceptor collections.
public class HandlerExecutionChain { private final Object handler; // Interceptor @Nullable private HandlerInterceptor[] interceptors; // Interceptor Array @Nullable private List<HandlerInterceptor> interceptorList; // Interceptor Collection // Method of executing interceptor chain 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]; // Note: If the interceptor returns false, the AfterCompletion method for all interceptors will be triggered immediately. And immediately return false if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; } }
Let's look at the getHandler() method, which we initialized earlier.
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; }
Where mapping.getHandler() is implemented by AbstractHandlerMapping and has the following main functions:
- Find handler processors, specifically left to subclasses to override the implementation, such as finding matches based on URL s, and so on.
- Build a processor chain, bind to the handler, and get all the interceptors internally, then add them to the processor chain.
- If cross-domain is set, configure cross-domain information and place it in the handler.
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // This is left to subclasses to override the implementation: find handler processors such as matches based on URL s, etc. // The process of getting a hadnler is very complex, so let's just mention a little later, not in detail 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); } // Build wrapper classes for HandlerMethod and filter chains, and bind handler HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); //...log // Is it a cross-domain request if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { //Override Template Method CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null); //Method @CrossOrigin annotation information CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); config = (config != null ? config.combine(handlerConfig) : handlerConfig); //Add new CorsInterceptor(config) to interceptor chain executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
getHandlerInternal
Class AbstractHandlerMethodMapping < T >, which implements the InitializingBean interface, means that the afterPropertiesSet method must be overridden.
Rewrite reference article for this method: Spring5 Source Read Notes (5.2.1) After PropertiesSet in AbstractHandler Mapping
AbstractHandlerMethodMapping#getHandlerInternal
Let's take a look at the getHandlerInternal implementation in AbstractHandlerMethodMapping.
//Assume the current access path: http://localhost:10000/product/category/list/tree protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //Get uri from request object String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // /product/category/list/tree request.setAttribute(LOOKUP_PATH, lookupPath); this.mappingRegistry.acquireReadLock(); // Read Lock try { //Find the corresponding HandlerMethod object from the mapping relationship based on uri HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //Instantiate Controller class return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); // Unlock } }
AbstractHandlerMethodMapping#lookupHandlerMethod
Let's look at how getHandlerInternal calls lookupHandlerMethod in AbstractHandlerMethodMapping to find the corresponding HandlerMethod object. Let's look at the implementation.
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // getMappingsByUrl(lookupPath) is really this.urlLookup.get(urlPath) List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { // If empty, no mapping found in this handler Mapping addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); } if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.handlerMethod.getMethod(); Method m2 = secondBestMatch.handlerMethod.getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); } }
AbstractHandlerMethodMapping#addMatchingMappings
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) { for (T mapping : mappings) { //T:RequestMappingInfo, returns null if @RequestMapping does not match request T match = getMatchingMapping(mapping, request); if (match != null) { //RequestMappingInfo object and HandlerMethod object are encapsulated in Match object matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping))); } } }
getHandlerExecutionChain
Get the interceptor chain.
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { //Wrap the HandlerMethod object into the HandlerExecutionChain object, which has an array of interceptors HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); //Get uri String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); //Is there an interceptor for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }
getHandlerAdapter
handlerAdapters are finished initializing the nine components, and the default configuration is Dispatcher Servlet. In the properties file, whether or not the adapter can be adapted depends on how the support method of the adapter is implemented.
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { // If the corresponding adapter supports handler, it returns, which is implemented by different subclasses return adapter; } } } // Exception thrown without adapter throw new ServletException("..."); }
The default Handler Adapter processor adapter is Dispatcher Servlet. The properties configuration file is as follows:
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ org.springframework.web.servlet.function.support.HandlerFunctionAdapter
There are four default Handler Adapter processor adapters:
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- RequestMappingHandlerAdapter
- HandlerFunctionAdapter
HttpRequestHandlerAdapter
public class HttpRequestHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } }
SimplerControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof Controller); } }
AbstractHandlerMethodAdapter
The following is the support method of the AbstractHandlerMethodAdapter, where the support Internal method is implemented by the subclass RequestMappingHandlerAdapter and has a fixed return value of true, whereas the HandlerMethod is the one in the request processing chain, so the adapter uses the RequestMappingHandlerAdapter.
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered { public final boolean supports(Object handler) { return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); } }
RequestMappingHandlerAdapter
// RequestMappingHandlerAdapter inherits AbstractHandlerMethodAdapter public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { protected boolean supportsInternal(HandlerMethod handlerMethod) { return true; } }
HandlerFunctionAdapter
public class HandlerFunctionAdapter implements HandlerAdapter, Ordered { public boolean supports(Object handler) { return handler instanceof HandlerFunction; } }
processDispatchResult
ProceDispatchResult is used to process results, render views, handle exceptions, and so on.
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; //If there are exceptions, go into exception handling logic and return to the exception page if (exception != null) { // Exceptions with exception page views if (exception instanceof ModelAndViewDefiningException) { logger.debug("...", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); //1. All HandlerExceptionResolver processors configured by ourselves (or by default) will be executed //2. The above needs attention, but any processing method returns not null, but mv returns. The subsequent processors are no longer processed. With short circuit effect, it is important to note that it is judged by null //3. After processing, get the error's view mv, and finally set a viewName, then go back mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // If the view is not empty or null, the render() method is executed and the view is rendered. if (mv != null && !mv.wasCleared()) { render(mv, request, response); // If there is an error view, this clears all error attributes from all request domains if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } //Processing asynchronous we find that it does not execute the following AFTERrCompletion method, just notice if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } // AfterCompletion method to execute interceptor if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
So far, there is only one way to render the view: render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Resolve it locally through localeResolver and put it in response Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); //=======================View: Key in Key============= View view; String viewName = mv.getViewName(); // If you already have a viewName (in most cases) if (viewName != null) { // View parser parses a view based on the name of the String type (there are multiple view parsers) // Or that principle: once a non-null is returned, the following will not be resolved view = resolveViewName(viewName, mv.getModelInternal(), locale, request); // If the view cannot be resolved, an exception is thrown saying the view cannot be resolved if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { //No view name, but must have view content or throw an exception 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() + "'"); } } try { //Set Response Horse status if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // Formal rendering based on the data in the model (more complex to follow on this part of the logic) view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { throw ex; } }