SpringMVC

实现MVC设计模式的企业级开发框架,Spring的一个子模块,无需整合,开发起来更便捷

SpringMVC的核心组件

  • DispatcherServlet:前置控制器,是整个流程控制的核⼼,控制其他组件的执⾏,进⾏统⼀调
    度,降低组件之间的耦合性,相当于总指挥。
  • Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。
  • HandlerMapping:DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求映
    射到不同的 Handler。
  • HandlerInterceptor:处理器拦截器,是⼀个接⼝,如果需要完成⼀些拦截处理,可以实现该接
    ⼝。
  • HandlerExecutionChain:处理器执⾏链,包括两部分内容:Handler 和
  • HandlerInterceptor(系统会有⼀个默认的 HandlerInterceptor,如果需要额外设置拦截,可以
    添加拦截器)。
  • HandlerAdapter:处理器适配器,Handler 执⾏业务⽅法之前,需要进⾏⼀系列的操作,包括表
    单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由
  • HandlerApater 来完成,开发者只需将注意⼒集中业务逻辑的处理上,DispatcherServlet 通过
  • HandlerAdapter 执⾏不同的 Handler。
  • ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给
    DispatcherServlet。
  • ViewResolver:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染
    结果响应给客户端。

SpringMVC工作流程

image.png

SpringMVC的注解

@RequestMapping

Spring MVC 通过 @RequestMapping 注解将 URL 请求与业务⽅法进⾏映射,在 Handler 的类定义处
以及⽅法定义处都可以添加 @RequestMapping ,在类定义处添加,相当于客户端多了⼀层访问路径。

相关参数 value,method,params

@Controller

@Controller 在类定义处添加,将该类交个 IoC 容器来管理(结合 springmvc.xml 的⾃动扫描配置使
⽤),同时使其成为⼀个控制器,可以接收客户端请求。

Restful风格的URL

传统类型:http://localhost:8080/hello/index?name=zhangsan&id=10
REST:http://localhost:8080/hello/index/zhangsan/10

传统的通过jsp+servlet开发方式

jsp界面通过表单的action属性将请求发送到servlet中处理

public class login extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	/**
		 * Constructor of the object.
		 */
	public login() {
		super();
	}
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String username=request.getParameter("username");
	    String password=request.getParameter("password");
		HttpSession session=request.getSession();
		session.setAttribute("username", username);
		UserDao userDao=UserDao.getInstance();
		User result=userDao.queryUser(username, password);
		response.setContentType("text/html;charset=utf-8");
		if(result!=null){			
			response.sendRedirect("homePage.jsp");/
		}else{			
			PrintWriter out = response.getWriter();
			out.print("<script language='javascript'>alert('');window.location.href='login.jsp';</script>");
		}

	}


}

映射cookie

 @RequestMapping("/cookie")
    public String cookie(@CookieValue("JSESSIONID") String jessionId){
        System.out.println(jessionId);
        return "index";
    }

遇到的问题

安装lombok插件后并引入相关依赖后,仍无法识别@Date注解
解决方法:引入最新版本的lombok依赖

使用javaBean绑定参数

Spring MVC 会根据请求参数名和 JavaBean 属性名进⾏⾃动匹配,⾃动为对象填充属性值,同时⽀持级联属性。
当请求中文出现乱码时,可以在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>

转发与重定向

不写默认为转发
转发 return "forward:/index.jsp";
重定向 return "forward:/index.jsp";

数据绑定

将@Controller前面加一个Rest就可以把每个方法前的@ResponseBody注解去掉

@RestController 表示该控制器会直接将业务⽅法的返回值响应给客户端,不进⾏视图解析。
@Controller 表示该控制器的每⼀个业务⽅法的返回值都会交给视图解析器进⾏解析,如果只需要将数
据响应给客户端,⽽不需要进⾏视图解析,则需要在对应的业务⽅法定义处添加 @ResponseBody。
@ResponseBody 表示 Spring MVC 会直接将业务⽅法的返回值响应给客户端,如果不加
@ResponseBody 注解,Spring MVC 会将业务⽅法的放回值传递给 DispatcherServlet,再由
DisptacherServlet 调⽤ ViewResolver 对返回值进⾏解析,映射到⼀个 JSP 资源。

