SpringMVC记录

警告
本文最后更新于 2023-03-26,文中内容可能已过时,请谨慎使用。
/images/all/image-20230326163514733.png
  • DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性
  • Handler: 处理器,完成具体的业务逻辑,相当于 Servlet 或 Action
  • HandlerMapping: DispatcherServlet接收到请求之后,通过HandlerMapping 将不同的请求映射到不同的Handler
  • HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可通过实现该接口来完成
  • HandlerExecutionChain:处理器执行链,包括两部分内容:HandlerHandlerInterceptor(系统会有一个默认的Handlerlnterceptor,如果需要额外设置拦截,可以添加拦截器)
  • HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由HandlerApater来完成
  • ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet
  • ViewResolver:视图解析器,DispatcheServlet通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端
/images/all/image-20230326170634176.png

1)用户发送请求至前端控制器 DispatcherServlet。

2)DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。

3) 处理器映射器根据请求 url 找到具体的 Handler,生成处理器对象(handler)及处理器拦截器(handlerInterceptor)(如果有则生成)一并返回给 DispatcherServlet。

4) DispatcherServlet 通过 HandlerAdapter 处理器适配器调用处理器。

5) HandlerAdapter 执行handler(也叫后端控制器Controller)的方法。

6) Handler执行完成后返回 ModelAndView。

7) HandlerAdapter 将 handler 执行结果 ModelAndView 返回给 DispatcherServlet。

8) DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。

9) ViewReslover 解析后返回具体 View 对象。

10) DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)。

11) DispatcherServlet 响应用户。

1.添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.0.6</version>
</dependency>

2.在web.xml中配置DispatcherServlet,在resources下新建一个springmvc.xml文件用来注入 Bean

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

springmvc.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://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="cc.bnblogs"></context:component-scan>
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>

3.配置 tomcat

/images/all/image-20230325011250342.png
/images/all/image-20230325011310860.png

为了使用index.jsp中的中文在网页中不乱码,需要在该文件开头添加

<%@ page contentType="text/html;charset=utf-8" %>

为了使Handler输出到控制台的中文不乱码,需要在web.xml中添加过滤器

<!--添加一个过滤器,避免终端乱码-->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>

<!--拦截所有请求-->
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

为了使得RestController返回数据中的中文不乱码,在springmvc.xml中添加消息转换器配置

<mvc:annotation-driven>
    <!--消息转换器配置-->
    <!--用于解决controller返回给前端的数据乱码的问题-->
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

集成fastJson

先导入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.25</version>
</dependency>

springMVC.xml中进行配置

    <mvc:annotation-driven>
        <!--消息转换器配置-->
        <!--用于解决controller返回给前端的数据乱码的问题-->
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
            </bean>
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

编写 Controller 进行测试

/**
 * 接收一个json对象,返回User对象
 */
@RequestMapping(value = "json",method = RequestMethod.POST)
public User getJson(@RequestBody User user) {
    System.out.println("user = " + user);
    return user;
}

常见内置对象

对象 类名 作用
PageContext javax.servlet.jsp.PageContext jsp 的页面容器
request javax.servlet.http.HttpServletrequest 获取用户的请求信息
response javax.servlet.http.HttpServletResponse 服务器向客户端的响应信息
session javax.servlet.http.HttpSession 用来保存每一个用户的信息
application javax.servlet.ServletContext 保存当前项目的所有信息

Map 类型

Handler

@RequestMapping("view")
public class ViewHandler {
    @RequestMapping("map")
    public String map(Map<String, User> userMap) {
        User user = new User();
        user.setId(18);
        user.setName("张三");
        userMap.put("user",user);
        return "view";
    }
}

view.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<%--注意添加上面这一行配置--%>
<html>
<head>
    <title>Title</title>
</head>
<body>
    ${requestScope.user}
</body>
</html>

Model 类型

Handler

@RequestMapping("model")
public String model(Model model) {
    User user = new User();
    user.setId(18);
    user.setName("张三");
    model.addAttribute("user",user);
    return "view";
}

view.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<%--注意添加上面这一行配置--%>
<html>
<head>
    <title>Title</title>
</head>
<body>
    ${requestScope.user}
</body>
</html>

ModelAndView 类型

上面的都是直接将模型数据返回给 view,这里是两者同时返回

有多种方式可以设置 model 和 view,下面提供了 4 种实现方式

@RequestMapping("modelAndView")
public ModelAndView modelAndView() {
    User user = new User();
    user.setId(18);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView();
    // 添加model数据
    modelAndView.addObject("user",user);
    // 添加view
    modelAndView.setViewName("view");
    return modelAndView;
}

