Spring MVC相关问题及答案(2024)

1、什么是Spring MVC?

Spring MVC 是基于 Java 的 Spring Framework 的一部分,它实现了 Web 应用程序的 Model-View-Controller (MVC) 设计模式。Spring MVC 设计目标是通过分离关注点来提高开发效率,这样可以让业务逻辑、用户界面和控制逻辑相互独立。下面是对 Spring MVC 的深入解析,结合源码和代码演示。

核心组件

  • DispatcherServlet: 作为前端控制器,它是 MVC 模式中的中心点,负责接收 HTTP 请求,解析它们,并将工作委托给其他组件。
  • HandlerMapping: 定位到处理特定请求的控制器。
  • Controller: 接受用户输入并调用后端服务。
  • ModelAndView: 封装了模型数据和视图信息。
  • ViewResolver: 解析逻辑视图名称到实际视图(如 JSP)。
  • View: 负责渲染模型数据,将其展示给用户。

工作流程

  1. 客户端发送请求到 DispatcherServlet
  2. DispatcherServlet 调用 HandlerMapping 获得处理请求的 Controller
  3. Controller 处理请求,返回 ModelAndView
  4. DispatcherServlet 使用 ViewResolver 来解析 ModelAndView 中的视图名到实际的 View
  5. View 负责渲染结果。

源码解析和代码演示

下面是一个简单的 Spring MVC Controller 示例代码:

@Controller
public class MyController {

    @RequestMapping(value = "/greet", method = RequestMethod.GET)
    public ModelAndView greet() {
        ModelAndView mav = new ModelAndView("greet");
        mav.addObject("message", "Hello from Spring MVC");
        return mav;
    }
}

在这段代码中:

  • @Controller 标记这个类作为 Spring MVC 控制器。
  • @RequestMapping 注解用来映射 Web 请求到类的方法。
  • 方法 greet() 被调用来处理对 /greet 的 GET 请求。
  • 一个新的 ModelAndView 实例被创建并返回,它指定了视图名 “greet” 和模型数据。

现在,让我们看一下 DispatcherServlet 如何初始化和处理请求的大概流程,以下是简化的伪代码来说明这一点:

public class DispatcherServlet extends HttpServlet {

    // 初始化方法,加载配置
    public void init() {
        initStrategies();
    }

    // 处理请求
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
        // 确定此次请求的处理器
        Object handler = getHandler(request);
        if (handler == null) {
            return;
        }
        
        // HandlerAdapter可以调用我们的方法
        HandlerAdapter ha = getHandlerAdapter(handler);
        
        // 这里的ModelAndView包含了视图名和模型数据
        ModelAndView mav = ha.handle(request, response, handler);
        
        // 渲染视图
        processDispatchResult(request, response, mav);
    }
    
    // ...其他辅助方法...
}

在真实的 DispatcherServlet 类中,initStrategies() 方法会初始化若干策略对象,包括 HandlerMappingHandlerAdapterViewResolver 等。doDispatch() 方法是请求处理的核心,它会找到对应的处理器(Controller),通过合适的 HandlerAdapter 执行处理器的方法,并最终将 ModelAndView 对象传给视图进行渲染。

在深入研究 Spring MVC 时,源码阅读者会注意到许多细节和优化点,例如:

  • 请求映射的灵活性:Spring MVC 的 @RequestMapping 注解提供了高度灵活的方式来映射请求,包括 URL 路径、HTTP 方法、请求参数、头部信息等。
  • 数据绑定机制:Spring 将 HTTP 请求数据绑定到 Java 对象使用的是 DataBinder,这使得如表单提交这样的操作可以直接映射到 Java 对象上。
  • 类型转换与格式化:Spring MVC 使用 ConversionServiceFormatter 接口来处理类型转换和数据格式化。
  • 内部化和异常处理:通过 MessageSource 进行国际化支持,使用 @ExceptionHandler@ControllerAdvice 对异常进行统一处理。
  • 插拔式的视图解析:Spring 允许定义多个 ViewResolver,它们可以根据不同的情况选择不同的视图技术。
  • 拦截器:Spring MVC 的拦截器(HandlerInterceptor)可以在请求前后进行额外的处理。

在深入了解时,你会需要阅读具体的类和方法,如 AbstractAnnotationConfigDispatcherServletInitializer,它是 Java 配置风格的 DispatcherServlet 初始化方式。你也可能需要查看 AnnotationMethodHandlerAdapterRequestMappingHandlerMapping 的实现细节。这些类和它们的方法揭示了 Spring MVC 的内部工作原理。

2、Spring MVC流程

Spring MVC 的工作流程是基于 Servlet API 构建的,它利用前端控制器模式的 DispatcherServlet 来中央处理所有的 HTTP 请求。Spring MVC 的设计允许请求通过一系列的处理步骤,这些步骤被封装在不同的组件中。

核心组件

Spring MVC 中的几个核心组件包括:

  1. DispatcherServlet: 提供一个中央控制器,它委托请求给其它组件进行处理。
  2. HandlerMapping: 确定每个请求由哪个 Controller 处理。
  3. Controller: 具体的业务逻辑处理器。
  4. ModelAndView: 包含视图和模型数据,是控制器和视图之间的纽带。
  5. ViewResolver: 负责解析逻辑视图名称到具体的 View 实现。
  6. View: 用于渲染响应,显示模型数据。

接下来,我们将结合源码来更详细地解析流程。

