1. Introduction
1.介绍
Simply put, in the Front Controller design pattern, a single controller is responsible for directing incoming HttpRequests to all of an application’s other controllers and handlers.
简单地说,在Front Controller设计模式中,单个控制器负责将传入的HttpRequests引导至应用程序的所有其他控制器和处理程序。
Spring’s DispatcherServlet implements this pattern and is, therefore, responsible for correctly coordinating the HttpRequests to their right handlers.
Spring的DispatcherServlet实现了这种模式,因此,它负责将HttpRequests正确地协调到它们的处理程序中。
In this article, we will examine the Spring DispatcherServlet’s request processing workflow and how to implement several of the interfaces that participate in this workflow.
在这篇文章中,我们将考察Spring DispatcherServlet的请求处理工作流以及如何实现参与该工作流的几个接口。
2. DispatcherServlet Request Processing
2.DispatcherServlet请求处理
Essentially, a DispatcherServlet handles an incoming HttpRequest, delegates the request, and processes that request according to the configured HandlerAdapter interfaces that have been implemented within the Spring application along with accompanying annotations specifying handlers, controller endpoints, and response objects.
从本质上讲,DispatcherServlet处理传入的HttpRequest,委托请求,并根据配置的HandlerAdapter接口处理该请求,这些接口已在Spring应用程序中实现,并附带有指定处理程序、控制器端点和响应对象的注释。
Let’s get more in-depth about how a DispatcherServlet processes a component:
让我们更深入地了解DispatcherServlet如何处理一个组件。
- the WebApplicationContext associated to a DispatcherServlet under the key DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE is searched for and made available to all of the elements of the process
- The DispatcherServlet finds all implementations of the HandlerAdapter interface configured for your dispatcher using getHandler() – each found and configured implementation handles the request via handle() through the remainder of the process
- the LocaleResolver is optionally bound to the request to enable elements in the process to resolve the locale
- the ThemeResolver is optionally bound to the request to let elements, such as views, determine which theme to use
- if a MultipartResolver is specified, the request is inspected for MultipartFiles – any found are wrapped in a MultipartHttpServletRequest for further processing
- HandlerExceptionResolver implementations declared in the WebApplicationContext picks up exceptions that are thrown during processing of the request
You can learn more about all the ways to register and set up a DispatcherServlet here.
你可以了解更多关于注册和设置DispatcherServlet的所有方法。
3. HandlerAdapter Interfaces
3.HandlerAdapter接口
The HandlerAdapter interface facilitates the use of controllers, servlets, HttpRequests, and HTTP paths through several specific interfaces. The HandlerAdapter interface thus plays an essential role through the many stages of the DispatcherServlet request processing workflow.
HandlerAdapter接口通过几个特定的接口促进了控制器、Servlet、HttpRequests和HTTP路径的使用。因此,HandlerAdapter接口在DispatcherServlet请求处理工作流程的许多阶段中发挥了重要作用。
First, each HandlerAdapter implementation is placed into the HandlerExecutionChain from your dispatcher’s getHandler() method. Then, each of those implementations handle() the HttpServletRequest object as the execution chain proceeds.
首先,每个HandlerAdapter实现都被放入HandlerExecutionChain,从你的分派器的getHandler()方法中。然后,随着执行链的进行,这些实现中的每一个都handle()了HttpServletRequest对象。
In the following sections, we will explore a few of the most important and commonly used HandlerAdapters in greater detail.
在下面的章节中,我们将更详细地探讨几个最重要和常用的HandlerAdapters。
3.1. Mappings
3.1.映射
To understand mappings, we need to first look at how to annotate controllers since controllers are so essential to the HandlerMapping interface.
为了理解映射,我们需要首先看看如何注释控制器,因为控制器对HandlerMapping接口来说是非常重要的。
The SimpleControllerHandlerAdapter allows for the implementation of a controller explicitly without a @Controller annotation.
SimpleControllerHandlerAdapter允许在没有@Controller注解的情况下明确实现一个控制器。
The RequestMappingHandlerAdapter supports methods annotated with the @RequestMapping annotation.
RequestMappingHandlerAdapter支持带有@RequestMapping注解的方法。
We’ll focus on the @Controller annotation here, but a helpful resource with several examples using the SimpleControllerHandlerAdapter is also available.
我们将在这里重点讨论@Controller注解,但也有一个有用的资源,其中有几个使用SimpleControllerHandlerAdapter的例子。
The @RequestMapping annotation sets the specific endpoint at which a handler will be available within the WebApplicationContext associated with it.
@RequestMapping 注解设置了与之相关的WebApplicationContext中处理器可用的特定端点。
Let’s see an example of a Controller that exposes and handles the ‘/user/example’ endpoint:
让我们看看一个Controller的例子,它暴露并处理‘/user/example’端点。
@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {
@GetMapping("/example")
public User fetchUserExample() {
// ...
}
}
The paths specified by the @RequestMapping annotation are managed internally via the HandlerMapping interface.
由@RequestMapping注解指定的路径通过HandlerMapping接口进行内部管理。
The URLs structure is naturally relative to the DispatcherServlet itself – and determined by the servlet mapping.
URLs结构自然是相对于DispatcherServlet本身而言的,并由Servlet映射决定。
Thus, if the DispatcherServlet is mapped to ‘/’, then all mappings are going to be covered by that mapping.
因此,如果DispatcherServlet被映射到’/’,那么所有的映射都将被该映射所覆盖。
If, however, the servlet mapping is ‘/dispatcher‘ instead, then any @RequestMapping annotations are going to be relative to that root URL.
然而,如果servlet映射是”/dispatcher“,那么任何@RequestMapping注释都将是相对于该根URL的。
Remember that ‘/’ is not the same as ‘/*’ for servlet mappings! ‘/’ is the default mapping and exposes all URL’s to the dispatcher’s area of responsibility.
请记住,对于servlet映射来说,’/’与’/*’不一样!’/’是默认的映射,将所有的URL暴露在调度器的责任区。’/’是默认的映射,将所有的URL暴露在调度器的责任区。
‘/*’ is confusing to a lot of newer Spring developers. It does not specify that all paths with the same URL context are under the dispatcher’s area of responsibility. Instead, it overrides and ignores the other dispatcher mappings. So, ‘/example’ will come up as a 404!
‘/*’对于很多较新的Spring开发者来说是很困惑的。它并没有指定所有具有相同URL上下文的路径都在调度器的责任范围内。相反,它覆盖并忽略了其他调度器的映射。因此,’/example’会显示为404!
For that reason, ‘/*’ shouldn’t be used except in very limited circumstances (like configuring a filter).
出于这个原因,‘/*’不应该被使用,除非在非常有限的情况下(比如配置一个过滤器)。
3.2. HTTP Request Handling
3.2.HTTP请求处理
The core responsibility of a DispatcherServlet is to dispatch incoming HttpRequests to the correct handlers specified with the @Controller or @RestController annotations.
DispatcherServlet的核心职责是将传入的HttpRequests分派给@Controller或@RestController注解指定的正确处理程序。
As a side note, the main difference between @Controller and @RestController is how the response is generated – the @RestController also defines @ResponseBody by default.
顺便提一下,@Controller和@RestController的主要区别在于如何生成响应–@RestController也默认定义了@ResponseBody。
A writeup where we go into much greater depth regarding Spring’s controllers can be found here.
我们对Spring的控制器进行了更深入的研究,可以在这里找到的一篇文章。
3.3. The ViewResolver Interface
3.3.ViewResolver 接口
A ViewResolver is attached to a DispatcherServlet as a configuration setting on an ApplicationContext object.
ViewResolver作为ApplicationContext对象的配置设置被附加到DispatcherServlet。
A ViewResolver determines both what kind of views are served by the dispatcher and from where they are served.
一个ViewResolver决定了哪种类型的视图由调度器提供,以及它们从哪里被提供。
Here’s an example configuration which we’ll place into our AppConfig for rendering JSP pages:
这里有一个配置的例子,我们将把它放入我们的AppConfig,用于渲染JSP页面。
@Configuration
@EnableWebMvc
@ComponentScan("com.baeldung.springdispatcherservlet")
public class AppConfig implements WebMvcConfigurer {
@Bean
public UrlBasedViewResolver viewResolver() {
UrlBasedViewResolver resolver
= new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
}
Very straight-forward! There are three main parts to this:
非常简单明了!这有三个主要部分。
- setting the prefix, which sets the default URL path to find the set views within
- the default view type which is set via the suffix
- setting a view class on the resolver which allows technologies like JSTL or Tiles to be associated with the rendered views
One common question involves how precisely a dispatcher’s ViewResolver and the overall project directory structure are related. Let’s take a look at the basics.
一个常见的问题涉及到调度器的ViewResolver 和整个项目的目录结构是如何精确关联的。让我们看一下基础知识。
Here’s an example path configuration for an InternalViewResolver using Spring’s XML configuration:
下面是一个使用Spring的XML配置的InternalViewResolver的路径配置示例。
<property name="prefix" value="/jsp/"/>
For the sake of our example, we’ll assume that our application is being hosted on:
为了我们的例子,我们将假设我们的应用程序被托管在。
http://localhost:8080/
This is the default address and port for a locally hosted Apache Tomcat server.
这是本地托管的Apache Tomcat服务器的默认地址和端口。
Assuming that our application is called dispatcherexample-1.0.0, our JSP views will be accessible from:
假设我们的应用程序被称为dispatcherexample-1.0.0,我们的JSP视图将可以从。
http://localhost:8080/dispatcherexample-1.0.0/jsp/
The path for these views within an ordinary Spring project with Maven is this:
使用Maven的普通Spring项目中的这些视图的路径是这样的。
src -|
main -|
java
resources
webapp -|
jsp
WEB-INF
The default location for views is within WEB-INF. The path specified for our InternalViewResolver in the snippet above determines the subdirectory of ‘src/main/webapp’ in which your views will be available.
视图的默认位置是在WEB-INF中。上面的片段中为我们的InternalViewResolver指定的路径决定了’src/main/webapp’的子目录,你的视图将在其中可用。
3.4. The LocaleResolver Interface
3.4.LocaleResolver 接口
The primary way to customize session, request, or cookie information for our dispatcher is through the LocaleResolver interface.
为我们的调度器定制会话、请求或cookie信息的主要方式是通过LocaleResolver接口。
CookieLocaleResolver is an implementation allowing the configuration of stateless application properties using cookies. Let’s add it to AppConfig.
CookieLocaleResolver是一个允许使用cookie配置无状态应用程序属性的实现。让我们把它添加到AppConfig。
@Bean
public CookieLocaleResolver cookieLocaleResolverExample() {
CookieLocaleResolver localeResolver
= new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
localeResolver.setCookieName("locale-cookie-resolver-example");
localeResolver.setCookieMaxAge(3600);
return localeResolver;
}
@Bean
public LocaleResolver sessionLocaleResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
localResolver.setDefaultTimeZone(TimeZone.getTimeZone("UTC"));
return localeResolver;
}
SessionLocaleResolver allows for session-specific configuration in a stateful application.
SessionLocaleResolver 允许在一个有状态的应用程序中进行特定会话的配置。
The setDefaultLocale() method represents a geographical, political, or cultural region, whereas setDefaultTimeZone() determines the relevant TimeZone object for the application Bean in question.
setDefaultLocale()方法代表一个地理、政治或文化区域,而setDefaultTimeZone()决定了相关应用程序Bean的相关TimeZone对象。
Both methods are available on each of the above implementations of LocaleResolver.
这两个方法在上述每个LocaleResolver的实现中都是可用的。
3.5. The ThemeResolver Interface
3.5.ThemeResolver 接口
Spring provides stylistic theming for our views.
Spring为我们的视图提供了风格化的主题。
Let’s take a look at how to configure our dispatcher to handle themes.
让我们来看看如何配置我们的调度器来处理主题。
First, let’s set up all the configuration necessary to find and use our static theme files. We need to set a static resource location for our ThemeSource to configure the actual Themes themselves (Theme objects contain all of the configuration information stipulated in those files). Add this to AppConfig:
首先,让我们设置所有必要的配置,以找到并使用我们的静态主题文件。我们需要为我们的ThemeSource设置一个静态资源位置,以配置实际的Themes本身(Theme对象包含这些文件中规定的所有配置信息)。将此添加到AppConfig。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/", "/resources/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
@Bean
public ResourceBundleThemeSource themeSource() {
ResourceBundleThemeSource themeSource
= new ResourceBundleThemeSource();
themeSource.setDefaultEncoding("UTF-8");
themeSource.setBasenamePrefix("themes.");
return themeSource;
}
Requests managed by the DispatcherServlet can modify the theme through a specified parameter passed into setParamName() available on the ThemeChangeInterceptor object. Add to AppConfig:
由DispatcherServlet管理的请求可以通过传递到ThemeChangeInterceptor对象上的setParamName()的指定参数来修改主题。添加到AppConfig:
@Bean
public CookieThemeResolver themeResolver() {
CookieThemeResolver resolver = new CookieThemeResolver();
resolver.setDefaultThemeName("example");
resolver.setCookieName("example-theme-cookie");
return resolver;
}
@Bean
public ThemeChangeInterceptor themeChangeInterceptor() {
ThemeChangeInterceptor interceptor
= new ThemeChangeInterceptor();
interceptor.setParamName("theme");
return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(themeChangeInterceptor());
}
The following JSP tag is added to our view to make the correct styling appear:
下面的JSP标签被添加到我们的视图中,以使正确的样式出现。
<link rel="stylesheet" href="${ctx}/<spring:theme code='styleSheet'/>" type="text/css"/>
The following URL request renders the example theme using the ‘theme’ parameter passed into our configured ThemeChangeIntercepter:
下面的URL请求使用传入我们配置的ThemeChangeIntercepter的’theme’参数渲染了example主题:。
http://localhost:8080/dispatcherexample-1.0.0/?theme=example
3.6. The MultipartResolver Interface
3.6.MultipartResolver 接口
A MultipartResolver implementation inspects a request for multiparts and wraps them in a MultipartHttpServletRequest for further processing by other elements in the process if at least one multipart is found. Add to AppConfig:
一个MultipartResolver实现检查请求中的多部分,并将其包装在MultipartHttpServletRequest中,如果发现至少有一个多部分,则由流程中的其他元素进一步处理。添加到AppConfig。
@Bean
public CommonsMultipartResolver multipartResolver()
throws IOException {
CommonsMultipartResolver resolver
= new CommonsMultipartResolver();
resolver.setMaxUploadSize(10000000);
return resolver;
}
Now that we’ve configured our MultipartResolver bean, let’s set up a controller to process MultipartFile requests:
现在我们已经配置了我们的 MultipartResolver Bean,让我们设置一个控制器来处理 MultipartFile 请求。
@Controller
public class MultipartController {
@Autowired
ServletContext context;
@PostMapping("/upload")
public ModelAndView FileuploadController(
@RequestParam("file") MultipartFile file)
throws IOException {
ModelAndView modelAndView = new ModelAndView("index");
InputStream in = file.getInputStream();
String path = new File(".").getAbsolutePath();
FileOutputStream f = new FileOutputStream(
path.substring(0, path.length()-1)
+ "/uploads/" + file.getOriginalFilename());
int ch;
while ((ch = in.read()) != -1) {
f.write(ch);
}
f.flush();
f.close();
in.close();
modelAndView.getModel()
.put("message", "File uploaded successfully!");
return modelAndView;
}
}
We can use a normal form to submit a file to the specified endpoint. Uploaded files will be available in ‘CATALINA_HOME/bin/uploads’.
我们可以使用一个正常的表单来提交一个文件到指定的端点。上传的文件将在’CATALINA_HOME/bin/uploads’中提供。
3.7. The HandlerExceptionResolver Interface
3.7.HandlerExceptionResolver 接口
Spring’s HandlerExceptionResolver provides uniform error handling for an entire web application, a single controller, or a set of controllers.
Spring的HandlerExceptionResolver为整个Web应用、单个控制器或一组控制器提供统一的错误处理。
To provide application-wide custom exception handling, create a class annotated with @ControllerAdvice:
为了提供应用范围内的自定义异常处理,创建一个带有@ControllerAdvice注释的类。
@ControllerAdvice
public class ExampleGlobalExceptionHandler {
@ExceptionHandler
@ResponseBody
public String handleExampleException(Exception e) {
// ...
}
}
Any methods within that class annotated with @ExceptionHandler will be available on every controller within dispatcher’s area of responsibility.
该类中任何带有@ExceptionHandler注释的方法都将在调度员负责的区域内的每个控制器上可用。
Implementations of the HandlerExceptionResolver interface in the DispatcherServlet’s ApplicationContext are available to intercept a specific controller under that dispatcher’s area of responsibility whenever @ExceptionHandler is used as an annotation, and the correct class is passed in as a parameter:
DispatcherServlet的ApplicationContext中的HandlerExceptionResolver接口的实现可用于拦截该dispatcher职责范围内的特定控制器,只要@ExceptionHandler被用作注释,并且正确的类被作为参数传递进来。
@Controller
public class FooController{
@ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
// ...
}
// ...
}
The handleException() method will now serve as an exception handler for FooController in our example above if either exception CustomException1 or CustomException2 occurs.
handleException()方法现在将作为FooController的异常处理程序,在我们上面的例子中,如果CustomException1或CustomException2发生异常。
Here’s an article that goes more in-depth about exception handling in a Spring web application.
这里有一篇文章更深入地介绍了Spring Web应用程序中的异常处理。
4. Conclusion
4.结论
In this tutorial, we’ve reviewed Spring’s DispatcherServlet and several ways to configure it.
在本教程中,我们回顾了Spring的DispatcherServlet以及配置它的几种方法。
As always, the source code used in this tutorial is available over on Github.
一如既往,本教程中所使用的源代码可在Github上获得。