@RequestMapping("modelAndView1")
public ModelAndView modelAndView1() {
    User user = new User();
    user.setId(18);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView();
    // 添加model数据
    modelAndView.addObject("user",user);
    View view = new InternalResourceView("/view.jsp");
    modelAndView.setView(view);
    return modelAndView;
}

@RequestMapping("modelAndView2")
public ModelAndView modelAndView2() {
    User user = new User();
    user.setId(18);
    user.setName("张三");
    // 直接提供一个视图的名字
    ModelAndView modelAndView = new ModelAndView("view");
    modelAndView.addObject("user",user);
    return modelAndView;
}

@RequestMapping("modelAndView3")
public ModelAndView modelAndView3() {
    Map<String,User> userMap = new HashMap<String, User>();
    User user = new User();
    user.setId(18);
    user.setName("张三");
    userMap.put("user",user);
    // 直接提供一个视图的名字
    return new ModelAndView("view",userMap);
}

基于 Request 的方式

上面的方式本质上都是将模型数据添加到 request,也可以直接使用 request 来实现

添加依赖

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

Handler

@RequestMapping("request")
public String request(HttpServletRequest request) {
    User user = new User();
    user.setId(18);
    user.setName("张三");
    request.setAttribute("user",user);
    return "view";
}

使用@ModelAttribute 注解

  • 定义一个方法,该方法专门用来返回要填充到模型数据中的对象。
  • 注意:无论执行哪个具体的业务方法,都会先执行@ModelAttribute注解的方法
@ModelAttribute
public User getUser() {
    User user = new User();
    user.setId(18);
    user.setName("李四");
    return user;
}
  • 业务方法中无需再处理模型数据,只需返回视图即可。
@RequestMapping("modelAttr")
public String getUserAttribute(){
    return "view";
}
  • 基于session的方式
@RequestMapping("session")
public String session(HttpServletRequest request) {
    HttpSession httpSession = request.getSession();
    User user = new User();
    user.setId(18);
    user.setName("李四");
    httpSession.setAttribute("user",user);
    return "view";
}
@RequestMapping("application")
public String application(HttpServletRequest request) {
    ServletContext application = request.getServletContext();
    User user = new User();
    user.setId(18);
    user.setName("李四");
    application.setAttribute("user",user);
    return "view";
}

HandlerAdapter 默认并不提供 String 到 Date 类型的数据转换

但是可以自己创建一个StringDate的数据转换器,需要实现 Converter 接口

public class DateConverter implements Converter<String, Date> {
    private final String pattern;

    public DateConverter(String pattern) {
        this.pattern = pattern;
    }

    public Date convert(String s) {
        SimpleDateFormat format = new SimpleDateFormat(pattern);
        Date date = null;
        try {
            date = format.parse(s);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        return date;
    }
}

注入 Bean

<!--配置自定义转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <!--配置日期转换器-->
            <bean class="cc.bnblogs.converter.DateConverter">
                <constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
            </bean>
        </list>
    </property>
</bean>

<mvc:annotation-driven conversion-service="conversionService">
    <!--消息转换器配置-->
    <!--用于解决controller返回给前端的数据乱码的问题-->
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
        </bean>
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>application/json;charset=UTF-8</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

ConvertHandler

@RestController
@RequestMapping("convert")
public class ConvertHandler {
    @RequestMapping(value = "date",method = RequestMethod.POST)
    public String getDate(Date date) {
        return date.toString();
    }
}

导入依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

springmvc.xml 配置

<!--配置文件组件-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>

web.xml放行图片资源

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>

upload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
  <form action="/file/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="img" />
      <hr/>
    <input type="submit" value="上传">
    <img src="${requestScope.path}">
  </form>
</body>
</html>

fileHandler

@PostMapping("upload")
public String imgUpload(MultipartFile img, HttpServletRequest request) throws IOException {
    if (!img.isEmpty()) {
        // 保存路径: D:\apache-tomcat-9.0.73\webapps\ROOT\files
        // 每次重启tomcat后会删掉
        String path = request.getServletContext().getRealPath("files");
        // 获取上传文件的文件名
        String name= img.getOriginalFilename();
        // 存到tomcat安装目录下的bin文件夹下的files文件夹
        File file = new File(path,name);
        // 保存内容到文件中
        img.transferTo(file);
        // 保存到request中用于回显
        request.setAttribute("path","/files/" + name);
    }
    return "upload";
}

添加依赖

<!--jstl-->
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

<!--taglibs-->
<dependency>
    <groupId>org.apache.taglibs</groupId>
    <artifactId>taglibs-standard-impl</artifactId>
    <version>1.2.5</version>
    <scope>runtime</scope>
</dependency>

fileHandler

@PostMapping("uploads")
public String uploadImages(MultipartFile[] images,HttpServletRequest request) throws IOException {
    List<String> paths = new ArrayList<String>();
    for (MultipartFile img: images) {
        if (!img.isEmpty()) {
            // 保存路径: D:\apache-tomcat-9.0.73\webapps\ROOT\files
            // 每次重启tomcat后会删掉
            String path = request.getServletContext().getRealPath("files");
            // 获取上传文件的文件名
            String name= img.getOriginalFilename();
            // 存到tomcat安装目录下的bin文件夹下的files文件夹
            File file = new File(path,name);
            // 保存内容到文件中
            img.transferTo(file);
            // 保存到request中用于回显
            paths.add("/files/"+name);
        }
    }
    request.setAttribute("path",paths);
    return "uploads";
}

uploads.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="/file/uploads" method="post" enctype="multipart/form-data">
        file1: <input type="file" name="images" /> <br/>
        file2: <input type="file" name="images" /> <br/>
        file3: <input type="file" name="images" /> <br/>
        <input type="submit" value="上传">
    </form>
    <c:forEach items="${requestScope.path}" var="file">
        <img src="${file}" width="300px">
    </c:forEach>
</body>
</html>

创建一个实体类Account

@Data
public class Account {
    private String username;
    private String password;
}

创建一个自定义验证器AccountValidator,实现Validator接口

这里简单校验字段是否为空或者全是空格

public class AccountValidator implements Validator {
    /**
     * 是否支持对该数据类型进行验证
     * @param aClass
     * @return
     */
    public boolean supports(Class<?> aClass) {
        return aClass.equals(Account.class);
    }