Spring MVC 工作流程详解

  1. 请求处理: 当一个请求到达服务器,但在到达 Spring MVC 应用之前,先被 DispatcherServlet 捕获。DispatcherServlet 是在 web.xml 文件或者在 Java 配置中注册的一个 Servlet。

  2. 请求映射: DispatcherServlet 调用 HandlerMapping 来确定哪个 Controller 应该处理请求。

HandlerExecutionChain mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
    noHandlerFound(processedRequest, response);
    return;
}
  1. 请求处理器适配: HandlerAdapter 被用于调用 Controller 方法。
HandlerAdapter ha = getHandlerAdapter(handler);
ModelAndView mv = ha.handle(processedRequest, response, handler);
  1. 模型和视图: Controller 处理请求后,返回一个 ModelAndView 对象。
@RequestMapping("/greeting")
public ModelAndView greeting() {
    ModelAndView mav = new ModelAndView("greeting");
    mav.addObject("message", "Hello World!");
    return mav;
}
  1. 视图解析: DispatcherServlet 通过 ViewResolver 来解析 ModelAndView 中的视图名称到一个具体的 View。
View view;
String viewName = mv.getViewName();
if (viewName != null) {
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
}
  1. 渲染视图: 最后,View 负责渲染模型数据,返回给客户端。
if (view != null) {
    try {
        view.render(mv.getModelInternal(), request, response);
    }
}

Spring MVC 的工作流程充满了可扩展的抽象和默认实现。例如:

  • HandlerMapping 的默认实现是 RequestMappingHandlerMapping,它基于注解来工作。
  • HandlerAdapter 的默认实现是 RequestMappingHandlerAdapter
  • ViewResolver 的默认实现是 InternalResourceViewResolver,它解析 JSP 文件。

深入源码,每个组件都有相应的接口和提供的默认实现,可以通过扩展它们来适配自定义需要。例如,可以自定义一个 HandlerInterceptor 来在请求处理之前和之后执行代码:

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 逻辑代码
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        // 逻辑代码
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 逻辑代码
    }
}

而且,Spring MVC 通过 DispatcherServlet 的 WebApplicationContext 加载配置,包括 Controllers、ViewResolvers、HandlerMappings 等,这些都可以在 XML 配置文件中定义,或者通过利用 Spring 的 Java 配置类(基于 @Configuration)进行配置。

在深入理解 Spring MVC 流程时,重点关注 DispatcherServlet 的 doDispatch 方法,它是请求处理的核心。 掌握这个方法的工作原理,对理解 Spring MVC 处理请求的机制至关重要。

3、什么是DispatcherServlet以及它的作用是什么?

DispatcherServlet 是 Spring MVC 框架中的核心组件,继承自 Java Servlet API 的 HttpServlet 类,并充当前端控制器(Front Controller)。它负责接收所有的 Web 应用请求并将它们分发到相应的处理器。

作用

DispatcherServlet 的主要作用包括:

  1. 请求拦截:拦截进入应用的所有请求。
  2. 请求处理:解析请求,并将其分派给相应的控制器(Controller)。
  3. 处理器映射:通过 HandlerMapping 确定每个请求的处理器。
  4. 请求转发:使用 HandlerAdapter 调用处理器的方法处理请求。
  5. 模型与视图:处理器返回 ModelAndView,其中包含响应的模型数据和视图信息。
  6. 视图解析:使用 ViewResolver 将视图名称解析为具体的视图(JSP、Thymeleaf、Freemarker 等)。
  7. 响应返回:渲染视图和返回响应给客户端。

源码解析

在 Spring MVC 的初始化过程中,当 DispatcherServlet 启动时,会创建 Web 应用上下文(WebApplicationContext),并加载配置的组件,如控制器、视图解析器、拦截器等。

以下是 DispatcherServlet 相关方法的源码级解析,解释其工作原理。

public class DispatcherServlet extends FrameworkServlet {

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 1. 请求的预处理,比如语言、主题解析
        // 2. 确定请求的处理器
        HandlerExecutionChain mappedHandler = getHandler(request);
        if (mappedHandler == null) {
            noHandlerFound(request, response);
            return;
        }
        
        // 3. 确定适配器
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // 4. 处理请求,返回 ModelAndView
        ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());

        // 处理多重视图情况
        if (mv != null && !mv.wasCleared()) {
            render(mv, request, response);
        }
    }

    private void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 解析视图
        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            // 5. 通过 ViewResolver 解析视图
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        } else {
            view = mv.getView();
        }
        
        // 6. 渲染视图
        if (view != null) {
            view.render(mv.getModelInternal(), request, response);
        }
    }
    
    // ...其它方法...
}

代码演示

配置 DispatcherServlet 通常是在 web.xml 中或者通过 Java 配置完成的。以下是使用 Java 配置的示例:

public class MyWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 创建一个 ApplicationContext
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);
        
        // 创建 DispatcherServlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
        
        // 注册并配置 DispatcherServlet
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

DispatcherServlet 启动时,它会查找并使用以下策略对象:

  • HandlerMapping: 确定每个请求的控制器。
  • HandlerAdapter: 执行控制器的方法。
  • ViewResolver: 解析视图名到实际视图。
  • LocaleResolver: 解析请求的区域设置。
  • MultipartResolver: 如果是文件上传请求,解析多部件。

DispatcherServlet 还支持链式处理器映射和适配器,使得可以同时使用多种映射和适配策略。此外,它还支持异常解析,通过 HandlerExceptionResolver 处理在请求处理过程中抛出的异常。

