Java必知必会系列:Web框架与Spring MVC

发布于:2023-10-25 ⋅ 阅读:(106) ⋅ 点赞:(0)

作者:禅与计算机程序设计艺术

1.背景介绍

随着互联网信息爆炸式增长、移动互联网的兴起、云计算的普及,Web开发已经成为一种越来越重要的技能,同时也是IT行业的一个热门方向。Web开发的技术栈主要包括三层:前端、后端、数据库,在现代开发模式下,后端开发人员更倾向于选择基于框架的开发方式。

传统的Java Web开发的框架有Struts、Hibernate等,而现在流行的Web开发框架主要有Spring MVC、Struts2、Play Framework等。它们都遵循MVC(Model-View-Controller)设计模式,其中MVC分离可以有效提高开发效率并降低耦合性。

本文将介绍 Spring MVC 的基础知识和核心组件,并通过实际案例进行讲解,对读者理解Spring MVC有一个很好的帮助。

2.核心概念与联系

2.1 Spring 框架简介

Spring Framework 是由 Pivotal、VMWare 和其他开源社区贡献者共同发起的轻量级开源 Java 框架,是围绕核心容器(如 Core container、Beans 模块、context 模块)和领域特定语言(如 web MVC、 messaging、数据访问、远程调用)构建的全面且功能丰富的应用框架。Spring 框架使得开发人员能够快速构造单个或协作的应用,它可以轻松地整合各种优秀的第三方库来解决复杂的业务需求。

Spring 框架由模块组成,这些模块共同构建了一个健壮、可测试且易于使用的体系结构。Spring 框架的几个主要模块包括:

  • Core Container:提供了 Bean factory,支持IoC(控制反转)和 DI(依赖注入),用于管理应用中对象的生命周期;ApplicationContext 支持多种配置形式(XML、Java注解、Groovy脚本)。

  • Context:提供了各种上下文,如 ApplicationContext,它是BeanFactory的子接口,为 BeanFactory 提供了额外的功能;MessageSource 提供国际化支持,并支持根据指定的国家/地区返回相应的消息。

  • DAO(Data Access Object):提供了一个简单的编程模型来存储和检索数据源中的对象;Spring JDBC 模块允许开发人员编写 JDBC 操作的精简代码,简化数据库访问的复杂性。

  • ORM(Object Relational Mapping):用于实现面向对象编程的持久化机制,允许开发人员使用 Hibernate 或 TopLink 通过映射关系将他们的实体类与底层的数据存储建立关联。

  • Web:提供了面向 WEB 应用的集成环境,包括支持多种视图技术(如 Velocity、Tiles、FreeMaker)的 MVC web 框架,以及对 RESTful Web 服务的支持。

  • AOP(Aspect-Oriented Programming):提供了面向切面编程的模型,可以用来在不修改代码的情况下增加横切关注点(crosscutting concerns)。比如日志记录、事务管理、安全检查等。

  • Messaging:提供了一套完整的消息传递解决方案,包括用于发布/订阅(pub/sub)消息的消息代理,以及用于异步通信(如 STOMP)的支持。

  • 测试:提供了单元测试和集成测试工具,可以帮助开发人员快速验证自己的代码是否正确工作,并防止错误的功能扩散到生产环境。

  • 事务(Transaction Management):提供声明式事务管理,可以使用 XML 文件或者 API 来定义事务属性。

  • Aspects:提供了一系列用于横切关注点的 AOP 集成,如缓存(Caching)、方法计时(Method Timing)、调度(Scheduling)等。

除了上述的模块之外,还有一些重要的特性如事件驱动模型(Event Driven Model)、非侵入式框架(Non-invasive)等。

2.2 Spring MVC 概念

Spring MVC 是 Spring 框架中的一个重要模块,其目的是为了构建基于 Web 的应用,提供控制器(DispatcherServlet)、处理器映射器(HandlerMapping)、视图解析器(ViewResolver)等功能。

Spring MVC 涉及的主要概念如下:

  • DispatcherServlet:该servlet是一个请求处理器,负责接受用户请求,查找 HandlerMapping 寻找处理器(Controller),然后将请求提交给处理器执行。通过视图解析器(ViewResolver)找到相应的视图(view)生成响应输出。

  • Controller:处理用户请求的类,负责业务逻辑的处理。

  • HandlerMapping:维护一个HandlerMapping表,保存已注册的Controller映射关系。

  • ViewResolver:找到相应的视图,并把数据填充到视图模板中,然后渲染视图。

  • ModelAndView:封装处理结果,包含视图名和模型数据。

  • Interceptor:拦截器,用于对请求做预处理和后处理。

  • LocaleResolver:用于解析客户端请求中的Locale信息。

  • MultipartResolver:用于文件上传。

  • FlashMapManager:用于FlashScope数据的获取和设置。

  • StaticResourceHttpRequestHandler:用于处理静态资源请求。