基本数据类型

后面必须要加对应参数,否则报错,可以通过包装类来解决

  @RequestMapping("baseType")
    @ResponseBody
    public String baseType(int i){
        return ""+i;
    }

包装类

包装类可以接收 null,当 HTTP 请求没有参数时,使⽤包装类定义形参的数据类型,程序不会抛出异
常。
@RequestParam
value = "num":将 HTTP 请求中名为 num 的参数赋给形参 id。
requried:设置 num 是否为必填项,true 表示必填,false 表示⾮必填,可省略。
defaultValue = “0”:如果 HTTP 请求中没有 num 参数,默认值为0

数组

  @RequestMapping("/arr")
    public String arr(int[] arr){
        String s = Arrays.toString(arr);
        return s;
    }

List

Spring MVC 不⽀持 List 类型的直接转换,需要对 List 集合进⾏包装。
包装类

import java.util.List;
@Data
public class UserList {
    List<User> userList;
}

业务代码

  @RequestMapping("/list")
    public String ist(UserList userList){
        StringBuilder stringBuilder=new StringBuilder();
        for(User user:userList.getUserList()){
               stringBuilder.append(user);
        }
        return stringBuilder.toString();
    }

JSP

<html>
<head>
    <title>AddList</title>
</head>
<body>
<form action="/data/list" method="post">
    用户1编号<input type="text" name="userList[0].id"><br>
    用户1姓名<input type="text" name="userList[0].name"><br>
    用户1账户余额<input type="text" name="userList[0].account.money"><br>
    用户2编号<input type="text" name="userList[1].id"><br>
    用户2姓名<input type="text" name="userList[1].name"><br>
    用户3编号<input type="text" name="userList[2].id"><br>
    用户3姓名<input type="text" name="userList[2].name"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

处理 @ResponseBody 中⽂乱码,在 springmvc.xml 中配置消息转换器。

  <mvc:annotation-driven>
        <!-- 消息转换器 -->
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes" value="text/html;charset=UTF8"></property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

这里感觉是在表单中生成一个数组,然后转化为list,在转化为其包装类

JSON数据格式

导入JQuery后要修改web.xml配置,避免js文件被DispatcherServlet拦截,配置代码如下

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

JSON类型和JavaBean的相互转化通过阿里的fastjoson实现,在pom.xml中引入。并在springmvc.xml中引入
JSP
ajax方法里的几个参数:url,type,data,contentType,dataType,success

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>AddJSON</title>
    <script type="text/javascript" src="js/jquery-3.6.0.min.js"></script>
    <script type="text/javascript">
        $(function () {
            var user = {
                "id": 1,
                "name": "川普"/*  前面的属性也要用""包一下 */
            };
                     $.ajax({
                    url: "/data/json",
                    data: JSON.stringify(user),
                    type: "POST",
                    contentType: "application/json;charset=UTF-8",/*中间是;*/
                    dataType: "JSON",
                    success: function (data) {
                        alert(data.id + "====>"
                            +data.name);
                    }

            })
        })

    </script>
</head>
<body>
    1111
</body>
</html>

业务代码:
若接受参数为json类型,前面要加requestBody注解

@RequestMapping("/json")
    public User json(@RequestBody User user){//此处重点注意,传入的是json对象,要加注解
        System.out.println(user);
        user.setId(12138);
        user.setName("特朗普");
        return user;
    }

SpringMVC模型数据解析

绑定到Request中

再一次服务器请求范围内有效,重定向第一次可以获取到

模型数据的绑定是由 ViewResolver 来完成的,实际开发中,我们需要先添加模型数据,再交给ViewResolver 来绑定。
Spring MVC 提供了以下⼏种⽅式添加模型数据:

  1. Map
  2. Model
  3. ModelAndView
  4. @SessionAttribute
  5. @ModelAttribute