在 Spring MVC 应用中,通常只需要一个 DispatcherServlet 来处理所有请求,但它能够共享或独立地拥有多个 Web 应用上下文,为不同的请求提供不同的处理器映射、视图解析等配置。

总而言之,DispatcherServlet 通过将请求路由到 Spring MVC 应用中的正确组件,从而使得应用能够高效且灵活地处理 HTTP 请求。

4、Controller和RestController的区别是什么?

@Controller@RestController 都是 Spring MVC 中用来创建控制器类的注解,但它们在用法和目的上有所不同。

@Controller

@Controller 是一个用来创建 Spring MVC 控制器的常规注解。它标记的类会被 Spring 的扫描器注册为一个 Bean,并且它通常与 @RequestMapping 和其他相关注解一起使用,来定义 Web 页面的请求处理。通常,@Controller 返回的是一个视图名称(String),Spring 使用 ViewResolver 解析这个名称,然后渲染给定视图。

@Controller
public class WebController {

    @RequestMapping("/greet")
    public String greet(Model model) {
        model.addAttribute("message", "Hello, World");
        return "greet"; // 视图名称,需要一个对应的 ViewResolver 来解析为实际视图
    }
}

@RestController

@RestController@Controller 的特化版本,它是在 Spring 4.0 引入的,用于创建 RESTful 控制器。它结合了 @Controller@ResponseBody 的功能,意味着 @RestController 注解的类中的所有方法都默认以 @ResponseBody 方式处理,并且返回的数据直接写入 HTTP 响应体中,适合做 REST API。

@RestController
public class ApiController {

    @RequestMapping("/api/greet")
    public Map<String, String> apiGreet() {
        return Collections.singletonMap("message", "Hello, World");
    }
}

在这个例子中,apiGreet 方法返回的 Map 将会被自动转换成 JSON 格式,因为 @RestController 包含了 @ResponseBody 注解的功能。

源码级别的区别

在源码级别,@RestController 是一个组合注解,它本身被 @Controller@ResponseBody 注解了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(annotation = Controller.class)
    String value() default "";
}

从这个定义中,我们可以看出 @RestController 不仅使用了 @Controller,还添加了 @ResponseBody,这意味着它会将所有的返回值放到响应体内,通常是以 JSON 或 XML 格式。

代码演示

下面是一个 @Controller@RestController 的简单示例,用于对比两者的不同行为:

@Controller
public class MyWebController {
    
    @RequestMapping("/web")
    public String handleWebRequest(Model model) {
        model.addAttribute("msg", "Web Message");
        return "myView"; // 返回视图名,需要配置 ViewResolver
    }
}

@RestController
public class MyRestController {
    
    @RequestMapping("/api")
    public MyResponse handleApiRequest() {
        return new MyResponse("API Message"); // 直接返回数据对象,Spring 会自动序列化为 JSON
    }
}

public class MyResponse {
    private String message;

    public MyResponse(String message) {
        this.message = message;
    }
    // Getter and Setter
}

在这个例子中,MyWebController 中的 handleWebRequest 方法返回一个视图名称 myView,这个视图需要被解析和渲染。而 MyRestController 中的 handleApiRequest 方法返回的 MyResponse 对象会被自动转换为 JSON 格式的响应体。

@Controller@RestController 的区别不仅仅在于返回的内容。由于 @RestController 通常用于编写 REST API,因此它也常常与 @RequestMapping 的其他变种如 @GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping 等注解一起使用,以便定义不同类型的 HTTP 请求方法。

当创建一个 REST API 时,通常期望请求和响应都是无状态的,并且遵循 REST 原则。使用 @RestController 可以更容易地实现这些目标,而 @Controller 更多用于传统的 Web 应用程序,其中控制器方法通常返回一个视图的名称。

5、Spring MVC中的ModelAndView

在Spring MVC框架中,ModelAndView是一个持有模型数据与视图信息的容器,用于将控制器(Controller)处理过的数据以及确定的视图传递给前端控制器(DispatcherServlet),从而实现视图渲染。

Model

“模型”(Model)部分包含了要展示的数据。在Spring MVC中,模型可以是任何Java对象,不过它通常是一个Map或者一个自定义的领域对象(也被称为模型对象)。控制器会将处理逻辑的结果放入模型中,这样数据就可以被传递到视图层,并在最终的页面上展示给用户。

View

“视图”(View)是用于渲染模型数据的组件,通常是JSP、Freemarker页面、Thymeleaf模板或其他视图技术。在ModelAndView中设置视图可以通过设置视图的名称来间接完成,Spring的ViewResolver会负责解析这个名称到一个具体的View实例;也可以直接提供一个View实例。

ModelAndView的使用

ModelAndView对象通常在控制器的处理方法中创建,然后添加数据和视图信息。这个对象然后被返回给DispatcherServlet,DispatcherServlet会利用这个信息来完成后续的视图渲染流程。

下面的代码展示了如何在控制器中使用ModelAndView

@Controller
public class MyController {

    @RequestMapping("/greeting")
    public ModelAndView greeting() {
        // 创建ModelAndView实例
        ModelAndView mav = new ModelAndView();

        // 添加模型数据
        mav.addObject("greeting", "Hello World");

        // 设置视图名称
        mav.setViewName("greet");

        // 返回ModelAndView对象
        return mav;
    }
}

