Java进击框架:Spring-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,并进一步将其细分为contextPathservletPathpathInfo,它们的值根据Servlet的映射方式而变化。从这些输入中,Spring MVC需要确定用于映射处理程序的查找路径,这应该排除contextPath和任何servletMapping前缀(如果适用)。

路径可能包含编码的保留字符,如“/”“;”,这些字符在解码后会改变路径的结构,这也会导致安全问题。

如果DispatcherServlet被映射为默认的带有“/”或不带“/*”前缀的Servlet,并且Servlet容器是4.0+,那么Spring MVC能够检测Servlet映射类型并避免使用servletPathpathInfo。幸运的是,默认的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;
	}
}
  • DefaultHandlerExceptionResolverSpring 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定义了ViewResolverView允许您在浏览器中呈现模型的界面,而不会将您束缚于特定的视图技术。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.classSpring 框架中的一个视图类,用于解析 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 引擎来渲染模板视图。

  • BeanNameViewResolverSpring 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请求给客户端浏览器,然后由浏览器重新发起该请求到新的URLURL会发生变化。

转发可以在请求之间共享数据,而重定向不能直接在请求之间共享数据。参数传递可以通过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配置中声明一个名为MultipartResolverMultipartResolver beanDispatcherServlet检测它并将其应用于传入请求。当接收到内容类型为multipart/form-dataPOST时,解析器解析将当前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 GETHTTP POST提交表单数据,Servlet API要求ServletRequest.getParameter*()方法只支持HTTP POST的表单字段访问。

FormContentFilter来拦截内容类型为application/x-www-form-urlencodedHTTP PUT、PATCHDELETE请求,从请求体中读取表单数据,并包装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注解来指定控制器的映射路径。

声明

您可以在ServletWebApplicationContext中使用标准的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将请求映射到控制器方法的注释。它有各种属性,可以通过URLHTTP方法、请求参数、头和媒体类型进行匹配。您可以在类级别使用它来表达共享映射,或者在方法级别使用它来缩小到特定的端点映射。

还有特定于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_VALUEAPPLICATION_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注解:用于将HTTP会话中的属性值绑定到控制器方法的参数或模型属性上。

@Controller
public class UserController {
    @PostMapping("/login")
    public String doLogin(@RequestParam String username, @RequestParam String password, Model model) {
        // 校验用户名和密码
        if (isValidUser(username, password)) {
            User user = new User(username);
            model.addAttribute("user", user); // 将用户对象添加到模型中
            return "redirect:/dashboard";
        } else {
            return "login";
        }
    }
    
    @GetMapping("/dashboard")
    public String dashboard(@SessionAttribute("user") User user) {
        // 从会话中获取当前用户对象
        if (user != null) {
            return "dashboard";
        } else {
            return "redirect:/login";
        }
    }
}

@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();
    }
}

HttpEntityHttpEntity或多或少与使用@RequestBody相同,但它基于一个公开请求头和请求体的容器对象。

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    HttpHeaders headers = entity.getHeaders();
    Account account = entity.getBody();

    // 处理请求头
    String contentType = headers.getContentType().toString();
    String authorization = headers.getFirst("Authorization");

    // 处理请求体
    if (account != null) {
        String username = account.getUsername();
        String password = account.getPassword();

        // 进行业务逻辑操作,例如创建新账户或更新现有账户
        // ...
    }
}
  • 返回值

@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();

ServerRequestServerResponse是不可变的接口,提供对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配置RestTemplateWebClient来定制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
         */
    }

WebClientRestTemplate通过在内部扩展和编码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

异步请求

DeferredResultCallable控制器方法中的返回值为单个异步返回值提供了基本支持。

  • 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

CallableJava 并发编程中的一个接口,用于表示一个可并发执行的任务。

@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 WebSocketWebSocket with SockJS)。

CORS

跨域资源共享(Cross-Origin Resource Sharing, CORS)是由大多数浏览器实现的W3C规范,它允许您指定授权哪种跨域请求,而不是使用基于IFRAMEJSONP的不太安全且功能不太强大的解决方案。