    /**
     * 验证的具体逻辑
     * @param o
     * @param errors
     */
    public void validate(Object o, Errors errors) {
        // 校验用户名
        ValidationUtils.rejectIfEmptyOrWhitespace(errors,"username",null,"用户名不能为空或空格");
        // 校验密码
        ValidationUtils.rejectIfEmptyOrWhitespace(errors,"password",null,"密码不能为空或空格");
    }
}

springmvc.xml配置

<!--注入自定义验证器到IOC容器-->
<bean id="accountValidator" class="cc.bnblogs.validator.AccountValidator" />
<!--在MVC注册自定义验证器-->
<mvc:annotation-driven validator="accountValidator"/>

ValidatorHandler

@Controller
@RequestMapping("validator")
public class ValidatorHandler {
    @GetMapping("login")
    public String login(Model model) {
        // 绑定一个空数据,便于测试jsp
        model.addAttribute("account",new Account());
        return "login";
    }

    @PostMapping("/login")
    public String login(@Validated Account account, BindingResult bindingResult) {
        // 表单校验失败,返回登录页面
        if (bindingResult.hasErrors()) {
            return "login";
        }
        return "index";
    }
}

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
  <form:form modelAttribute="account" action="/validator/login" method="post">
    姓名: <form:input path="username"/>&nbsp;&nbsp;<form:errors path="username"/><br />
    密码: <form:input path="password"/>&nbsp;&nbsp;<form:errors path="password"/><br />
    <input type="submit" value="登录">
  </form:form>

</body>
</html>

表单验证

/images/all/image-20230326182249955.png

导入依赖

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.23.Final</version>
</dependency>

springmvc.xml添加配置

<mvc:annotation-driven></mvc:annotation-driven>

通过注解的方式直接在实体类中添加相关的验证规则。

@Data
public class Person {
    @NotEmpty(message = "用户名不能为空")
    private String username;
    @Size(min = 6,max=12,message = "密码必须为6-12位")
    private String password;
    @Email(regexp = "^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(?:\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$",message = "请输入正确的邮箱")
    private String email;
    @Pattern(regexp = "^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[5-79])|\n" +
             "(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|\n" +
             "(?:8[\\d])|(?:9[189]))\\d{8}$",message = "请输入正确的电话")
    private String phone;
}

ValidatorHandler

@GetMapping("person")
public String person(Model model) {
    model.addAttribute("person",new Person());
    return "person";
}

@PostMapping("/person")
public String person(@Valid Person person, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "person";
    }
    return "index";
}

person.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form:form modelAttribute="person" action="/validator/person" method="post">
  姓名: <form:input path="username"/>&nbsp;&nbsp;<form:errors path="username"/><br />
  密码: <form:password path="password"/>&nbsp;&nbsp;<form:errors path="password"/><br />
  邮箱: <form:input path="email"/>&nbsp;&nbsp;<form:errors path="email"/><br />
  电话: <form:input path="phone"/>&nbsp;&nbsp;<form:errors path="phone"/><br />
  <input type="submit" value="提交">
</form:form>
</body>
</html>

表单验证

/images/all/image-20230326204115810.png

相关文章