在上面的例子中,控制器方法greeting创建了一个新的ModelAndView实例,然后它添加了一个名为greeting的属性到模型中。它也设置了视图名称为greet。当DispatcherServlet接收到这个ModelAndView,它将使用配置的ViewResolver来解析视图名称greet到一个具体的视图实现。

源码级别的解析

在Spring MVC的源码中,ModelAndView简单直观。它主要包含了一个模型Map和一个视图对象或视图名称字符串。下面是一个简化的ModelAndView源码片段,显示了其核心结构:

public class ModelAndView {

    @Nullable
    private Object view;

    @Nullable
    private Map<String, Object> model;

    private boolean cleared = false;

    public ModelAndView(String viewName, Map<String, ?> model) {
        this.view = viewName;
        if (model != null) {
            getModelMap().addAllAttributes(model);
        }
    }

    public ModelAndView(View view, Map<String, ?> model) {
        this.view = view;
        if (model != null) {
            getModelMap().addAllAttributes(model);
        }
    }

    // ...其他构造函数和方法...

    public void addObject(String attributeName, @Nullable Object attributeValue) {
        getModelMap().addAttribute(attributeName, attributeValue);
    }

    @Nullable
    public View getView() {
        return (this.view instanceof View ? (View) this.view : null);
    }

    public void setViewName(@Nullable String viewName) {
        this.view = viewName;
    }

    // ...其他getter和setter...

    protected Map<String, Object> getModelInternal() {
        if (this.model == null) {
            this.model = new LinkedHashMap<>();
        }
        return this.model;
    }

    // ...其他实用方法...
}

在这个简化的类中,可以看到ModelAndView可以通过构造函数接受视图名或View实例,还可以接受一个模型Map。此外,通过addObject方法可以向模型中添加属性。

ModelAndView的设计允许控制器以高级别的方式控制它们的输出,同时与特定的视图技术解耦。控制器的职责集中于业务逻辑处理,而视图的选择和呈现则留给了DispatcherServlet和配置的视图解析器。

要注意的是,即使在RESTful服务和API开发中,ModelAndView的用途可能不如在传统的Web页面渲染中那么频繁,但它在Spring MVC中仍然是连接模型和视图的重要部分。

6、如何在Spring MVC中进行异常处理?

在Spring MVC中,异常处理可以通过多种方式实现。这些方式包括使用@ExceptionHandler注解、实现HandlerExceptionResolver接口,或者使用@ControllerAdvice注解来全局处理异常。下面详细介绍每种方式。

1. 使用 @ExceptionHandler

@ExceptionHandler注解用于在Controller内部处理特定的异常。可以在控制器中定义一个或多个这样的方法,当控制器内部发生异常时,这些方法会被调用。

@Controller
public class MyController {

    @RequestMapping("/somePath")
    public String handleRequest() {
        // ...业务逻辑,可能会抛出SomeException...
        return "someView";
    }

    @ExceptionHandler(SomeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ModelAndView handleSomeException(SomeException ex) {
        ModelAndView mav = new ModelAndView("errorView");
        mav.addObject("message", ex.getMessage());
        return mav;
    }
}

在这个例子中,如果handleRequest()方法抛出SomeException,则handleSomeException()方法会被调用,并返回定义的错误页面。

2. 实现 HandlerExceptionResolver

HandlerExceptionResolver接口可以用于全局异常处理。通过实现这个接口,你可以自定义异常处理逻辑。Spring已经提供了一些实现,如SimpleMappingExceptionResolverDefaultErrorAttributes等。

public class MyExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception ex) {
        ModelAndView mav = new ModelAndView("errorView");
        mav.addObject("message", ex.getMessage());
        return mav;
    }
}

在这个例子中,resolveException方法会被调用,并返回定义的错误视图。要在Spring中注册自定义的HandlerExceptionResolver,可以通过Java配置或XML配置。

3. 使用 @ControllerAdvice

@ControllerAdvice注解是在Spring 3.2中引入的,用于全局的Controller层异常处理。通过标注一个类为@ControllerAdvice,你可以定义多个@ExceptionHandler方法来处理全局异常。

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(SomeException.class)
    public ResponseEntity<String> handleSomeException(SomeException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(AnotherException.class)
    public ResponseEntity<String> handleAnotherException(AnotherException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

在这个例子中,任何控制器抛出的SomeExceptionAnotherException都将由这里的方法处理。

源码解析

当Spring MVC处理请求时,如果在执行过程中抛出异常,DispatcherServlet会委托配置的HandlerExceptionResolver来解决异常。

以下是Spring MVC中处理异常的简化代码流程:

public class DispatcherServlet extends FrameworkServlet {

    // ... DispatcherServlet其他代码 ...

    protected void processDispatchResult(HttpServletRequest request,
                                         HttpServletResponse response,
                                         HandlerExecutionChain mappedHandler,
                                         ModelAndView mv,
                                         Exception exception) throws Exception {
        boolean errorView = false;
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }
        // 如果不是异常视图,则渲染正常的视图
        if (!errorView && mv != null && !mv.wasCleared()) {
            render(mv, request, response);
            return;
        }
    }

    @Nullable
    protected ModelAndView processHandlerException(HttpServletRequest request,
                                                   HttpServletResponse response,
                                                   @Nullable Object handler,
                                                   Exception ex) throws Exception {
        ModelAndView exMv = null;
        for (HandlerExceptionResolver resolver : getHandlerExceptionResolvers()) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
        if (exMv != null) {
            if (exMv.isEmpty()) {
                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            }
            else {
                // ...执行异常视图的渲染...
            }
        }
        return exMv;
    }
}

processDispatchResult方法中,如果存在异常,会调用processHandlerException方法来处理。processHandlerException方法会遍历所有的HandlerExceptionResolver并调用resolveException方法,直到有一个返回非null的ModelAndView

代码演示

下面的代码演示了如何在Spring MVC应用中使用@ControllerAdvice@ExceptionHandler进行全局异常处理:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) {
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", req.getRequestURL());
        mav.setViewName("error"); // 设置错误页面
        return mav;
    }
}

