Java进击框架:Spring-Web(八)
- 前言
- DispatcherServlet
- 拦截器
- 异常
- 视图解析
- 重定向
- 转发
- 语言环境
- 日志
- 过滤器
- 带注释的控制器
- 声明
- 映射请求
- 其它注解
- 验证
- 功能性端点
- URI Links
- 异步请求
- CORS
- HTTP缓存
- 视图技术
- MVC配置
- 其他Web框架
前言
Spring Web MVC是构建在Servlet API上的原始Web框架,从一开始就包含在Spring框架中。正式名称“Spring Web MVC”来自其源模块的名称(spring-webmvc),但更通俗的说法是“springMVC”。Spring Framework 5.0引入了一个反应式堆栈Web框架,它的名字“Spring WebFlux”也是基于它的源模块(spring-webflux)。
DispatcherServlet
与许多其他web框架一样,Spring MVC是围绕前端控制器模式设计的,其中中心Servlet DispatcherServlet为请求处理提供共享算法,而实际工作由可配置的委托组件执行。这个模型是灵活的,并且支持不同的工作流。
DispatcherServlet和任何Servlet一样,需要通过使用Java配置或在web.xml中根据Servlet规范声明和映射。
- Java配置示例示例代码如下:
@Configuration
//启用 Spring MVC
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
// 配置静态资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/");
}
// 配置视图解析器
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");//前缀
resolver.setSuffix(".jsp");//后缀
return resolver;
}
}
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 创建 AnnotationConfigWebApplicationContext 并注册配置类
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MyWebMvcConfig.class);
// 创建 DispatcherServlet 并注册到 ServletContext
DispatcherServlet dispatcherServlet = new DispatcherServlet(ctx);
//向 ServletContext 中注册了一个名为 "app" 的 Servlet
ServletRegistration.Dynamic registration = servletContext.addServlet("app", dispatcherServlet);
//表示在容器启动时就立即初始化并加载这个 Servlet
registration.setLoadOnStartup(1);
//将这个Servlet映射到根路径("/")。
//这意味着当用户访问应用的根路径时(例如 http://localhost:8080/),容器会将请求交给这个注册的 Servlet 来处理。
registration.addMapping("/");
}
}
- 传统Java Web项目,
web.xml
配置示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置DispatcherServlet所需的bean和其他相关内容
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 其他配置 -->
</beans>
在Servlet环境中,您可以选择以编程方式配置Servlet容器,作为替代或与web.xml
文件结合使用。
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 创建 XmlWebApplicationContext 并注册配置类
XmlWebApplicationContext ctx = new XmlWebApplicationContext();
ctx.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
// 创建 DispatcherServlet 并注册到 ServletContext
DispatcherServlet dispatcherServlet = new DispatcherServlet(ctx);
//向 ServletContext 中注册了一个名为 "app" 的 Servlet
ServletRegistration.Dynamic registration = servletContext.addServlet("app", dispatcherServlet);
//表示在容器启动时就立即初始化并加载这个 Servlet
registration.setLoadOnStartup(1);
//将这个Servlet映射到根路径("/")。
//这意味着当用户访问应用的根路径时(例如 http://localhost:8080/),容器会将请求交给这个注册的 Servlet 来处理。
registration.addMapping("/");
}
}
- 路径匹配
Servlet API将完整的请求路径公开为requestURI,并进一步将其细分为contextPath、servletPath和pathInfo,它们的值根据Servlet的映射方式而变化。从这些输入中,Spring MVC需要确定用于映射处理程序的查找路径,这应该排除contextPath和任何servletMapping前缀(如果适用)。
路径可能包含编码的保留字符,如“/”
或“;”
,这些字符在解码后会改变路径的结构,这也会导致安全问题。
如果DispatcherServlet被映射为默认的带有“/”
或不带“/*”
前缀的Servlet,并且Servlet容器是4.0+,那么Spring MVC能够检测Servlet映射类型并避免使用servletPath和pathInfo。幸运的是,默认的Servlet映射“/”
是一个不错的选择
上述问题在使用PathPatternParser和解析模式时得到解决,作为使用AntPathMatcher进行字符串路径匹配的替代方案。PathPatternParser从5.3版开始就可以在Spring MVC中使用,并且从6.0版开始默认启用。
拦截器
在 Spring MVC 中,您可以使用 HandlerInterceptor 来实现对请求处理过程的拦截和控制。当您希望将特定功能应用于某些请求时,这些拦截器非常有用——例如,检查主体。
HandlerInterceptor 接口定义了三个方法,分别是 preHandle()
、postHandle()
和 afterCompletion()
,它们分别在请求处理之前、请求处理之后以及请求完成之后被调用。
(1)preHandle(..)
方法
在实际处理程序运行之前,返回一个布尔值。可以使用此方法中断或继续执行链的处理。当此方法返回true时,处理程序执行链继续。当它返回false时,则中断执行链,不会继续执行后续的拦截器或目标方法。可以用于进行登录验证、权限检查等操作。
(2)postHandle(..)
方法
在处理程序运行之后。可以用于添加公共的模型数据、进行日志记录等操作。
(3)afterCompletion(..)
方法
在整个请求处理完成之后被调用,即在视图渲染完毕之后。通常用于进行资源清理操作,例如释放资源、记录执行时间等。
示例代码如下:
@Controller
public class MyController {
@PostMapping("/api")
@ResponseBody
public void a(){
System.out.println("进来了");
/** Output:
* 请求前
* 进来了
* 请求后
* 请求完成之后
*/
}
}
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("请求前");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("请求后");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("请求完成之后");
}
}
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyHandlerInterceptor())
.addPathPatterns("/api/**") // 需要拦截的路径
.excludePathPatterns("/api/login"); // 不需要拦截的路径;
}
}
异常
如果在请求映射期间发生异常或从请求处理程序(如@Controller
)抛出异常,则DispatcherServlet将委托给HandlerExceptionResolver bean链来解决异常并提供替代处理,这通常是一个错误响应。下表列出了可用的HandlerExceptionResolver实现:
- SimpleMappingExceptionResolver:异常类名和错误视图名之间的映射。对于在浏览器应用程序中呈现错误页非常有用。
示例代码如下:
@Configuration
public class Config {
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
// 设置异常映射 可定义不同错误类型条装不同页面
Properties exceptionMappings = new Properties();
exceptionMappings.setProperty("java.lang.RuntimeException", "error");
exceptionMappings.setProperty("java.lang.NullPointerException", "error2");
resolver.setExceptionMappings(exceptionMappings);
// 设置默认错误视图
resolver.setDefaultErrorView("/error");
return resolver;
}
}
@Controller
public class MyController {
@GetMapping(value = "/api")
public void api(){
//测试返回异常,跳转错误页面
throw new NullPointerException();
}
}
web.xml
中声明一个错误页映射。下面的例子展示了如何这样做:
<error-page>
<location>/error</location>
</error-page>
当出现异常或响应具有错误状态时,Servlet容器在容器内将error分派到配置的URL(例如,/error
)。然后由DispatcherServlet处理,可能将其映射到一个@Controller
,它可以被实现为返回一个带有模型的错误视图名称或呈现一个JSON响应,如下面的示例所示:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
return map;
}
}
- DefaultHandlerExceptionResolver:Spring MVC默认的异常处理器,解决Spring MVC引发的异常,并将它们映射到HTTP状态代码。
示例代码如下:
@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = { Exception.class })
protected ResponseEntity<Object> handleCustomException(Exception ex, WebRequest request) {
String errorMessage = "entity发生异常:" + ex.getMessage();
return handleExceptionInternal(ex, errorMessage, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
}
执行结果
继承了 ResponseEntityExceptionHandler,使用 @ControllerAdvice
注解将其标记为一个全局异常处理器。自定义方法使用 @ExceptionHandler
注解来指定需要处理的异常类型,在方法内部,我们可以根据具体的业务需求自定义返回的 HTTP 响应。在这个例子中,我们调用父类handleExceptionInternal()
方法,返回了一个具有自定义消息和状态码的 ResponseEntity 对象。
- ResponseStatusExceptionResolver:使用
@ResponseStatus
注释解析异常,并根据注释中的值将异常映射到HTTP状态码。
示例代码如下:
@ResponseStatus(value = HttpStatus.BAD_REQUEST,reason = "自定义错误异常")
public class MyException extends RuntimeException{
}
@Controller
public class MyController {
@GetMapping(value = "/api")
public void api(){
throw new MyException();
}
}
执行结果
- ExceptionHandlerExceptionResolver:通过调用
@Controller
或@ControllerAdvice
类中的@ExceptionHandler
方法来解决异常。
示例代码如下:
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
// 处理异常逻辑 可以根据异常类型、业务需求等进行相应的处理
// 返回自定义的响应
String errorMessage = "发生异常:" + ex.getMessage();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorMessage);
}
}
@Controller
public class MyController {
@GetMapping(value = "/api")
public void api(){
throw new NullPointerException("空指针错误");
}
}
执行结果
您可以通过在Spring配置中声明多个HandlerExceptionResolver bean并根据需要设置它们的顺序属性来形成异常解析器链。order属性越高,异常解析器的位置就越靠后。
@Configuration
public class ExceptionResolverConfig {
@Bean
public HandlerExceptionResolver myExceptionResolver() {
MyExceptionResolver resolver = new MyExceptionResolver();
resolver.setOrder(1); // 设置执行顺序为1
return resolver;
}
@Bean
public HandlerExceptionResolver anotherExceptionResolver() {
AnotherExceptionResolver resolver = new AnotherExceptionResolver();
resolver.setOrder(2); // 设置执行顺序为2
return resolver;
}
}
HandlerExceptionResolver的契约规定它可以返回:
- 指向错误视图的ModelAndView。
- 如果在解析器中处理异常,则为空ModelAndView。
- 如果异常仍然无法解决,则为null,供后续解析器尝试,如果异常仍然存在,则允许它冒泡到Servlet容器中。
MVC配置自动为默认Spring MVC异常、@ResponseStatus
注释异常和@ExceptionHandler
方法的支持声明内置解析器。您可以自定义该列表或替换它。
示例代码如下:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ModelAndView handleNotFoundException(NotFoundException ex) {
ModelAndView modelAndView = new ModelAndView("error404");
modelAndView.addObject("message", ex.getMessage());
return modelAndView;
}
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex) {
ModelAndView modelAndView = new ModelAndView("error500");
modelAndView.addObject("message", ex.getMessage());
return modelAndView;
}
}
上述代码使用注解的方式定义不同错误类型,跳转不同页面,这样的好处就是提高代码阅读性。
视图解析
Spring MVC定义了ViewResolver和View允许您在浏览器中呈现模型的界面,而不会将您束缚于特定的视图技术。ViewResolver提供视图名称和实际视图之间的映射。View在移交给特定的视图技术之前,解决数据准备问题。
以下是几个常用的 ViewResolver 实现类:
- UrlBasedViewResolver:用于解析基于 URL 的视图。它会根据配置的前缀和后缀,将逻辑视图名称转换为完整的 URL。例如,逻辑视图名称为
"redirect:/home"
,则最终的视图为重定向到"/home"
的 URL。
示例代码如下:
@Configuration
public class WebConfig {
@Bean
public UrlBasedViewResolver viewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
// 设置视图类
resolver.setViewClass(JstlView.class);
// 设置前缀和后缀
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
JstlView.class
是 Spring 框架中的一个视图类,用于解析 JSP 页面并渲染视图。在使用 JSP 技术时,可以使用 JstlView 来渲染 JSP 视图。
- InternalResourceViewResolver:用于解析 JSP 视图。它会根据配置的前缀和后缀,将逻辑视图名称转换为 JSP 文件路径。例如,逻辑视图名称为
"home"
,配置的前缀为"/WEB-INF/views/"
,后缀为".jsp"
,则最终的视图路径为"/WEB-INF/views/home.jsp"
。
示例代码如下:
@Configuration
public class WebConfig {
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
// 设置视图类
resolver.setViewClass(JstlView.class);
、 // 设置前缀和后缀
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
- FreeMarkerViewResolver:用于解析 FreeMarker 模板视图。它会根据配置的前缀和后缀,将逻辑视图名称转换为 FreeMarker 模板文件路径。
示例代码如下:
@Configuration
public class WebConfig {
@Bean
public FreeMarkerViewResolver viewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
// 设置前缀和后缀
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".ftl");
// 设置视图类
resolver.setViewClass(FreeMarkerView.class);
return resolver;
}
}
我们通过 resolver.setViewClass(FreeMarkerView.class)
设置视图类为 FreeMarkerView,以告知 Spring MVC 使用 FreeMarker 引擎来渲染模板视图。
- BeanNameViewResolver:Spring MVC 中一个特殊的视图解析器,它会根据视图名称查找对应的 View 实例。与其他视图解析器不同,BeanNameViewResolver 不依赖于视图文件或模板,而是通过访问 Spring 容器中的 bean 来返回相应的视图。
示例代码如下:
@Configuration
public class WebConfig {
@Bean
public BeanNameViewResolver viewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
return resolver;
}
}
@Component("myView")
public class MyView implements View {
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
}
}
通过 BeanNameViewResolver 创建一个视图解析器。BeanNameViewResolver 将会找到名为 "myView"
的 Bean,并将其作为视图进行渲染。
- ContentNegotiatingViewResolver:用于根据请求的媒体类型(如 Accept 头部)选择合适的视图来进行内容协商。它可以根据请求的媒体类型,将控制器方法返回的模型数据渲染到不同的视图上,以满足客户端的需求。
示例代码如下:
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
@Bean
public ContentNegotiatingViewResolver contentNegotiatingViewResolver() {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
// 设置视图解析器列表
List<ViewResolver> resolvers = new ArrayList<>();
// 添加其他视图解析器,如 InternalResourceViewResolver、FreeMarkerViewResolver 等
resolvers.add(internalResourceViewResolver());
resolvers.add(freeMarkerViewResolver());
resolver.setViewResolvers(resolvers);
return resolver;
}
}
ContentNegotiatingViewResolver本身不解析视图,而是委托给其他视图解析器,并选择与客户端请求的表示相似的视图。表示形式可以从Accept报头或查询参数(例如,"/path?format=pdf"
)确定。
您可以通过声明多个解析器bean来链接视图解析器,如果有必要,还可以通过设置order属性来指定顺序。记住,order属性越高,视图解析器在链中的位置就越靠后。
重定向
视图名称中的特殊redirect:
前缀允许您执行重定向。UrlBasedViewResolver(及其子类)将此识别为需要重定向的指令。视图名称的其余部分是重定向URL。
@Controller
public class MyController {
@GetMapping(value = "/api")
public String api(){
return "redirect:http://www.baidu.com";
}
}
最终效果与控制器返回了RedirectView相同,但现在控制器本身可以根据逻辑视图名称进行操作。逻辑视图名称(如redirect:/myapp/some/resource
)相对于当前Servlet上下文重定向,而名称(如redirect:https://myhost.com/some/arbitrary/path
)重定向到绝对URL。
转发
你也可以为最终由UrlBasedViewResolver和子类解析的视图名使用一个特殊的forward:
前缀。
@Controller
public class MyController {
@GetMapping(value = "/api")
public String api(){
return "forward:page";
}
@RequestMapping(value = "/page")
public String page() {
return "defaultError";
}
}
转发是在服务器内部完成的,请求从一个控制器或处理方法直接转发到另一个控制器或处理方法,客户端浏览器感知不到这个过程,URL 不会发生变化。而重定向是通过发送一个新的HTTP请求给客户端浏览器,然后由浏览器重新发起该请求到新的URL,URL会发生变化。
转发可以在请求之间共享数据,而重定向不能直接在请求之间共享数据。参数传递可以通过HttpServletRequest对象的setAttribute()
方法将参数设置到请求属性中,然后在转发的目标控制器或处理方法中获取该属性值。示例代码如下:
@Controller
public class MyController {
@GetMapping(value = "/api")
public String api(HttpServletRequest request){
request.setAttribute("param","1");
return "forward:page";
}
@RequestMapping(value = "/page")
public String handle(HttpServletRequest request) {
Object attribute = request.getAttribute("param");
System.out.println(attribute);
return "defaultError";
}
}
语言环境
Spring架构的大多数部分都支持国际化,就像Spring web MVC框架一样。当一个请求进来时,DispatcherServlet寻找一个语言环境解析器,如果找到了,它就尝试使用它来设置语言环境。通过使用RequestContext.getLocale()
方法,您总是可以检索由语言环境解析器解析的语言环境。
@Controller
public class MyController {
@GetMapping(value = "/api")
public void api(HttpServletRequest request){
Locale locale = request.getLocale();
System.out.println(locale.getDisplayCountry());
System.out.println(locale.getCountry());
System.out.println(locale.getDisplayName());
System.out.println(locale.getDisplayLanguage());
/** Output:
* 中国
* CN
* 中文 (中国)
* 中文
*/
}
}
- Cookie解析器
通过使用该区域设置解析器的属性,您可以指定cookie的名称以及最长使用时间。下面的例子定义了一个CookieLocaleResolver:
@Configuration
public class WebConfig {
@Bean
public CookieLocaleResolver localeResolver() {
CookieLocaleResolver resolver = new CookieLocaleResolver();
resolver.setCookieName("myLocaleCookie"); // 设置cookie的名称
resolver.setCookieMaxAge(3600); // 设置cookie的最长期限,单位为秒
return resolver;
}
}
- Session解析器
相比CookieLocaleResolver,该策略将本地选择的语言环境设置存储在Servlet容器的HttpSession。因此,这些设置对于每个会话都是临时的,因此在每个会话结束时会丢失。
注意,它与外部会话管理机制没有直接关系,比如Spring Session项目。这SessionLocaleResolver计算并修改相应的HttpSession属性与当前HttpServletRequest。
@Configuration
public class WebConfig {
@Bean
public SessionLocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.ENGLISH); // 设置默认语言环境
return resolver;
}
}
- Multipart 解析器
MultipartResolver是一种用于解析多部分请求(包括文件上传)的策略。有一个基于容器的用于Servlet多部分请求解析的StandardServletMultipartResolver实现。
@Controller
public class MyController {
@GetMapping(value = "/api")
public void api(HttpServletRequest request){
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
if (resolver.isMultipart(request)) {
// 将request转换为MultipartHttpServletRequest
MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);
// 获取上传的文件
MultipartFile file = multipartRequest.getFile("file");
// 获取表单中的其他参数
String description = multipartRequest.getParameter("description");
// 处理上传的文件
if (file != null) {
String fileName = file.getOriginalFilename();
try {
byte[] bytes = file.getBytes();
} catch (IOException e) {
throw new RuntimeException(e);
}
// 处理上传的文件数据
}
// 处理表单中的其他参数
}
}
要启用多部分处理,您需要在DispatcherServlet Spring配置中声明一个名为MultipartResolver的MultipartResolver bean。DispatcherServlet检测它并将其应用于传入请求。当接收到内容类型为multipart/form-data
的POST时,解析器解析将当前HttpServletRequest包装为MultipartHttpServletRequest的内容,以提供对已解析文件的访问,并将部分作为请求参数公开。
@Configuration
public class MyWebMvcConfig {
@Bean
public StandardServletMultipartResolver standardServletMultipartResolver(){
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
return resolver;
}
}
启用MultipartResolver后,将解析带有多部分/
表单数据的POST请求的内容,并将其作为常规请求参数访问。以下示例访问一个常规表单字段和一个上传的文件:
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
将参数类型声明为List<MultipartFile>
允许为同一参数名解析多个文件。
日志
Spring MVC中的调试级日志被设计成紧凑、最小化和人性化的。它专注于反复使用的高价值信息,而不是仅在调试特定问题时有用的其他信息。
跟踪级日志记录通常遵循与调试相同的原则(例如,也不应该是消防水管),但可以用于调试任何问题。此外,一些日志消息在跟踪和调试时可能会显示不同级别的详细信息。
过滤器
spring-web模块提供了一些有用的过滤器:数据格式、Forwarded Headers。
- 格式数据
浏览器只能通过HTTP GET或HTTP POST提交表单数据,Servlet API要求ServletRequest.getParameter*()
方法只支持HTTP POST的表单字段访问。
FormContentFilter来拦截内容类型为application/x-www-form-urlencoded的HTTP PUT、PATCH和DELETE请求,从请求体中读取表单数据,并包装ServletRequest,使表单数据通过ServletRequest. getparameter *()
系列方法可用。
@Controller
public class MyController {
@PostMapping(value = "/api")
@ResponseBody
public void api(HttpServletRequest request){
String name = request.getParameter("name");
String user = request.getParameter("user");
}
}
- Forwarded Headers
当请求经过诸如负载平衡器之类的代理时,主机、端口和模式可能会发生变化,这使得从客户端角度创建指向正确的主机、端口和模式的链接成为一项挑战。
RFC 7239定义了转发的HTTP报头,代理可以使用它来提供有关原始请求的信息。
还有其他非标准头,包括X-Forwarded-Host, X-Forwarded-Port,X-Forwarded-Proto, X-Forwarded-Ssl,以及X-Forwarded-Prefix。
(1)X-Forwarded-Host:该字段用于指示原始请求中的主机名。例如,如果请求example.com/resource
被发送到代理,代理将请求转发到localhost:8080/resource
,然后是标题为X-Forwarded-Host: example.com
可以发送通知服务器原始主机example.com
。
X-Forwarded-Host: example.com
(2)X-Forwarded-Port:该字段用于指示原始请求中的端口号。
X-Forwarded-Port: 443
(3)X-Forwarded-Proto:该字段用于指示原始请求中的协议。
X-Forwarded-Proto: https
(4)X-Forwarded-Ssl:该字段用于指示原始请求是否通过SSL进行加密。它可以有两个值,on
表示通过SSL加密,off
表示没有通过SSL加密。
X-Forwarded-Ssl: on
(5)X-Forwarded-Prefix:该字段用于指示原始请求中的URL前缀。
X-Forwarded-Prefix: /app
ForwardedHeaderFilter是一个Servlet过滤器,它可以修改请求,由于应用程序无法知道报头是由代理按预期添加的,还是由恶意客户端添加的,因此转发的报头需要考虑安全性。这就是为什么信任边界处的代理应该被配置为移除不信任的代理Forwarded来自外部的标题。您还可以配置ForwardedHeaderFilter随着removeOnly=true
,在这种情况下,它会移除但不使用标头。
带注释的控制器
Spring MVC提供了一个基于注释的编程模型,其中@Controller
和@RestController
组件使用注释来表达请求映射、请求输入、异常处理等等。以下示例显示了由批注定义的控制器:
@Controller
public class MyController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
需要注意的是,从Spring 4.3版本开始,
@Controller
注解的value属性已被废弃,推荐使用@RequestMapping
注解来指定控制器的映射路径。
声明
您可以在Servlet的WebApplicationContext中使用标准的Spring bean定义来定义控制器bean。@Controller
构造型允许自动检测,与Spring在类路径中检测@Component
类并为它们自动注册bean定义的通用支持一致。它还充当带注释的类的原型,指示其作为web组件的角色。
要启用@Controller
bean的自动检测,您可以在Java配置中添加组件扫描,如下面的示例所示:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
XML配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.web"/>
</beans>
@RestController
是一个复合注释,它本身带有@Controller
和@ResponseBody
的元注释,以表明控制器的每个方法都继承了类型级的@ResponseBody
注释,因此,直接写入响应体,而不是使用HTML模板进行视图解析和呈现。
在某些情况下,例如当控制器上标注了@Transactional
注解时,你可能需要使用AOP代理来修饰控制器。特别是对于控制器,建议使用基于类的代理。
如果控制器实现了一个接口,并且需要AOP代理,你可能需要显式地配置基于类的代理。例如,在使用@EnableTransactionManagement
注解启用事务管理时,你可以将proxyTargetClass属性设置为true,以明确指定使用基于类的代理。示例如下:
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class AppConfig {
// 配置其他的Bean
}
XML配置:
<tx:annotation-driven proxy-target-class="true"/>
启用基于类的代理可能会影响性能,因此建议仅在需要使用AOP代理时才将其设置为true。
映射请求
您可以使用@RequestMapping
将请求映射到控制器方法的注释。它有各种属性,可以通过URL、HTTP方法、请求参数、头和媒体类型进行匹配。您可以在类级别使用它来表达共享映射,或者在方法级别使用它来缩小到特定的端点映射。
还有特定于HTTP方法的快捷方式变体@RequestMapping
:@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
、@PatchMapping
:
@RestController
@RequestMapping("/api")
public class MyController {
@GetMapping("/resource")
public String getResource() {
// 处理GET请求
return "Get resource";
}
@PostMapping("/resource")
public String createResource() {
// 处理POST请求
return "Create resource";
}
}
@RequestMapping
可以使用method
属性来指定可以处理的HTTP请求方法,如果不指定method
属性,那么该方法将处理所有的HTTP请求方法。
@RequestMapping(value = "/example", method = RequestMethod.GET)
public String exampleGetMethod() {
// 处理GET请求
return "get_response";
}
@RequestMapping
可以使用URL模式映射方法。有两种选择:
- PathPattern—与URL路径匹配的预解析模式,也预解析为PathContainer。这个解决方案是为web使用而设计的,可以有效地处理编码和路径参数,并高效地进行匹配。
@RestController
@RequestMapping("/api")
public class MyController {
@RequestMapping(path = "/users/{id}")
public String getUserById(@PathVariable("id") String id) {
// 处理根据用户ID获取用户的请求
return "User: " + id;
}
}
从请求的URL路径中提取参数,可以通过@PathVariable
,例如:
@RestController
@RequestMapping("/api")
public class MyController {
@GetMapping("/user/{id}")
public String getUserById(@PathVariable("id") String id) {
// 处理根据用户ID获取用户的请求
return "User: " + id;
}
}
- AntPathMatcher—根据字符串路径匹配字符串模式。这是Spring配置中使用的原始解决方案,用于选择类路径、文件系统和其他位置上的资源。它效率较低,字符串路径输入对于有效处理编码和URL的其他问题是一个挑战。
@RestController
@RequestMapping("/api")
public class MyController {
@RequestMapping(path = "/users/*")
public String getUsers() {
// 处理获取所有用户的请求
return "All users";
}
}
其中,"*"
是通配符,可以匹配任意字符。建议使用PathPattern进行URL模式匹配。如果你在其他场景下进行资源选择,可以使用AntPathMatcher进行字符串路径模式匹配。
一些示例模式:
"/resources/ima?e.png"-匹配路径段中的一个字符
"/resources/*.png"-匹配路径段中的零个或多个字符
"/resources/**"-匹配多个路径段
"/projects/{project}/versions"-匹配路径段并将其作为变量捕获
"/projects/{project:[a-z]+}/versions"-用正则表达式匹配和捕获变量
除此之外,你还可以在Spring MVC中控制请求和响应的媒体类型:
consumes
属性:用于指定控制器方法可以接受的请求内容类型。它可以限制请求的Content-Type头必须匹配指定的值才能触发该方法。
@PostMapping(path = "/pets", consumes = "application/json")
public void createPet(@RequestBody Pet pet) {
// 处理创建宠物的请求
}
produces
属性:用于指定控制器方法返回的响应内容类型。它可以限制响应的Content-Type头必须匹配指定的值。
@GetMapping(path = "/pets", produces = "application/json")
public List<Pet> getPets() {
// 处理获取宠物列表的请求
return petService.getPets();
}
MediaType为常用的媒体类型提供常数,例如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE。
作为@RequestMapping
的替代方案,您还可以使用@HttpExchange
方法处理请求。这些方法是在HTTP接口上声明的,可以通过HttpServiceProxyFactory作为客户端使用,也可以由服务器@Controller
实现。
public interface ExampleHttpService {
@HttpExchange(method = HttpMethod.GET, path = "/example")
String exampleGetMethod();
@HttpExchange(method = HttpMethod.POST, path = "/example")
String examplePostMethod(String requestBody);
}
public class MyController {
public static void main(String[] args) {
ExampleHttpService httpService = HttpServiceProxyFactory.create(ExampleHttpService.class, "http://localhost:8080");
String response = httpService.exampleGetMethod();
System.out.println(response);
}
}
需要spring版本号6以上,才能使用。
其它注解
- 请求参数
@RequestParam
注解:是Spring框架中用于从请求中获取参数的注解,通常用于处理HTTP请求中的查询参数(query parameter)或表单参数(form parameter)。
@GetMapping("/example")
public String handleRequest(@RequestParam("name") String name, @RequestParam("age") int age) {
}
@RequestHeader
注解:从HTTP请求头中获取值的注解。通常情况下,它用于在处理HTTP请求时,将请求头中的信息映射到处理方法的参数上。
@GetMapping("/example")
public String handleRequest(@RequestHeader("User-Agent") String userAgent) {
}
@CookieValue
注解:用于从HTTP请求的Cookie中获取值的注解。
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
@GetMapping("/example")
public String handleRequest(@CookieValue("JSESSIONID") String jsessionId) {
}
@RequestBody
注解:用于接收HTTP请求体中的数据的注解。
@PostMapping("/example")
public String handleRequest(@RequestBody User user) {
}
@RequestAttribute
注解:用于从当前请求的attributes
中获取值。
@GetMapping("/example")
public String handleRequest(@RequestAttribute("attributeName") String attributeValue) {
}
@SessionAttribute
注解:获取会话中存储的属性值。
@GetMapping("/user")
public String getUserInfo(@SessionAttribute("user") User user) {
// 处理方法的逻辑
}
@ModelAttribute
注解:可以将请求参数自动绑定到一个 Java 对象上。
@Controller
public class UserController {
@GetMapping("/user")
public String getUser(@RequestParam("id") Long id, Model model) {
// 根据 id 查询用户信息
User user = userService.getUserById(id);
model.addAttribute("user", user);
return "user";
}
@PostMapping("/user/save")
public String saveUser(@ModelAttribute("user") User user) {
// 处理保存用户信息的逻辑
userService.saveUser(user);
return "redirect:/user?id=" + user.getId();
}
}
- 返回值
@ResponseBody
注解:告诉Spring框架,响应体的返回不是一个视图,而是对象数据本身。
@GetMapping("/user/{id}")
@ResponseBody
public User getUser(@PathVariable("id") Long id) {
User user = userService.getUserById(id);
return user;
}
@ControllerAdvice
是一个 Spring 框架的注解,用于定义全局控制器增强器。它可以用于集中管理和处理多个控制器共享的逻辑,例如异常处理、全局数据绑定、全局模型属性等。
@ControllerAdvice
public class GlobalControllerAdvice {
@ExceptionHandler(Exception.class)
public ModelAndView handleException(Exception ex) {
// 处理全局异常
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
modelAndView.addObject("errorMessage", ex.getMessage());
return modelAndView;
}
}
你也可以使用@RestControllerAdvice
注解。
- 类型转换
类型转换中的一个实际问题是空字符串源值的处理。如果该值变为,则该值被视为缺失null作为类型转换的结果。如果你想允许null要被注入,要么使用required标志,或者将参数声明为@Nullable
。
@PostMapping
public void createUser(@RequestBody @Nullable User user) {
}
- 矩阵变量
矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔(例如,/cars;color=red,green;year=2012
).也可以通过重复的变量名指定多个值(例如,color=red;color=green;color=blue)。
@GetMapping("/hello/{id}")
@ResponseBody
public void hello(@PathVariable("id") String id, @MatrixVariable(name = "param", required = false) String param) {
System.out.println(id);
System.out.println(param);
}
在上面的示例中,{id}
表示路径变量,而param
表示矩阵变量。没有提供param
矩阵变量,那么param
方法参数将为null。你可以发送以下请求来调用hello方法:
GET /hello/123;param=value
- Flash属性
闪存属性为一个请求提供了一种方法来存储要在另一个请求中使用的属性。这是重定向时最常见的需要,例如,Post-Redirect-Get模式。闪存属性在重定向之前被临时保存(通常在会话中),以便在重定向之后可用于请求,并且被立即移除。
@GetMapping("/login")
public String login(RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("message", "Login successful!");
return "redirect:/dashboard";
}
@GetMapping("/dashboard")
public String dashboard(Model model) {
return "dashboard";
}
在上面的代码中,login()
方法将重定向参数 "message": "Login successful!"
添加到 FlashMap 中,并使用 redirect:/dashboard
进行重定向。在 dashboard()
方法中,可以使用 Model 来访问这个重定向参数:
flash属性的概念存在于许多其他web框架中,并且已经证明有时会暴露于并发问题。这是因为,根据定义,闪存属性将存储到下一次请求。然而,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,闪存属性被过早地移除。
- @InitBinder
@InitBinder
注解用于初始化 Web 数据绑定器,可以用来定制数据绑定过程。通常情况下,它用于注册自定义的属性编辑器,以便将请求参数转换为控制器方法所需的对象类型。
@Controller
public class UserController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@PostMapping("/user/save")
public String saveUser(@ModelAttribute("user") User user) {
// 保存用户信息的逻辑
userService.saveUser(user);
return "redirect:/user";
}
}
在这个例子中,我们创建了一个 SimpleDateFormat 对象,并将其注册为处理 Date 类型的参数。这样做可以确保当控制器方法接收到日期类型的参数时,能够正确地进行数据绑定。
验证
@Validated
是一个 Spring 框架的注解,用于在控制器方法参数上启用参数校验。它可以与 javax.validation
标准规范中的注解一起使用,例如 @NotNull
、@Size
、@Pattern
等,用于对方法参数进行验证。
public class User {
@NotNull
private String name;
@Min(18)
private int age;
// getter setter ...
}
@RestController
public class MyController {
@PostMapping("/data")
public ResponseEntity<String> processData(@RequestBody @Valid DataObject data) {
// 处理数据
return ResponseEntity.ok("Data processed successfully");
}
}
功能性端点
Spring Web MVC包括WebMvc.Fn是一种轻量级的函数式编程模型,其中函数用于路由和处理请求,契约被设计为不变性。它是基于注释的编程模型的替代方案,但运行在相同的DispatcherServlet上。它可以作为一个 Lambda 表达式或方法引用传递给 Spring WebFlux 框架中的路由定义,用于定义在特定请求路径下执行的处理程序。
RouterFunction相当于@RequestMapping
注释,但主要区别在于路由器功能不仅提供数据,还提供行为。RouterFunctions.route()
提供有助于创建路由器的路由器生成器,如下例所示:
RouterFunction<ServerResponse> build = RouterFunctions.route()
.GET("/hello", request -> ServerResponse.ok().body(BodyInserters.fromValue("Hello World!")))
.build();
ServerRequest
和ServerResponse
是不可变的接口,提供对HTTP请求和响应的JDK 8友好访问,包括头、主体、方法和状态代码。
- 服务器请求
ServerRequest提供对HTTP方法、URI、标头和查询参数的访问,而对正文的访问通过body方法。
获取路径参数,假设请求的路径为 /users/{id}
,则可以使用 pathVariable()
方法提取路径中的 {id}
参数。
String id = request.pathVariable("id");
获取查询参数,返回一个 MultiValueMap 对象。可以使用 get()
方法来获取特定参数名的参数值。
MultiValueMap<String, String> queryParams = request.queryParams();
List<String> names = queryParams.get("name");
获取表单参数,获取表单参数。
Mono<MultiValueMap<String, String>> formData = request.formData();
Mono<String> username = formData.map(data -> data.getFirst("username"));
- 服务器响应
ServerResponse提供对HTTP响应的访问,因为它是不可变的,所以您可以使用build
方法来创建它。您可以使用生成器来设置响应状态、添加响应标题或提供正文。
创建一个包含JSON内容的200 (OK)响应:
ServerResponse response = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).build();
创建一个带有响应体的响应:
String message = "Hello, World!";
ServerResponse response = ServerResponse.ok().bodyValue(message);
创建一个自定义状态码的响应:
ServerResponse response = ServerResponse.status(HttpStatus.CREATED).build();
创建一个异常响应:
ServerResponse response = ServerResponse.status(HttpStatus.NOT_FOUND).bodyValue("Resource not found");
添加其他响应头或响应体:
ServerResponse response = ServerResponse.ok()
.header("Content-Type", "application/json")
.bodyValue("{\"message\": \"Hello, World!\"}");
完整示例代码如下:
@Configuration
public class Config {
@Bean
public RouterFunction<ServerResponse> route(MyHandler handler) {
return RouterFunctions.route()
.GET("/hello", handler::handleRequest)
.build();
}
}
@Component
public class MyHandler {
public Mono<ServerResponse> handleRequest(ServerRequest request) {
// 处理请求逻辑
String name = request.queryParam("name").orElse("Anonymous");
String message = "Hello, " + name + "!";
// 构建响应
return ServerResponse.ok().body(BodyInserters.fromValue(message));
}
public Mono<ServerResponse> handleNotFound(ServerRequest request) {
return ServerResponse.status(HttpStatus.NOT_FOUND).build();
}
}
在配置类中,使用 @Bean
注解定义了一个 RouterFunction<ServerResponse>
类型的 bean,请求/hello
路径时,会将请求映射到相应的处理器方法。在处理器类中,定义了一个 handleRequest()
方法,用于处理请求逻辑。
URI Links
UriComponentsBuilder帮助从带有变量的URI模板中构建URI,如下面的例子所示:
public static void main(String[] args) {
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http")//协议
.host("example.com")//主机
.path("/api/users")//路径
.queryParam("name", "john")//参数
.queryParam("age", 30)
.fragment("section-1")//片段通常用于指定文档内的特定位置或锚点
.build();
String uri = uriComponents.toUriString();
System.out.println(uri);
/** Output:
* http://example.com/api/users?name=john&age=30#section-1
*/
}
UriComponentsBuilder实现了UriBuilder。反过来,你可以用UriBuilderFactory创建一个UriBuilder。您可以使用UriBuilderFactory配置RestTemplate和WebClient来定制URL的准备。
配置RestTemplate的示例如下:
public class RestTemplateExample {
public static void main(String[] args) {
UriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://example.com");
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(uriBuilderFactory);
// 使用 RestTemplate 发起请求...
}
}
配置 WebClient:
public static void main(String[] args) {
UriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://example.com");
WebClient webClient = WebClient.builder()
.uriBuilderFactory(uriBuilderFactory)
.build();
// 使用 WebClient 发起请求...
}
另外,你也可以直接使用DefaultUriBuilderFactory。
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
UriComponentsBuilder在两个级别公开编码选项:
UriComponentsBuilder#encode()
:首先对URI模板进行预编码,然后在扩展时对URI变量进行严格编码。UriComponents#encode()
:编码URI组件在…之后URI变量被扩展。
这两个选项都用转义的八位字节替换非ASCII字符和非法字符。但是,第一个选项也会替换URI变量中出现的具有保留含义的字符。
public static void main(String[] args) {
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri();
System.out.println(uri);
/** Output:
* /hotel%20list/New%20York?q=foo%2Bbar
*/
}
您可以通过直接转到URI(这意味着编码)来缩短前面的示例:
public static void main(String[] args) {
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar");
//或者这样
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar");
System.out.println(uri);
/** Output:
* /hotel%20list/New%20York?q=foo%2Bbar
*/
}
WebClient和RestTemplate通过在内部扩展和编码URI模板UriBuilderFactory策略。两者都可以配置自定义策略,如下例所示:
public static void main(String[] args) {
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES);
// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
}
RestTemplate设置为EncodingMode.URI_COMPONENT出于历史原因和向后兼容性。WebClient依赖于中的默认值DefaultUriBuilderFactory,已由更改为EncodingMode.URI_COMPONENT在5.0.x到EncodingMode.TEMPLATE_AND_VALUES在5.1中。
DefaultUriBuilderFactory实施用途UriComponentsBuilder在内部扩展和编码URI模板。作为一个工厂,它提供了一个单一的位置来配置编码方法,基于以下编码模式之一:
-
TEMPLATE_AND_VALUES:用途
UriComponentsBuilder#encode()
对应于前面列表中的第一个选项,对URI模板进行预编码,并在扩展时对URI变量进行严格编码。 -
VALUES_ONLY:不对URI模板进行编码,而是通过对URI变量进行严格编码
UriUtils#encodeUriVariables
然后将它们扩展到模板中。 -
URI_COMPONENT:用途
UriComponents#encode()
对应于前面列表中的第二选项,对URI分量值进行编码在…之后URI变量被扩展。 -
NONE:不应用任何编码。
Spring MVC提供了一种机制来准备到控制器方法的链接。
@Controller
public class MyController {
@PostMapping(value = "/api/{id}")
@ResponseBody
public void api(@PathVariable String id) {
System.out.println("进来了");
}
@GetMapping(value = "/hello2")
@ResponseBody
public void hello() {
UriComponents api = MvcUriComponentsBuilder
.fromMethodName(MyController.class, "api", "21").build();
System.out.println(api.toUriString());
/** Output:
* http://localhost:8088/api/21
*/
}
}
我们使用 fromMethodName()
方法指定了 MyController类和 api方法,并传递了id参数值为 1。然后,我们可以通过调用 build()
构建URL。
异步请求
DeferredResult和Callable控制器方法中的返回值为单个异步返回值提供了基本支持。
- DeferredResult
Spring MVC 提供的一种异步处理机制。它是一个持有异步处理结果的容器,可以在处理请求时返回一个 DeferredResult 对象,然后在另一个线程中处理异步任务,并将结果设置到 DeferredResult 对象中,最终返回给客户端。
@RestController
public class MyController {
@GetMapping("/task")
public DeferredResult<String> handleTask() {
DeferredResult<String> deferredResult = new DeferredResult<>();
// 在另一个线程中执行异步任务
CompletableFuture.supplyAsync(() -> {
// 执行异步任务
String result = doSomething();
return result;
}).whenCompleteAsync((result, throwable) -> {
if (throwable != null) {
deferredResult.setErrorResult(throwable.getMessage());
} else {
deferredResult.setResult(result);
}
});
return deferredResult;
}
private String doSomething() {
// 执行异步任务的逻辑
return "task result";
}
}
使用 DeferredResult 还可以处理一些特殊的场景,例如:长时间的计算任务、文件上传等。
- Callable
Callable 是 Java 并发编程中的一个接口,用于表示一个可并发执行的任务。
@Controller
public class MyController {
@GetMapping(value = "/task")
@ResponseBody
public Callable<String> task() {
return ()-> doSomething();
}
private String doSomething() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 执行异步任务的逻辑
return "task result";
}
}
如果您想生成多个异步值并将它们写入响应,您可以使用ResponseBodyEmitter返回值以产生对象流,其中每个对象都用HttpMessageConverter并写入响应,如下例所示:
@Controller
public class MyController {
@GetMapping(value = "/task")
@ResponseBody
public ResponseBodyEmitter task() {
ResponseBodyEmitter responseBodyEmitter = new ResponseBodyEmitter();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
responseBodyEmitter.send(i);
Thread.sleep(1000);
}
responseBodyEmitter.complete(); // 发送完成事件
} catch (Exception e) {
responseBodyEmitter.completeWithError(e); // 发送错误事件
}
}).start();
/** Output:
* 0123456789
*/
return responseBodyEmitter;
}
}
当一个emitter抛出一个IOException(例如,如果远程客户端离开了),应用程序不负责清理连接,也不应该调用emitter.complete()
或者emitter.completeWithError()
。
- SSE
SSE 是一种基于 HTTP 协议的服务器推送技术,允许服务器向客户端发送异步的、持久化的消息流。其中从服务器发送的事件根据W3C SSE规范进行格式化。
@Controller
public class MyController {
@GetMapping(value = "/task")
@ResponseBody
public SseEmitter task() {
SseEmitter sseEmitter = new SseEmitter();
// 在新线程中生成事件和数据并发送给客户端
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
SseEmitter.SseEventBuilder event = SseEmitter.event()
.id(String.valueOf(i))
.data("Data " + i)
.comment("Comment " + i);
sseEmitter.send(event);
Thread.sleep(1000);
}
sseEmitter.complete(); // 发送完成事件
} catch (Exception e) {
sseEmitter.completeWithError(e); // 发送错误事件
}
}).start();
return sseEmitter;
}
}
客户端可以通过使用 JavaScript 中的 EventSource 对象来接收服务器发送的 SSE 事件和数据。例如:
const eventSource = new EventSource("/stream");
eventSource.onmessage = function(event) {
console.log(event.data);
};
eventSource.onerror = function(event) {
console.error("Error:", event);
};
eventSource.onopen = function() {
console.log("Connection opened.");
};
eventSource.onclose = function() {
console.log("Connection closed.");
};
当远程客户端离开时,Servlet API不提供任何通知。因此,在流式传输到响应时,无论是通过SseEmitter还是响应类型,定期发送数据是很重要的,因为如果客户端已断开连接,则写入将失败。发送可以采用空(仅注释)SSE事件的形式,也可以是另一方必须将其解释为心跳而忽略的任何其他数据。
或者,考虑使用具有内置心跳机制的web消息传递解决方案(例如STOMP over WebSocket或WebSocket with SockJS)。
CORS
跨域资源共享(Cross-Origin Resource Sharing, CORS)是由大多数浏览器实现的W3C规范,它允许您指定授权哪种跨域请求,而不是使用基于IFRAME或JSONP的不太安全且功能不太强大的解决方案。
要实现跨域访问,需要在服务端设置响应头信息。具体来说,需要在响应头中添加以下信息:
//允许访问的源地址
Access-Control-Allow-Origin: https://example.com
//允许访问的 HTTP 方法
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
//允许访问的请求头字段
Access-Control-Allow-Headers: Content-Type
//缓存预检请求的时间(单位:秒)
Access-Control-Max-Age: 3600
//是否允许发送 Cookie
Access-Control-Allow-Credentials: true
Spring MVC HandlerMapping实现提供了对CORS的内置支持。为了启用跨来源请求(即Origin头存在并且不同于请求的主机),您需要有一些显式声明的CORS配置。如果没有找到匹配的CORS配置,飞行前请求将被拒绝。
您可以在中组合全局CORS配置HandlerMapping更细粒度的处理程序级CORS配置。例如,带注释的控制器可以使用类级或方法级@CrossOrigin
注释(其他处理程序可以实现CorsConfigurationSource).
组合全局和本地配置的规则通常是相加的,例如,所有全局和所有本地起源。对于那些只能接受单个值的属性,例如allowCredentials和maxAge,局部值会覆盖全局值。
@RestController
public class MyController {
@CrossOrigin(origins = "https://example.com", maxAge = 3600)
@GetMapping("/data")
public Map<String, Object> getData() {
// ...
}
}
在上述示例中,我们在控制器方法(也可以在类)上添加了 @CrossOrigin
注解,指定了允许访问的来源地址和缓存预检请求的时间。
allowCredentials默认情况下不启用,因为这将建立一个公开敏感的用户特定信息(如cookies和CSRF令牌)的信任级别,并且只应在适当的时候使用。当它被启用时allowOrigins必须设置为一个或多个特定域(但不是特殊值"*"
)或者allowOriginPatterns属性可用于匹配一组动态原点。
除了细粒度的控制器方法级配置,您可能还想定义一些全局CORS配置,您可以使用CorsRegistry回调。
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/index/**")//开启跨域的接口路径,使用了 /** 通配符,表示所有以 /index/ 开头的接口都需要开启跨域访问。
.allowedOrigins("http://example.com")//允许跨域访问的来源地址。
.allowedMethods("GET", "POST")//允许的请求方法。
.allowedHeaders("*")//允许的请求头。
.allowCredentials(true)//开启了跨域请求的 Cookie 支持,
.maxAge(3600);// 使用 maxAge 方法设置了响应缓存的时间,以提高跨域请求的性能。
}
转换为XML配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:cors>
<mvc:mapping path="/index/**"/>
<mvc:allowed-origins>
<mvc:origin>http://example.com</mvc:origin>
</mvc:allowed-origins>
<mvc:allowed-methods>
<mvc:method>GET</mvc:method>
<mvc:method>POST</mvc:method>
</mvc:allowed-methods>
<mvc:allowed-headers>
<mvc:header>*</mvc:header>
</mvc:allowed-headers>
<mvc:allow-credentials>true</mvc:allow-credentials>
<mvc:max-age>3600</mvc:max-age>
</mvc:cors>
</beans>
你可以通过内置的CorsFilter应用CORS支持。
@Configuration
public class Config {
@Bean
public CorsFilter corsFilter() {
//创建了一个 CorsConfiguration 对象,并设置了允许携带凭证、允许的来源、请求头和请求方法。
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowCredentials(true);
corsConfig.addAllowedOrigin("http://example.com");
corsConfig.addAllowedHeader("*");
corsConfig.addAllowedMethod("GET");
corsConfig.addAllowedMethod("POST");
//将 CorsConfiguration 对象注册到路径为 /index/** 的 URL 上。
UrlBasedCorsConfigurationSource corsConfigSource = new UrlBasedCorsConfigurationSource();
corsConfigSource.registerCorsConfiguration("/index/**", corsConfig);
// 创建 CorsFilter 对象
return new CorsFilter(corsConfigSource);
}
}
HTTP缓存
HTTP缓存可以显著提高web应用程序的性能。
@Controller
public class MyController {
@GetMapping("/index")
public ResponseEntity<String> index(){
String data = "hello world";
创建 CacheControl 对象,设置缓存时间为 1 小时
CacheControl cacheControl = CacheControl.maxAge(1, TimeUnit.HOURS);
return ResponseEntity.ok().cacheControl(cacheControl).body(data);
}
}
我们创建了一个 CacheControl 对象,并使用 maxAge()
方法设置了最大缓存时间为 60 分钟。然后,在返回 ResponseEntity 对象时,使用 cacheControl()
方法将 CacheControl 对象添加到响应头部中。
当客户端收到响应时,就可以看到 "Cache-Control: max-age=3600"
字段,表示客户端可以将该响应缓存 60 分钟。
视图技术
在Spring MVC中使用视图技术是可扩展的。决定使用Thymeleaf、Groovy Markup Templates、jsp还是其他技术,主要是一个配置更改问题。视图可以访问应用程序上下文的所有bean。因此,不建议在模板可由外部源编辑的应用程序中使用Spring MVC的模板支持,因为这可能有安全隐患。
- Thymeleaf
Thymeleaf是一个现代的服务器端Java模板引擎,它强调可以通过双击在浏览器中预览的自然HTML模板,这对于独立处理UI模板(例如,由设计师)非常有帮助,而不需要运行服务器。
pom依赖:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
示例代码如下:
@Controller
public class MyController {
@GetMapping("/index")
public ModelAndView index(){
User user = new User();
user.setName("张三");
user.setId(1);
user.setAge("16");
ModelAndView modelAndView = new ModelAndView("user");
modelAndView.addObject("user",user);
return modelAndView;
}
}
html文件:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${user.name}">Default Page Title</h1>
</body>
</html>
更多使用方法参考官方文档:https://www.thymeleaf.org/。
- FreeMarker
Apache FreeMarker是一个模板引擎,用于生成从HTML到电子邮件和其他任何类型的文本输出。Spring框架内置了使用Spring MVC和FreeMarker模板的集成。
pom依赖:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
以下示例显示了如何将FreeMarker配置为视图技术:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
// Configure FreeMarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
return configurer;
}
}
SpringBoot可以在 application.properties
(或 application.yml
)文件中配置 FreeMarker 的相关属性:
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.charset=UTF-8
示例代码如下:
@Controller
public class MyController {
@GetMapping("/index")
public String index(Model model){
User user = new User();
user.setName("张三");
user.setId(1);
user.setAge("16");
model.addAttribute("user",user);
return "index";
}
}
.ftl
文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>${user.name}</h1>
</body>
</html>
更多使用参考官方文档:https://freemarker.apache.org/。
除此之外,Spring框架有一个内置的集成,可以将Spring MVC与任何可以在JSR-223 Java脚本引擎。比如:Handlebars、Mustache、React、EJS、ERB、String templates、Kotlin Script templating。
- JSP和JSTL
Spring框架有一个内置的集成,可以将Spring MVC与JSP和JSTL结合使用。
示例代码如下:
@Controller
public class HomeController {
@GetMapping("/user/info")
public String userInfo(Model model) {
String name = "Tom";
int age = 20;
model.addAttribute("name", name);
model.addAttribute("age", age);
return "userInfo";
}
}
在 src/main/webapp/WEB-INF/views/
目录下创建 index.jsp 文件,以下是一个使用 JSP 和 JSTL 的示例代码::
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSTL 示例</title>
</head>
<body>
<c:set var="name" value="Tom"/>
<c:set var="age" value="20"/>
<c:if test="${age > 18}">
<p>${name}已经成年了。</p>
</c:if>
<c:choose>
<c:when test="${age > 20}">
<p>${name}已经超过20岁了。</p>
</c:when>
<c:otherwise>
<p>${name}还没有超过20岁。</p>
</c:otherwise>
</c:choose>
<ul>
<c:forEach var="i" begin="1" end="5">
<li>${i}</li>
</c:forEach>
</ul>
</body>
</html>
SpringBoot默认是不支持JSP
- PDF和Excel
Spring使得从模型数据动态生成PDF文档或Excel电子表格变得简单。文档是视图,并从服务器以正确的内容类型流式传输,以(希望)使客户端PC能够运行他们的电子表格或PDF查看器应用程序作为响应。
为了使用Excel视图,需要将Apache POI库添加到您的类路径中。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
示例代码如下:
@Controller
public class MyController {
@GetMapping("/report")
@ResponseBody
public String report(HttpServletResponse response) {
try {
// 创建一个空的工作簿对象
Workbook workbook = new XSSFWorkbook();
// 创建一个工作表对象
Sheet sheet = workbook.createSheet("My Sheet");
// 添加数据
Row row1 = sheet.createRow(0);
Cell cell1 = row1.createCell(0);
cell1.setCellValue("Name");
Row row2 = sheet.createRow(1);
Cell cell2 = row2.createCell(0);
cell2.setCellValue("John Doe");
Row row3 = sheet.createRow(2);
Cell cell3 = row3.createCell(0);
cell3.setCellValue("Jane Doe");
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-disposition", "attachment; filename=test.xlsx");
// 将工作簿写入到输出流中
ServletOutputStream outputStream = response.getOutputStream();
workbook.write(outputStream);
outputStream.close();
System.out.println("Excel文件已创建!");
} catch (Exception e) {
e.printStackTrace();
}
return "index";
}
}
对于PDF生成,您需要添加(最好)OpenPDF库。
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.35</version>
</dependency>
示例代码如下:
import com.lowagie.text.Document;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
@Controller
public class MyController {
@GetMapping("/report")
@ResponseBody
public String report(HttpServletResponse response) {
try {
// 创建一个空白的PDF文档对象
Document document = new Document();
response.setHeader("Content-disposition", "attachment; filename=test.pdf");
// 创建PdfWriter对象,将文档对象写入到一个输出流中
PdfWriter.getInstance(document, response.getOutputStream());
// 打开文档对象
document.open();
// 添加一段文字到文档中
document.add(new Paragraph("Hello, World!"));
// 关闭文档对象
document.close();
} catch (Exception e) {
e.printStackTrace();
}
return "index";
}
}
- Jackson
Jackson是一个流行的Java库,用于处理JSON数据。它提供了一组功能强大的API,可用于将Java对象序列化为JSON格式,以及将JSON数据反序列化为Java对象。
public class User {
private Integer id;
private String name;
private String age;
//getter setter ... ..
@Override
public String toString() {
return "id="+id+",name="+name+",age="+age;
}
JSON序列化:
public static void main(String[] args) throws JsonProcessingException {
User user = new User();
user.setName("张三");
user.setId(1);
user.setAge("16");
ObjectMapper objectMapper = new ObjectMapper();
String str = objectMapper.writeValueAsString(user);
System.out.println(str);
/** Output:
* {"id":1,"name":"张三","age":"16"}
*/
}
JSON反序列化:
public static void main(String[] args) throws JsonProcessingException {
String json = "{\"id\":1,\"name\":\"张三\",\"age\":\"16\"}";
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(json, User.class);
System.out.println(user);
/** Output:
* id=1,name=张三,age=16
*/
}
- XML编组
MarshallingView是Spring Framework提供的一个视图类,用于将Java对象通过不同的编组器(Marshaller)序列化为XML、JSON等格式。
public class XmlView extends AbstractView {
private final JAXBContext jaxbContext;
public XmlView(Class<?>... classesToBeBound) throws Exception {
jaxbContext = JAXBContext.newInstance(classesToBeBound);
setContentType("application/xml");
}
@Override
protected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
// 从模型中获取Java对象
Object object = map.get("object");
// 创建JAXB编组器
Marshaller marshaller = jaxbContext.createMarshaller();
// 设置编组选项
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//设置编码
httpServletResponse.setCharacterEncoding("utf-8");
// 将Java对象编组为XML,并写入到响应输出流中
marshaller.marshal(object, httpServletResponse.getWriter());
}
}
@Controller
public class MyController {
@GetMapping(value = "/toXml", produces = "application/xml;charset=UTF-8")
@ResponseBody
public ModelAndView toXml(HttpServletResponse response) throws Exception {
User user = new User();
user.setName("张三");
user.setId(1);
user.setAge("16");
ModelAndView modelAndView = new ModelAndView(new XmlView(User.class));
modelAndView.addObject("object",user);
return modelAndView;
}
}
@XmlRootElement
public class User {
private Integer id;
private String name;
private String age;
//getter setter ... ..
}
最终结果如图:
- XSLT视图
XSLT是XML的一种转换语言,作为web应用程序中的一种视图技术很受欢迎。如果您的应用程序自然地处理XML,或者如果您的模型可以很容易地转换成XML,那么XSLT作为一种视图技术可能是一个不错的选择。
挺麻烦的还得创建完xsl才能进行转换,可以使用Apache Cocoon、XStream、eXist-db等工具来处理XML数据。
示例代码如下:
public static void main(String[] args) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
Source xslt = new StreamSource(new FileInputStream("transform.xsl"));
Transformer transformer = factory.newTransformer(xslt);
Source xml = new StreamSource(new FileInputStream("books.xml"));
Result result = new StreamResult(new FileOutputStream("output.html"));
transformer.transform(xml, result);
}
xml文件:
<!-- books.xml -->
<library>
<book>
<title>Book 1</title>
<author>Author 1</author>
</book>
<book>
<title>Book 2</title>
<author>Author 2</author>
</book>
</library>
xsl文件:
<!-- transform.xsl -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Books</h2>
<table>
<tr>
<th>Title</th>
<th>Author</th>
</tr>
<xsl:for-each select="library/book">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="author"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
最终生成output.html文件:
MVC配置
MVC Java配置和MVC XML名称空间提供了适用于大多数应用程序的默认配置和一个配置API来定制它。
- 启用MVC配置
在Java配置中,您可以使用@EnableWebMvc
批注来启用MVC配置,如下例所示:
@Configuration
@EnableWebMvc
public class WebConfig {
}
在XML配置中,可以使用<mvc:annotation-driven>
元素来启用MVC配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
- MVC配置API
在Java配置中,您可以实现WebMvcConfigurer接口,如下例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
- 类型转换
默认情况下,安装了各种数字和日期类型的格式化程序,并支持通过字段上的@NumberFormat
和@DateTimeFormat
进行自定义。
要在Java config中注册自定义格式化程序和转换器,请使用以下代码:
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new CustomDateFormatter());
}
}
public class CustomDateFormatter implements Formatter<Date> {
@Override
public Date parse(String text, Locale locale) throws ParseException {
// 解析日期逻辑
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(text);
}
@Override
public String print(Date object, Locale locale) {
// 格式化日期逻辑
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(object);
}
}
- 内容类型
您可以配置Spring MVC如何从请求中确定所请求的媒体类型(例如,Accept头、URL路径扩展、查询参数等)。可以使用 configureContentNegotiation()
方法来配置内容协商策略。内容协商是指客户端和服务器之间协商数据传输格式的过程,常见的数据格式有 JSON、XML 等。
以下是一个示例代码:
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
// 设置支持的返回类型
configurer.mediaType("json", MediaType.APPLICATION_JSON);
configurer.mediaType("xml", MediaType.APPLICATION_XML);
}
}
- 消息转换器
configureMessageConverters()
方法是用来配置消息转换器的。消息转换器是负责将请求体和响应体中的数据转换成 Java 对象或者其他数据类型的组件。Spring MVC 内置了许多消息转换器,可以支持多种数据格式的转换,例如 JSON、XML、表单数据等。
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)//设置输出的 JSON 格式化时缩进。
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))//设置日期格式为 "yyyy-MM-dd"
.modulesToInstall(new ParameterNamesModule());//用于支持方法参数名的序列化和反序列化。
//创建的两个消息转换器对象添加到 converters 列表中,这样 Spring MVC 就会在处理请求和响应时使用这些消息转换器。
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
}
上述代码的作用是配置消息转换器,其中包括 JSON 消息转换器和 XML 消息转换器,以支持 JSON 和 XML 格式的请求和响应。
- 视图控制器
addViewControllers()
方法 用于注册简单的视图控制器。它可以方便地将路径与视图之间建立映射关系,而无需编写完整的控制器代码。
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
上述代码表示将根路径 /
映射到名为 index的视图。也就是说,当用户访问应用程序的根路径时,将返回名为 index 的视图作为响应。
XML配置:
<mvc:view-controller path="/" view-name="home"/>
- 查看解析器
MVC配置简化了视图解析器的注册。
在Java配置中,您可以添加各自的Configurerbean,如下例所示:
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//启用了内容协商功能,根据请求头中的 Accept 字段自动选择返回 JSON 或 HTML 视图。
registry.enableContentNegotiation(new MappingJackson2JsonView());
//配置了 FreeMarker 视图解析器,并禁用了模板缓存。
registry.freeMarker().cache(false);
}
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
//设置路径
configurer.setTemplateLoaderPath("/freemarker");
return configurer;
}
}
- 静态资源
addResourceHandlers()
方法 是 Spring MVC 中的一个方法,用于注册静态资源处理器。它可以方便地将路径与静态资源之间建立映射关系,从而能够让 Spring MVC 处理并返回静态资源。
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
上述代码,/static/**
路径被映射到了 classpath:/static/
目录下的静态资源。当用户请求 /static/
下的任意资源时,Spring MVC 将会在 classpath:/static/
目录下查找相应的资源文件,并将其返回给用户。
需要注意的是,多次调用 addResourceHandlers()
方法可以注册多个静态资源处理器,以处理不同的静态资源路径。
- 默认Servlet
configureDefaultServletHandling()
方法 是 Spring MVC 中的一个方法,用于配置是否将静态资源的请求交给默认的 Servlet 处理。
在某些情况下,我们可能希望将某些特定的请求交给默认的 Servlet 处理,而不经过 Spring MVC 的 DispatcherServlet。默认的 Servlet 通常由 Web 容器提供,它负责处理静态资源请求,如图片、CSS 文件、JavaScript 文件等。
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//启用默认的 Servlet 处理
configurer.enable();
}
}
当启用了默认的 Servlet 处理时,如果某个请求不能由 Spring MVC 的 DispatcherServlet 处理,将会交给默认的 Servlet 处理。这样,我们就可以在应用程序中同时使用 Spring MVC 和默认的 Servlet 来处理请求,以提供静态资源和动态内容。
- 路径匹配
在 Spring MVC 中,当一个请求到达 DispatcherServlet 后,会将其映射到对应的控制器方法上。这个映射过程就需要考虑 URL 的匹配规则,以便正确地选择合适的控制器方法来处理请求。
@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(MyController.class));
}
private PathPatternParser patternParser() {
// ...
}
}
这种配置方式可以实现对特定路径下的请求进行统一处理,例如将所有 API 请求都映射到 /api
路径下的控制器类中,方便管理和维护。例如,其中有一个处理器方法使用 @GetMapping("/users")
注解,则该方法的完整请求路径应为 /api/users
。
- 高级Java配置
DelegatingWebMvcConfiguration 是 Spring MVC 中的一个配置类,它实现了 WebMvcConfigurer 接口,并提供了默认的配置。包括处理器映射、视图解析器、消息转换器等。通过继承这个类并重写其中的方法,我们可以对默认配置进行修改和补充。
对于高级模式,你可以删除@EnableWebMvc
并直接从DelegatingWebMvcConfiguration扩展,如下例所示:
@Configuration
public class CustomWebMvcConfig extends DelegatingWebMvcConfiguration {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
}
- 高级XML配置
BeanPostProcessor 是 Spring 框架中的一个接口,它允许在 Spring 容器实例化 Bean 对象之后、初始化之前和销毁之后,对 Bean 实例进行一些自定义的处理。
当 Spring IOC 容器创建一个 Bean 的时候,在实例化 Bean 和完成依赖注入之后,Spring 会检查在容器中是否注册了 BeanPostProcessor 类型的 Bean。如果有,则 Spring 会在初始化 Bean 的前后自动调用 BeanPostProcessor 中的两个方法:postProcessBeforeInitialization()
和 postProcessAfterInitialization()
。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before initialization of bean " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After initialization of bean " + beanName);
return bean;
}
}
其他Web框架
Spring框架的核心价值主张之一是支持选择。一般来说,Spring不会强迫您使用或购买任何特定的架构、技术或方法(尽管它肯定会推荐一些而不是其他)。
- JSF
JavaServer Faces (JSF)是JCP标准的基于组件的、事件驱动的web用户界面框架。它是Jakarta EE保护伞的官方部分,但也可以单独使用,例如通过在Tomcat中嵌入Mojarra或MyFaces。
- Apache Struts
Struts由Craig McClanahan发明,是一个由Apache软件基金会托管的开源项目。Struts 1.x极大地简化了JSP/Servlet编程范例,并赢得了许多使用专有框架的开发人员的支持。简化了编程模型;它是开源的;它有一个庞大的社区,这使得项目不断发展,并在Java web开发人员中流行起来。
- Apache Tapestry
Tapestry是一个“面向组件的框架,用于在Java中创建动态的、健壮的、高度可伸缩的web应用程序”。
虽然Spring有自己强大的web层,但是通过结合使用Tapestry作为web用户界面和Spring容器作为底层来构建企业Java应用程序有许多独特的优势。