Spring MVC 使用了一个基于 servlet 的架构模型,前端控制器模式。这种架构模式通过一个中心的 Servlet 来处理所有的 HTTP 请求,并把请求分派给其他组件进行处理。Spring MVC 中的 DispatcherServlet 是这个架构的关键部分,负责对请求进行解析、调用 HandlerMapping 查找处理器进行处理,然后进行视图解析、渲染相应的视图返回给客户端。

3.核心算法原理和具体操作步骤以及数学模型公式详细讲解

Spring MVC 有多个组件构成,每个组件都会有独特的作用,但是 Spring MVC 中最核心的就是 DispatcherServlet 和 HandlerMapping。下面就从这两个组件讲起,分别介绍它们的原理和作用。

3.1 DispatcherServlet

DispatcherServlet 是 Spring MVC 的核心组件,它负责接收用户的请求并分派给不同的 Controller 来进行处理。它首先创建一个 HttpServletRequest 对象,并根据用户请求调用 HandlerMapping 来获取 Controller。然后将请求提交给 Controller 执行业务逻辑。Controller 根据业务逻辑处理结果,包装成 ModelAndView 返回给 DispatcherServlet。最后 DispatcherServlet 根据 ModelAndView 获取相应的视图,并将结果渲染到浏览器显示。

配置 DispatcherServlet

可以通过 Spring Boot 的配置文件 application.properties 来配置 DispatcherServlet。以下是一个例子:

spring.mvc.static-path-pattern=/resources/**
spring.mvc.locale=zh_CN
spring.mvc.throw-exception-if-no-handler-found=true
spring.mvc.favicon.enabled=false
spring.mvc.flash-scope-match-ip=true
server.error.whitelabel.enabled=false

配置参数描述如下:

  • static-path-pattern:指定静态资源文件的访问路径前缀。

  • locale:指定默认的本地化区域。

  • throw-exception-if-no-handler-found:当找不到对应的处理器时是否抛出异常。

  • favicon.enabled:是否开启 Favicon 支持。

  • flash-scope-match-ip:指定是否按 IP 地址匹配 FlashScope 数据。

  • whitelabel.enabled:当发生内部服务器错误时是否显示友好错误页面。

扩展 DispatcherServlet

Spring 提供了很多扩展点让你可以自定义 DispatcherServlet 的行为,例如自定义异常处理器、自定义视图解析器、自定义拦截器等。

ExceptionResolver

ExceptionResolver 可以用来自定义 Spring MVC 在遇到运行时异常时的行为,通常需要实现一个接口 ExceptionHandler,并且提供一个名为 resolveException 方法的实现。该方法传入一个 HttpRequest 对象、一个 HttpServletResponse 对象以及一个 RuntimeException 对象,返回 ModelAndView 对象。

@Component
public class MyExceptionResolver implements HandlerExceptionResolver {

  @Override
  public ModelAndView resolveException(HttpServletRequest request,
      HttpServletResponse response, Object handler, Exception ex) {
    // TODO: custom exception handling logic here
    return null;
  }
}
ViewResolver

ViewResolver 可以用来自定义 Spring MVC 如何解析和渲染视图。通常需要实现一个接口 ViewReslover,并且提供一个名为 resolveViewName 方法的实现。该方法传入一个字符串类型的视图名称(即配置在 controller 方法上的 @ResponseViet 注释的值),返回一个 View 对象。

@Component
public class MyViewResolver implements ViewResolver {

  private ResourceLoader resourceLoader;

  @Autowired
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

  @Override
  public View resolveViewName(String viewName, Locale locale) throws Exception {
    // TODO: create and configure a View object based on the view name
    return null;
  }
}
RequestToViewNameTranslator

RequestToViewNameTranslator 可以用来自定义 Spring MVC 将一个 HttpServletRequest 对象转换成视图名称的方法。通常需要实现一个接口 RequestToViewNameTranslator,并且提供一个名为 getViewName 方法的实现。该方法传入一个 HttpRequest 对象,返回一个视图名称字符串。

@Component
public class MyRequestToViewNameTranslator implements RequestToViewNameTranslator {

  @Override
  public String getViewName(HttpServletRequest request) throws Exception {
    // TODO: implement logic to translate from a request to a view name
    return "";
  }

}

3.2 HandlerMapping

HandlerMapping 把用户请求和处理器映射起来,这个过程就是 DispatcherServlet 为何能够完成请求处理的基础。通过 HandlerMapping ,Spring MVC 可以根据用户请求的信息(如 URL、HTTP 方法、参数等)来定位到对应的处理器(Controller)进行处理。HandlerMapping 也提供了灵活的配置方式,如根据注解自动映射处理器、配置文件手动映射处理器等。

配置 HandlerMapping

可以通过 xml 文件或者 JavaConfig 方式来配置 HandlerMapping 。以下是一个例子:

<!-- use annotations to map controllers -->
<bean class="org.springframework.web.servlet.config.annotation.DefaultAnnotationHandlerMapping" />

<!-- manually map handlers by using "mvc:mapping" elements in your XML configuration file -->
<mvc:annotation-driven>
  <mvc:mappings>
    <mvc:mapping path="/users" type="com.example.UsersController"></mvc:mapping>
    <mvc:mapping path="/orders" bean="orderController"/>
    <!--... more mappings... -->
  </mvc:mappings>
</mvc:annotation-driven>
// use component scanning to find Controllers as beans in the context
@Configuration
@EnableWebMvc
@ComponentScan(basePackages="com.example")
public class AppConfig extends WebMvcConfigurerAdapter {
  // override default mapping strategy with a custom one
  @Bean
  public SimpleUrlHandlerMapping urlMapping() {
    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    mapping.setOrder(-1); // ensure our custom mapping is used first
    return mapping;
  }
}

默认 HandlerMapping

Spring MVC 默认提供两种 HandlerMapping 实现:RequestMappingHandlerMapping (基于注解)和 BeanNameUrlHandlerMapping (根据控制器的名称映射)。

RequestMappingHandlerMapping 可以根据类的级别注解(如 @GetMapping@PostMapping@PutMapping 等)来进行路由映射。如果一个请求满足多个注解条件,则使用列表中第一个符合条件的处理器进行处理。

BeanNameUrlHandlerMapping 可以根据控制器类的名称来进行路由映射,它会根据控制器类的名称去容器中查找控制器实例,并把请求路由到对应控制器上进行处理。

当然,你也可以实现自己的 HandlerMapping 接口来定制化路由规则。

拓展 HandlerMapping

Spring MVC 提供了许多拓展点可以让你实现自己的 HandlerMapping。

RequestMappingInfoHandlerMapping

RequestMappingInfoHandlerMapping 继承自 RequestMappingHandlerMapping ,它的主要作用是根据 org.springframework.web.util.pattern.PathPatternParser 所定义的 Path 表达式来进行匹配请求路径。

@RestController
@RequestMapping("/api/")
class ExampleController {
    @GetMapping("/foo/{id}")
    public ResponseEntity<Void> handleGetFoo(@PathVariable int id) {
        // do something interesting here
        return ResponseEntity.ok().build();
    }

    @PatchMapping("/bar/{name}")
    public ResponseEntity<Void> handlePatchBar(@PathVariable("name") String name, @RequestBody Map<String, Integer> patch) {
        // do something even more interesting here
        return ResponseEntity.ok().build();
    }
}

上面的控制器可以被匹配到的路径包括:/api/foo/*'/api/bar/:name'

RouterFunctionHandlerMapping

RouterFunctionHandlerMapping 允许你使用基于 Java 函数的 DSL (Domain Specific Language)来进行路由配置。你可以按照自己的路由语法来声明路由,而不是采用约束性的注解。

@Bean
RouterFunction<ServerResponse> routes() {
    return route(GET("/"), req -> ServerResponse.ok().body("Hello World"));
}

上面的代码声明了一个只响应 GET 请求的路由 /

除此之外,还可以定义过滤器,在请求进入 RouterFunctionHandlerMapping 时自动触发。

@Bean
FilterFunction<ServerResponse, ServerResponse> securityFilter() {
    return (req, next) -> {
        if (!isAuthenticated()) {
            // redirect to login page or reject the request
            return ServerResponse.status(HttpStatus.UNAUTHORIZED).build();
        } else {
            // continue processing the request
            return next.handle(req);
        }
    };
}

@Bean
RouterFunction<ServerResponse> routes(FilterFunction<ServerResponse, ServerResponse> securityFilter) {
    return route(POST("/login"), req -> {
        // handle user authentication here
        return ServerResponse.ok().build();
    }).andRoute(GET("/secret"), req -> securityFilter.filter(req, res -> {
        // serve secret content only for authenticated users
        return ServerResponse.ok().body("This is a secret");
    }));
}
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到