@Controller
public class MyController {

    @RequestMapping(value = "/throwException")
    public String throwException() throws Exception {
        throw new Exception("A sample exception");
    }
}

在这个例子中,无论哪个控制器中抛出异常,GlobalExceptionHandler中的defaultErrorHandler方法都将处理该异常,并返回到错误页面。

7、什么是Spring Form标签库?

Spring Form标签库是Spring MVC中提供的一套JSP标签,用于简化HTML表单的创建和表单数据的绑定。这些标签库抽象了表单渲染的通用模式,并自动处理了数据绑定和验证的结果显示,使得在JSP页面上构建表单变得更加简单和易于管理。

标签库的优点

  1. 数据绑定:Spring Form标签库与后端的模型对象自动绑定,减少了编写绑定逻辑的代码量。
  2. 类型转换:自动进行数据类型的转换。
  3. 输入验证:与Spring的验证器集成,显示字段级的错误信息。
  4. 表单回显:在表单提交后,如果有验证错误,表单可以重新渲染并回显提交的数据。

标签库的使用

要使用Spring Form标签库,首先需要在JSP页面中包含标签库的指令:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

然后,您可以使用<form:form>标签以及其他相关的表单标签如<form:input>, <form:select>, 等等来创建表单:

<form:form modelAttribute="userForm" method="POST" action="/submitUser">
    <form:input path="username" />
    <form:password path="password" />
    <form:errors path="*" cssClass="errorbox" />
    <input type="submit" value="Submit">
</form:form>

在这个例子中,modelAttribute指向了表单背后的模型对象(通常是一个命令对象或表单对象),而path属性则指定了模型对象中的具体属性。

源码解析

Spring Form标签库的源码实现了多个标签类,每个类都扩展了Spring的AbstractFormTag或者其他的基础标签类。这些类在运行时会生成HTML,并且与Spring的DataBinder对象集成,这个对象负责实现数据绑定和验证逻辑。

InputTag为例,这是Spring Form标签库中用于渲染<input type="text">标签的类:

@SuppressWarnings("serial")
public class InputTag extends AbstractHtmlInputElementTag {

    // ...代码省略...

    @Override
    protected int writeTagContent(TagWriter tagWriter) throws JspException {
        tagWriter.startTag("input");

        writeDefaultAttributes(tagWriter);
        writeValue(tagWriter);

        tagWriter.endTag();
        return SKIP_BODY;
    }

    // ...其它方法...

}

这个类重写了writeTagContent方法,用于写入<input>标签的内容,writeDefaultAttributes方法用于填充默认属性,如idname等,而writeValue方法则用于绑定当前表单对象的对应属性的值。

代码演示

假设您有一个名为UserForm的表单对象,它有usernamepassword两个属性。您可以在控制器中将其添加到模型中,并在JSP页面上使用Spring Form标签库来构建表单。

首先,是控制器方法:

@Controller
public class UserController {

    @GetMapping("/register")
    public String showForm(Model model) {
        model.addAttribute("userForm", new UserForm());
        return "registerForm";
    }

    @PostMapping("/register")
    public String submitForm(@ModelAttribute("userForm") UserForm userForm,
                             BindingResult result) {
        if (result.hasErrors()) {
            return "registerForm";
        }
        // ...处理用户注册...
        return "redirect:/success";
    }
}

然后,在JSP页面registerForm.jsp中,您构建表单:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <!-- ... -->
</head>
<body>
    <form:form modelAttribute="userForm" method="post">
        <form:label path="username">Username:</form:label>
        <form:input path="username" />
        <form:errors path="username" cssClass="error" />
        
        <form:label path="password">Password:</form:label>
        <form:password path="password" />
        <form:errors path="password" cssClass="error" />
        
        <input type="submit" value="Register"/>
    </form:form>
</body>
</html>

在这个例子中,<form:form>标签创建了一个<form>元素,它绑定到了名为userForm的模型属性。<form:input><form:password>标签创建了对应的输入字段,并且绑定到了userForm对象的usernamepassword属性。<form:errors>标签用于显示字段的错误信息。

通过使用Spring Form标签库,开发者可以轻松处理表单提交,并管理表单的验证错误,同时将JSP页面与后端代码保持良好的整合和解耦。

8、Spring MVC的数据绑定

Spring MVC的数据绑定是一个将请求参数映射到Java对象属性的过程,这个过程通常由Spring MVC在后台自动完成。数据绑定的核心是DataBinder类,它负责将请求参数转换成JavaBean的属性值。