map

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>

业务代码

@Controller
@RequestMapping("/view")
public class ViewHandler {
@RequestMapping("/map")
    public String map(Map<String, User> map){
        User user = new User();
        user.setId(1);
        user.setName("Tomasd");
        map.put("user",user);
        return "view";
    }
}

ModelAndView

ModelAndView有很多种构造方法

    @RequestMapping("modelAndView")
    public ModelAndView modelAndView(){
        User user = new User();
        user.setId(1);
        user.setName("害呀");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject(user);
        modelAndView.setViewName("view");
        return modelAndView;
    }

HttpServletRequest


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

ModelAttritube

该注释修饰的方法用来专门返回要填充到模型数据的对象
业务方法执行前都会先执行它,即向Request中添加对象
代码示例:

 @ModelAttribute
    public User getUser(){
        User user = new User();
        user.setId(1456);
        user.setName("张三");
        return user;
    }

    @RequestMapping("/modelAttribute")
    public String modelAttribute() {
        return "view";
    }

将模型数据绑定到Session中

  1. 使用原生的ServletAPI
  2. 使用@SessionAttribute注解,该注解定义在类

将模型数据绑定到Application中

@RequestMapping("/application")
public String application(HttpServletRequest request){
 ServletContext application = request.getServletContext();
 User user = new User();
 user.setId(1L);
 user.setName("张三");
 application.setAttribute("user",user);
 return "view";
}

**注意:**不能在业务方法的形参中直接添加ServletContext,因为其会默认调用无参构造方法,而ServletContext没有无参构造方法,所以会报错

自定义数据转换器

字符串和转换成日期类型

  1. 创建实现Convert接口的类型转换器
public class DataConverter implements Converter<String, Date> {
    private String pattern;  //表示格式:类似 YYYY-MM-DD
    public DataConverter(String pattern){
        this.pattern = pattern;
    }