要实现跨域访问,需要在服务端设置响应头信息。具体来说,需要在响应头中添加以下信息:

//允许访问的源地址
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).

组合全局和本地配置的规则通常是相加的,例如,所有全局和所有本地起源。对于那些只能接受单个值的属性,例如allowCredentialsmaxAge,局部值会覆盖全局值。

@RestController
public class MyController {

    @CrossOrigin(origins = "https://example.com", maxAge = 3600)
    @GetMapping("/data")
    public Map<String, Object> getData() {
        // ...
    }

}

在上述示例中,我们在控制器方法(也可以在类)上添加了 @CrossOrigin 注解,指定了允许访问的来源地址和缓存预检请求的时间。

allowCredentials默认情况下不启用,因为这将建立一个公开敏感的用户特定信息(如cookiesCSRF令牌)的信任级别,并且只应在适当的时候使用。当它被启用时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 MVCFreeMarker模板的集成。

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脚本引擎。比如:HandlebarsMustacheReactEJSERBString templatesKotlin Script templating

  • JSP和JSTL

Spring框架有一个内置的集成,可以将Spring MVCJSPJSTL结合使用。

示例代码如下:

@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 文件,以下是一个使用 JSPJSTL 的示例代码::

<%@ 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编组

MarshallingViewSpring Framework提供的一个视图类,用于将Java对象通过不同的编组器(Marshaller)序列化为XMLJSON等格式。

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视图

XSLTXML的一种转换语言,作为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 消息转换器,以支持 JSONXML 格式的请求和响应。

  • 视图控制器

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 MVCDispatcherServlet。默认的 Servlet 通常由 Web 容器提供,它负责处理静态资源请求,如图片、CSS 文件、JavaScript 文件等。

@Configuration
@EnableWebMvc
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    	//启用默认的 Servlet 处理
        configurer.enable();
    }
}

当启用了默认的 Servlet 处理时,如果某个请求不能由 Spring MVCDispatcherServlet 处理,将会交给默认的 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配置

DelegatingWebMvcConfigurationSpring 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配置

BeanPostProcessorSpring 框架中的一个接口,它允许在 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中嵌入MojarraMyFaces

  • Apache Struts

StrutsCraig McClanahan发明,是一个由Apache软件基金会托管的开源项目。Struts 1.x极大地简化了JSP/Servlet编程范例,并赢得了许多使用专有框架的开发人员的支持。简化了编程模型;它是开源的;它有一个庞大的社区,这使得项目不断发展,并在Java web开发人员中流行起来。

  • Apache Tapestry

Tapestry是一个“面向组件的框架,用于在Java中创建动态的、健壮的、高度可伸缩的web应用程序”。

虽然Spring有自己强大的web层,但是通过结合使用Tapestry作为web用户界面和Spring容器作为底层来构建企业Java应用程序有许多独特的优势。

面试题:SpringMVC工作流程

  1. 客户端(如浏览器)发送 HTTP 请求到 Web 服务器进行解析,解析后的URL地址如果匹配到DispatchServlet的映射路径(通过web.xml中的servlet-mapping配置),Web 容器就将请求交给DispatcherServlet处理。
  2. DispatcherServlet 接收到这个请求后,调用 HandlerMapping,找到具体处理请求的Handler对象。
  3. DispatcherServlet根据得到的Handler对象,选择一个合适的HandlerAdapter,执行拦截器中的preHandler()方法。在拦截器方法中,提取请求中的数据模型,填充Handler入参,所以所有准备工作都已做好,开始执行Handler。处理器(Controller)执行业务逻辑,通常包括数据处理、调用服务层、持久化操作等。
  4. Handler执行完毕后返回一个ModelAndView对象给DispatcherServlet,其中包含视图名和模型数据。
  5. DispatcherServlet通过ViewResolver解析 ModelAndView 中的视图名,找到对应的视图(JSPHTML 页面等)。
  6. 视图被渲染成 HTML 页面,并通过 Response 返回给客户端,并根据内容显示页面。
  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值