数据绑定流程

  1. HandlerMapping确定控制器:当一个请求到达时,首先DispatcherServlet会查询HandlerMapping以找到处理该请求的控制器。

  2. 确定参数绑定策略:Spring MVC通过HandlerMethodArgumentResolver来决定如何解析控制器方法参数。

  3. 数据绑定:对于需要绑定的参数,比如使用了@ModelAttribute注解的命令对象,Spring MVC使用WebDataBinderDataBinder的子类)来执行数据绑定。

  4. 类型转换和格式化DataBinder使用ConversionServicePropertyEditor来转换请求参数的类型,如果需要,还会使用Formatter进行格式化。

  5. 验证:如果配置了验证器(Validator),DataBinder会在绑定之后调用验证逻辑。

DataBinder源码解析

以下是一个简化的版本的DataBinder源码,展示了数据绑定的核心方法:

public class DataBinder {

    private final Object target;

    private final Map<String, Object> bindingResult;

    public DataBinder(Object target) {
        this.target = target;
        this.bindingResult = new HashMap<>();
    }

    // 绑定请求参数到目标对象的方法
    public void bind(PropertyValues pvs) {
        // 创建一个BeanWrapper来包装目标对象
        BeanWrapper bw = new BeanWrapperImpl(target);
        // 遍历所有的属性值
        for (PropertyValue pv : pvs.getPropertyValues()) {
            // 设置属性值,可能会涉及类型转换
            bw.setPropertyValue(pv);
        }
    }

    // ... 其他方法 ...
}

DataBinder类使用BeanWrapper来处理实际的属性设置工作,BeanWrapper是Spring的一个基础类,用于访问JavaBean的属性。

代码演示

假设你有一个简单的JavaBean User,它有usernameage两个属性:

public class User {
    private String username;
    private int age;

    // standard setters and getters
}

当请求到达一个控制器方法时,Spring MVC如何将请求参数绑定到User对象的属性上:

@Controller
public class UserController {

    @PostMapping("/user")
    public ModelAndView addUser(@ModelAttribute User user) {
        // 数据绑定在这里自动发生,将请求参数绑定到user对象的属性
        // 可以直接使用user对象
        // ... 处理添加用户的逻辑 ...

        return new ModelAndView("userView", "user", user);
    }
}

在表单提交到/user时,Spring MVC会自动创建一个User对象,并将请求参数绑定到该对象的属性上。例如,如果请求参数包含username=John&age=30,则User对象将有username属性值为Johnage属性值为30

数据绑定过程中可能会涉及复杂的转换逻辑,例如,当表单参数是一个日期或者自定义对象时。Spring MVC通过PropertyEditorConverter接口来处理这种类型转换。这些转换器可以被注册到DataBinder中,并在绑定过程中使用。

还有一个格式化的概念,这是类型转换的一个特殊情况。Spring提供了@NumberFormat@DateTimeFormat注解来声明如何将字符串格式化为数字或日期类型。

如果需要在绑定过程中进行验证,可以使用@Valid注解与DataBindervalidate方法配合使用。这时,相关的Validator实现会被调用,以检查对象的属性值是否满足约束条件。

数据绑定和验证的结果会被保存在BindingResult对象中,这个对象通常作为控制器方法的一个参数跟随@ModelAttribute注解的参数出现。这使得控制器可以检查和处理可能发生的错误。

@Controller
public class UserController {

    @PostMapping("/user")
    public String addUser(@Valid @ModelAttribute User user, BindingResult result) {
        if (result.hasErrors()) {
            // 处理错误,通常是返回原始表单视图,并显示错误信息
            return "userForm";
        }
        // ... 处理添加用户的逻辑 ...
        return "redirect:/userSuccess";
    }
}

以上展示了Spring MVC数据绑定的概念、流程、源码解析和实际使用。通过这些机制,Spring MVC使得从请求到后端模型的数据传递变得简单和可控。

9、如何在Spring MVC中进行数据验证?

在Spring MVC中,数据验证通常是通过Java Validation API(即JSR-303和JSR-349)和/或使用Spring的Validator接口来实现的。数据验证可以确保提交的表单满足业务逻辑的要求(如:非空、格式正确、大小限制等)。

使用JSR-303和JSR-349 Bean Validation

  1. 依赖配置:首先确保你的项目中包含了Bean Validation的实现,比如Hibernate Validator。

    <!-- Maven中的依赖 -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>your.version.here</version>
    </dependency>
    
  2. 模型类注解:在你的模型(domain/model)类上应用适当的注解来定义验证规则。

    import javax.validation.constraints.*;
    
    public class User {
        @NotEmpty
        private String username;
    
        @Min(18)
        private int age;
    
        // standard setters and getters
    }
    
  3. 控制器中使用:在控制器方法中,使用@Valid(或@Validated)注解来触发验证,并使用BindingResult来接收验证结果。

    @PostMapping("/register")
    public String submitRegistration(@Valid @ModelAttribute("user") User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "registrationForm";
        }
        // 正常的业务逻辑
        return "redirect:/success";
    }
    

使用Spring的Validator接口