    @Override
    public Date convert(String s) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern);
        Date date =null;
        try {
             date=simpleDateFormat.parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

  1. 配置springmvc.xml文件
<!--  配置自定义转换器  -->
    <bean id="conversionService"
          class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.wjh.converter.DataConverter">
                    <constructor-arg type="java.lang.String" value="yyyy-MM-dd">
                    </constructor-arg>
                </bean>
            </list>
        </property>
    </bean>
<!--上面自定义转换器配置完成后要再下面加上conversion-service-->
    <mvc:annotation-driven conversion-service="conversionService">

  1. 前端JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/convert/date" method="post">
    请输入日期:<input type="text" name="date">(yyyy-MM-dd)<br>
    <input type="submit" value="提交">
</form>
</body>
</html>
  1. 后端业务代码
RestController
@RequestMapping("/convert")
public class ConvertHandler {
    @RequestMapping("/date")
    public String date(Date date){
        return date.toString();
    }
}

string转换成Student类型

大体步骤和上述一样

  1. 创建转换器
public class StudentConvert implements Converter<String, Student> {
    @Override
    public Student convert(String s) {
            String[] args = s.split("-");
         Student student = new Student();
         student.setId(Integer.parseInt(args[0]));//此处用到包装类字符串转换为相应类型的方法
        student.setName(args[1]);
        student.setAge(Integer.parseInt(args[0]));
        return student;
    }
}
  1. 配置文件
    再list中加配一个bean
 <bean class="com.wjh.converter.StudentConvert"></bean>
  1. JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/convert/student" method="post">
    <input type="text" name="student">(id-name-age)<br>
    <input type="submit" value="提交">

</form>
</body>
</html>

  1. Handler业务方法
 @RequestMapping("/student")
    public String student(Student student){
        return student.toString();
    }

SpringMVC Rest

REST 具体操作就是 HTTP 协议中四个表示操作⽅式的动词分别对应 CRUD 基本操作。
GET ⽤来表示获取资源。
POST ⽤来表示新建资源。
PUT ⽤来表示修改资源。
DELETE ⽤来表示删除资源。

@GetMapping("/findById/{id}")
 public Student findById(@PathVariable("id") long id){
 return studentRepository.findById(id);
 }

Spring MVC⽂件上传下载

底层是使⽤ Apache fileupload 组件完成上传,Spring MVC 对这种⽅式进⾏了封装。

单文件上传

演示效果:
image.png
配置省略,需要时可现查
展示后台代码

@RequestMapping("/upload")
    public String upload(MultipartFile img, HttpServletRequest httpServletRequest){
            if(img.getSize()>0){
                String path = httpServletRequest.getServletContext().getRealPath("files");
                String name = img.getOriginalFilename();
                File file =new File(path,name);
                try {
                    img.transferTo(file);
                    //保存上传后的文件路径
                    httpServletRequest.setAttribute("path","/files/"+name);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return "fileUpload";
    }
}

上传多张图片

image.png
JSP
**此处犯的错误:**items中没有加${};

<%@ 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/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="imgs"><br>
    <input type="file" name="imgs"><br>
    <input type="file" name="imgs"><br>
    <input type="submit" value="提交">
</form>
<c:forEach items="${filess}" var="file">
    <img src="${file}" width="500px">
</c:forEach>
</body>
</html>

业务代码

@RequestMapping("/file")
@Controller
public class FileHandler {
    //多文件上传
@RequestMapping("/upload")
    public String upload(MultipartFile[] imgs, HttpServletRequest httpServletRequest){
            List<String> files =new ArrayList<String>();
            for(MultipartFile img:imgs) {
                if (img.getSize() > 0) {
                    String path = httpServletRequest.getServletContext().getRealPath("files");
                    String name = img.getOriginalFilename();
                    File file = new File(path, name);
                    try {
                        img.transferTo(file);
                        //保存上传后的文件路径
                        files.add("/files/"+name);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            httpServletRequest.setAttribute("filess",files);
            return "fileUpload";
    }
}

文件下载

image.png
因为有此段代码,在前端页面的href属性后面不能跟.jpg,要在业务代码handler中执行name+=".jpg"

JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<a href="/file/fileDownload/1">图一</a>
</body>
</html>

后端业务代码

//文件下载
    //可以不返回
    @RequestMapping("/fileDownload/{name}")
    public void download(@PathVariable  String name, HttpServletRequest request, HttpServletResponse response){
        if(name!=null){
            name+=".jpg";
            String path = request.getServletContext().getRealPath("files");
            File file = new File(path,name);
            if(file.exists()){
                response.setContentType("application/forc-download");
                response.setHeader("Content-Disposition","attachment;filename="+name);
                OutputStream outputStream=null;
                try {
                    outputStream = response.getOutputStream();
                    outputStream.write(FileUtils.readFileToByteArray(file));
                    outputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    if(outputStream!= null){
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

    }

SpringMVC表单标签

需要注意的是 path 可以直接绑定模型数据的属性值,items 则需要通过 EL 表达式的形式从域对象中获取数据,不能直接写属性名。
Handler代码:

@Controller
@RequestMapping("/label")
public class labelHandler {
@GetMapping("/get")
    public ModelAndView get(){
        ModelAndView modelAndView = new ModelAndView("label");
        Student student = new Student(212112,"武帝",21);
        modelAndView.addObject(student);
        return modelAndView;
    }
    @GetMapping("/radio")
    public ModelAndView radio(){
        ModelAndView modelAndView = new ModelAndView("radio");
        Student student = new Student();
        Map<Integer,String> grade= new HashMap<>();
        grade.put(1,"一年级");
        grade.put(2,"二年级");
        grade.put(3,"三年级");
        grade.put(4,"四年级");
        grade.put(5,"五年级");
        student.setGrade(grade);
        //RadioBox只能选一个
        student.setSelectGrade(4);
        modelAndView.addObject(student);
        return modelAndView;
    }
}

JSP
input标签:

<%@ 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="student">
    学生id:<form:input path="id"></form:input><br>
    学生姓名:<form:input path="name"></form:input><br>
    学生年龄:<form:input path="age"></form:input><br>
    <input type="submit" value="提交">
</form:form>
</body>
</html>

RadioButtons标签:

<%--此处切记要加属性mab--%>
<form:form modelAttribute="student">
    学生年级:<form:radiobuttons items="${student.grade}" path="selectGrade"></form:radiobuttons>
</form:form>

其余向checkboxes,select,option,textarea与前面演示的例子大同小异,options与select绑定使用

<form:select path="selectCity">
 <form:options items="${student.cityMap}"></form:options>
 </form:select><br/>

另外还有一个errors标签,接下来与SpringMVC 数据校验一起说

SpringMVC 数据校验

SpringMVC提供两种数据校验方式 其中一种为validator,他需要用户自定义验证Validator验证器并定义没一条数据的验证规则;另一种为Annotation JSR - 303,通过注解在实体类的属性前自定义验证规则。

基于Validator接口

自定义Validator

public class AccoutValidator  implements Validator {
    @Override
    public boolean supports(Class<?> aClass) {
        return Account.class.equals(aClass);
    }

    @Override
    public void validate(Object o, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors,"name",null,"姓名不能为空");
        ValidationUtils.rejectIfEmpty(errors,"password",null,"密码不能为空");
    }
}

后台业务方法Handler

@Controller
@RequestMapping("/validator")
public class ValidatorHandler {
    @GetMapping("/get")
    public String get(Model model){
        model.addAttribute("account",new Account());
        return "validator";

    }
    @PostMapping("/post")
    public String post(@Validated  Account account, BindingResult bindingResult){ //bug:这里一开始漏加注解了,导致报错
        if(bindingResult.hasErrors()){
            return "validator";
        }
        return "index";

    }
}

前端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/post" method="post">
    姓名:<form:input path="name"></form:input><form:errors path="name"></form:errors><br>
    密码:<form:input path="password"></form:input><form:errors path="password"></form:errors><br>
    <input type="submit" value="登录">
</form:form>
</body>
</html>

配置Springmvc.xml

    <bean id="accountValidator" class="com.wjh.validator.AccoutValidator"></bean>

    <mvc:annotation-driven validator="accountValidator"></mvc:annotation-driven>

Annotation JSR - 303

相关注解:
校验规则详解:
@Null 被注解的元素必须为null
@NotNull 被注解的元素不能为null
@Min(value) 被注解的元素必须是⼀个数字,其值必须⼤于等于指定的最⼩值
@Max(value) 被注解的元素必须是⼀个数字,其值必须⼩于于等于指定的最⼤值
@Email 被注解的元素必须是电⼦邮箱地址
@Pattern 被注解的元素必须符合对应的正则表达式
@Length 被注解的元素的⼤⼩必须在指定的范围内
@NotEmpty 被注解的字符串的值必须⾮空

Null 和 Empty 是不同的结果,String str = null,str 是 null,String str = "",str 不是 null,其值为
空。
在pom.xml中导入相关依赖

<!-- JSR-303 -->
<dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-validator</artifactId>
 <version>5.3.6.Final</version>
</dependency>
<dependency>
 <groupId>javax.validation</groupId>
 <artifactId>validation-api</artifactId>
 <version>2.0.1.Final</version>
</dependency>
<dependency>
 <groupId>org.jboss.logging</groupId>
 <artifactId>jboss-logging</artifactId>
 <version>3.3.2.Final</version>
</dependency>

直接在属性前面加注释

@Data//这里也要记得加data注解
public class Account {
    int money;
    @NotEmpty(message = "用户名不能为空")
    private String name;
    @Size(min = 6,max = 12,message = "输入密码不符合格式")
    private String password;
}

Q.E.D.