Spring Framework 学习笔记(3) Sping MVC

1. 背景

Sping MVC 是在 Spring 之上的框架,用于开发 Web 程序。

2. 初步了解 Spring MVC

2.1 知识

Spring MVC 是建立在 Servlet API 之上的 Web 框架,包含在 Spring Framework 中。MVC 是指 模型,视图,控制器的意思,Spring MVC 实现了这种思想。

Spring MVC 分离了控制器、模型对象、过滤器以及处理程序对象的角色,这种分离让它们更容易进行定制。

Spring MVC 不依赖 JSP,可以使用其他模板引擎(JSP,thymeleaf等)。RESTful API 返回的 JSON 格式可以理解为 json View,也是 MVC。

Spring MVC 与许多其他 Web 框架一样,是围绕前端控制器模式( front controller )设计的,其中DispatcherServlet为请求处理提供统一入口,而实际工作由委托组件处理。

一个HTTP请求经过 Spring MVC 需要经历的过程如下:

image.png
  • 1、 HTTP 请求 携带用户请求的内容,比如表单等 到达 DispactcherServlet。
  • 2和3、DispactcherServlet 需要将请求委托给其他组件来执行,它查询 处理器映射(Handler Mapping)以确定具体将请求转发到哪个 控制器 (Controller)
  • 4和5、DispactcherServlet 将请求转发到具体选定的控制器(Controller),Controller 负责访问服务和数据库获得 模型( Model ),并返回一个视图名称。
  • 6和7、DispactcherServlet 将模型和视图 发送到一个 视图解析器 ( View Resolver), 由视图解析器 使用模型渲染输出到视图。
  • 8和9、DispactcherServlet 将视图的呈现内容返回,响应到请求内容给客户端。

2.2 编写一个精简的 MVC 项目

刚刚说了 一个请求所要经历的过程,提到了几个组件,下面我们通过搭建基础版的项目进一步了解。

传统的web项目需要一个web.xml进行配置,包括 Servlet的配置映射,请求映射,视图解析,异常处理,委托组件等。DispatcherServlet 需要知道这些配置。

我们这里不这么做,而由 java 代码配置 DispatcherServlet 。 通过继承 AbstractAnnotationConfigDispatcherServletInitializer 来实现,当它部署在 sevlet 3.0的容器中时,容器会自动发现它并应用配置,示例:

/**
 * web app 的初始化辅助类。等同于 web.xml
 */
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{ServletConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

上面的代码中配置说明如下:

  • getRootConfigClasses() 返回的类将会用来配置 ContextLoaderListener 创建的应用上下文中的Bean。
  • getServletConfigClasses() 返回的类用来配置 web 应用,比如控制器,视图解析器等。

通常在一个 web 应用中有这么两个上下文:

  • Servlet WebApplicationContext : 包含 控制器,视图解析器,Handler映射等。
  • Root WebApplicationContext: 通常包含基础架构 bean,例如数据存储库和业务服务Bean。

关系如下:

image.png

我这里 RootConfig 是空的。

@Configuration
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {
}

ServletConfig 类中配置一个 jsp 视图解析器。

@Configuration
@EnableWebMvc
@ComponentScan
public class ServletConfig extends WebMvcConfigurerAdapter {


    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

写一个简单的 Controller

@Controller
@RequestMapping("/main")
public class HelloWorldController {
    @RequestMapping(value = "/say", method = RequestMethod.GET)
    public String sayHello(Model model) {
        model.addAttribute("yourname", "zhangsan");
        return "welcome";
    }
}

welcome.jsp 文件代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>HelloWorld page</title>
</head>
<body>
<h1>这是首页</h1>
Your Name : ${yourname}
</body>
</html>

使用 Spring 结合 Spring MVC 的一个精简版的项目就搭建好了。
我的完整代码示例见:https://github.com/vir56k/java_demo/tree/master/spring_mvc_demo_1

2.3 读取请求中的参数

支持下述参数:

  • 查询字符串
  • Form 表单中的
  • 请求路径中的

示例:

使用 @RequestParam 读取查询字符串中或表单数据的参数值

@GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { 
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

使用 @PathVariable 注解读取 青汽路径的参数。

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}

2.4 参数的合法性校验

Spring Framework 提供对 Java Bean Validation API 的支持。

示例:

public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}
public class User {

    private String email;

    @NotNull @Email
    public String getEmail() {
      return email;
    }

    public void setEmail(String email) {
      this.email = email;
    }
}

public class UserService {

  public void createUser(@Email String email,
                         @NotNull String name) {
    ...
  }
}

更多请参考:https://beanvalidation.org/

2.5 视图渲染

而类似 JSP 开发的方式已是古老的方法。较新一点的是 Thymeleaf 框架。当前(本文写作时间2021-07-06)比较流行的开发方式是前后端分离的技术,使用 ReactJS,VUE 单独开发项目。本文不再多介绍。

2.6 扩展

@RequestMapping
@RequestMapping 标识了这是一个请求映射,还有一些基于此扩展的方法,像下面这些,从名字就能看出具体的含义。

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

自定义 servlet 和 filter
扩展一下,通过继承 WebApplicationInitializer 可以注册自定义 servlet 和 filter:

/**
 * 初始化 web 应用
 */
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext) throws ServletException {
        // 注册一个 servlet
        ServletRegistration.Dynamic servlet = servletContext.addServlet("myservlet1", MyServlet.class);
        servlet.addMapping("/custom/**");

        // 注册一个过滤器
        FilterRegistration.Dynamic filter1 = servletContext.addFilter("filter1", MyFilter1.class);
        filter1.addMappingForUrlPatterns(null, false, "/custom/*");
    }
}

Multipart 解析器和文件上传
注册一个 Multipart 解析器

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

}

接收上传文件

@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";
    }
}

异常处理
Spring MVC 提供了多种形式将异常转化成 响应:

  • 特定的 Spring 异常将自动映射到 HTTP 的状态码
  • 异常上使用 @ReponseStatus 注解,可以将其对应到某个 HTTP 状态码
  • 方法上使用 @ExceptionHandler 注解,标识处理异常。

@ResponseStatus 注解的自定义异常,将自动映射到 HTTP 的状态码:

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "啊呜,找不见了...")
public class MyException extends RuntimeException {

}

使用 @ExceptionHandler 注解捕获异常:
@ExceptionHandler 作用在 controller 类上时,可以捕获这个 controller 的异常。

// @ExceptionHandler 注解,在这里会捕获这个类异常
    @ExceptionHandler(MyException2.class)
    public String handleException() {
        return "errorrrr";
    }

@ExceptionHandler 注解 结合“ 控制器通知 ” 可以捕获所有 控制器的异常。

控制器通知( controller advise ) 是指 被标注了@ControllerAdvice 注解的类。这个类可以配合以下的注解使用:

  • @ExceptionHandle 标注的方法
  • @InitBinder 标注的方法
  • @ModelAttribute 标注的方法
    在标注了 @ControllerAdvice 的类中,上述的三个方法会运用到整个应用程序所有控制器中带有 @RequestMapping 方法上。
    示例:
@ControllerAdvice
public class AppExceptionHandler {

    // @ExceptionHandler 注解,在这里会捕获这个类异常
    @ExceptionHandler(MyException3.class)
    public String handleException() {
        return "errorrrr";
    }
}

3. 示例

我的代码示例见:https://github.com/vir56k/java_demo/tree/master/spring_mvc_demo_2

4.参考:

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html

http://websystique.com/springmvc/spring-4-mvc-helloworld-tutorial-annotation-javaconfig-full-example/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值