对于更复杂的验证逻辑,或当你希望编程式地控制验证逻辑时,你可以实现Spring的Validator接口。

  1. 实现Validator接口:创建一个类实现Validator接口,并覆盖supportsvalidate方法。

    public class UserValidator implements Validator {
    
        public boolean supports(Class<?> clazz) {
            return User.class.isAssignableFrom(clazz);
        }
    
        public void validate(Object obj, Errors errors) {
            User user = (User) obj;
            ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "field.required");
            if (user.getAge() < 18) {
                errors.rejectValue("age", "user.min.age");
            }
        }
    }
    
  2. 在控制器中应用Validator:在控制器中声明一个Validator类型的实例,并在处理表单提交的方法中应用它。

    @Controller
    public class UserController {
    
        private final Validator userValidator = new UserValidator();
    
        @InitBinder
        protected void initBinder(WebDataBinder binder) {
            binder.addValidators(userValidator);
        }
    
        @PostMapping("/register")
        public String submitRegistration(@ModelAttribute("user") User user, BindingResult bindingResult) {
            userValidator.validate(user, bindingResult);
            if (bindingResult.hasErrors()) {
                return "registrationForm";
            }
            // 正常的业务逻辑
            return "redirect:/success";
        }
    }
    

深入源码

在Spring框架中,DataBinder是执行数据绑定和验证的核心组件。以下是验证逻辑在DataBinder中的简化表示:

public class DataBinder {

   // ...其他方法...

   public void validate(Object... validationHints) {
       Validator validator = getValidator();
       if (validator != null) {
           if (errors == null) {
               errors = new BeanPropertyBindingResult(target, objectName);
           }
           if (validationHints.length > 0) {
               validator.validate(target, errors, validationHints);
           } else {
               validator.validate(target, errors);
           }
       }
   }
}

validate方法检查是否存在一个Validator,如果存在,它将会调用validate方法来验证目标对象。

代码示例

以下是一个简单的Spring MVC应用程序中的验证示例,它使用了JSR-303注解:

@Controller
public class UserController {

   @GetMapping("/register")
   public String showRegistrationForm(Model model) {
       model.addAttribute("user", new User());
       return "registerForm";
   }

   @PostMapping("/register")
   public String submitRegistration(@Valid @ModelAttribute("user") User user, BindingResult bindingResult) {
       if (bindingResult.hasErrors()) {
           return "registerForm";
       }
       // 将用户信息保存到数据库
       return "redirect:/success";
   }
}

在JSP注册表单中:

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<form:form modelAttribute="user" action="${pageContext.request.contextPath}/register" method="POST">
   <form:input path="username" />
   <form:errors path="username" cssClass="error" />
   
   <form:input path="age" />
   <form:errors path="age" cssClass="error" />
   
   <input type="submit" value="Register" />
</form:form>

在这个示例中,如果用户输入无效(如username为空或age小于18),则会在表单上显示错误消息,且不会继续表单的提交。如果输入有效,用户数据会被处理(例如保存到数据库),然后跳转到成功页面。

数据验证通常发生在请求处理管道的Controller层,BindingResult必须紧跟在具有@ModelAttribute@RequestBody注解的方法参数之后,以便捕获并处理由@ValidValidator引发的任何错误。如果有错误,可以选择返回相同的视图以显示错误,或者执行其他错误处理逻辑。

Spring的验证框架是灵活的,你可以根据需要将多个验证器应用于同一个对象,通过WebDataBinderaddValidators方法来组合它们。此外,你还可以对验证器进行分组,以便根据上下文应用不同的验证规则集合。

通过这些机制,Spring MVC提供了强大而灵活的数据验证框架,它可以轻松地集成到任何Spring MVC应用程序中。

10、Spring Security与Spring MVC的集成

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,它为Spring应用程序提供了安全性支持。它与Spring MVC紧密集成,提供了声明性和编程式的安全性控制。

Spring Security基本概念

  1. Authentication(身份验证): 确认用户身份的过程。
  2. Authorization(授权): 决定一个已认证的用户是否可以执行特定操作的过程。
  3. Principal(主体): 一个被认证的用户。
  4. Granted Authority(授权权限): 被授予一个主体的权限,通常以角色的形式出现。
  5. Filter Chain(过滤器链): 一系列过滤器,用于拦截HTTP请求进行安全检查。

集成Spring Security

依赖配置

首先,添加必要的依赖到你的项目中。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
安全配置

创建一个配置类继承WebSecurityConfigurerAdapter,并覆写相关方法以提供安全配置。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests() // 对请求进行授权
                .antMatchers("/public/**").permitAll() // 公共路径允许访问
                .anyRequest().authenticated() // 其他任何请求都需要认证
            .and()
            .formLogin() // 开启表单登陆
                .loginPage("/login") // 自定义登录页面
                .permitAll()
            .and()
            .logout() // 配置登出
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            // 配置用户详情服务,这里简单地使用内存存储
            .inMemoryAuthentication()
                .withUser("user").password(passwordEncoder().encode("password")).roles("USER")
            .and()
                .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
控制器保护

使用@Secured@PreAuthorize/@PostAuthorize注解来保护控制器方法。

@Controller
public class UserController {

    @GetMapping("/profile")
    @PreAuthorize("hasRole('USER')")
    public String userProfile(Model model) {
        // 返回用户资料页面
        return "userProfile";
    }

    @GetMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminDashboard(Model model) {
        // 返回管理员面板
        return "adminDashboard";
    }
}
Spring Security过滤器

Spring Security的核心是一系列的过滤器。每个请求都会通过定义好的过滤器链,每个过滤器执行特定的安全功能,如身份验证、授权等。

public class SecurityFilterChain { 

    public FilterChainProxy createFilterChain() {

        List<SecurityFilterChain> filterChains = new ArrayList<>();
        filterChains.add(new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/login"),
                Arrays.asList(
                        new UsernamePasswordAuthenticationFilter(), 
                        new BasicAuthenticationFilter())));
        // ... 其他过滤器链配置 ...

        return new FilterChainProxy(filterChains);
    }
}

这段代码是抽象的而非实际的Spring Security源代码。在实际代码中,FilterChainProxy由框架创建,并且根据HttpSecurity配置自动装配过滤器链。

集成Spring Security和Spring MVC时,以下是一些重要概念和最佳实践:

  • CSRF Protection: Spring Security提供了跨站请求伪造(CSRF)保护,默认情况下是启用的。它要求所有修改状态的请求(POST, PUT, DELETE等)都需要包含一个CSRF令牌。

  • Session Management: Spring Security提供了会话固定保护、会话超时和并发会话控制机制。

  • Method Security: 除了在HTTP层保护资源外,还可以在服务层方法上使用注解直接控制访问。

  • Custom UserDetailsService: 实现UserDetailsService以从数据库或其他源加载用户详细信息。

  • Password Storage: 使用PasswordEncoder实例来安全地存储密码。

  • Exception Handling: 处理认证和授权异常,比如定制AccessDeniedHandler

  • SSL/TLS: 配置requiresChannel()来要求或强制使用HTTPS。

代码示例

假设你有一个简单的登录页面:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<body>
<h2>Login</h2>
<form:form login="/login" method="post">
    <form:input path="username" />
    <form:password path="password" />
    <input type="submit" value="Log in" />
</form:form>
</body>
</html>

在这个示例中,form:form标签将自动包括CSRF令牌。用户提交表单后,如果身份验证成功,Spring Security将重定向到默认的成功URL或defaultTargetUrl(如果配置了的话)。如果身份验证失败,它将重定向到/login?error路径,并且可以在登录页面上显示错误消息。

通过这种方式,Spring Security提供了一个全面的安全解决方案,保护Spring MVC应用程序免受多种安全威胁。

11、使用Spring MVC时的注意点

在使用Spring MVC构建Web应用程序时,有多个方面需要注意,以确保应用程序的健壮性、安全性、性能和可维护性。以下是使用Spring MVC时需要注意的一些详细事项:

1. 控制器设计

  • 单一职责原则:每个控制器应该专注于单一功能区域,避免将所有的请求处理都写在一个控制器中。
  • 请求映射:确保URL到控制器方法的映射是清晰和一致的,使用组织良好的路由策略。
  • 请求参数验证:使用@Valid或手动验证输入参数,确保输入数据是正确的。
  • 使用DTOs:为每个操作创建数据传输对象(DTOs),而不是直接在控制器中使用领域模型。

2. 异常处理

  • 统一的异常处理:使用@ControllerAdvice@ExceptionHandler提供全局异常处理逻辑,而不是在每个控制器方法中处理异常。
  • 明确的错误响应:返回明确的错误信息和适当的HTTP状态代码。

3. 数据绑定与验证

  • 类型安全的数据绑定:使用@ModelAttribute来绑定请求参数到对象,确保类型安全。
  • 防止绑定错误:使用@InitBinderWebDataBinder避免不必要或危险的字段绑定。

4. 安全性

  • CSRF保护:确保Spring Security的CSRF保护功能是启用的,尤其是在处理表单提交时。
  • 避免暴露敏感信息:不要在URL中传递敏感信息,比如密码或私钥。
  • 输入验证:应用后端输入验证以避免XSS、SQL注入等安全漏洞。
  • 使用HTTPS:为应用程序配置SSL/TLS以确保数据传输的安全。

5. 性能优化

  • 视图缓存:对于不经常变更的数据,使用缓存来减少服务器的负载。
  • 限制数据载入:使用分页或懒加载策略来限制一次性加载到内存的数据量。

6. 数据管理

  • 事务管理:正确配置并使用Spring的声明式事务管理。
  • 服务层抽象:在业务逻辑和数据访问代码之间提供一个服务层,这有助于保持控制器的简洁,并促进代码重用。

7. 视图技术

  • 适当的视图技术:根据需要选择正确的视图技术(例如Thymeleaf, JSP, Freemarker等)。
  • 视图模板:使用视图模板来避免重复的HTML代码。

8. REST API设计

  • 状态码使用:当设计RESTful API时,正确使用HTTP状态码。
  • 资源表述:使用@RestControllerResponseEntity来操作资源表述。
  • HATEOAS:为REST API实现HATEOAS(Hypermedia As The Engine Of Application State)原则,提供链接导航信息。
  • API版本管理:考虑到未来可能的API变更,设计一个明智的API版本策略。

9. 国际化

  • 国际化支持:使用MessageSource等Spring的国际化支持,确保应用程序可以轻松地本地化。

10. 测试

  • 单元测试:为控制器编写单元测试,使用MockMvc来模拟HTTP请求和断言响应。
  • 集成测试:实施集成测试来验证应用程序不同层次之间的交互。

11. 代码组织

  • 配置管理:集中管理应用程序的配置,并使用Spring的环境抽象来针对不同的部署环境调整配置。
  • 模块化:保持代码的模块化,通过功能划分代码库。

12. 文档

  • API文档:如果你提供了REST API,使用Swagger或Spring REST Docs自动生成API文档。

13. 遵循Spring框架的最佳实践

  • 使用Spring的便利:利用Spring提供的便利,比如依赖注入、Spring Boot自动配置等功能,避免“重复发明轮子”。

遵循这些注意事项将有助于你构建一个健壮、安全、可维护并且易于扩展的Spring MVC应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辞暮尔尔-烟火年年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值