SpringMVC学习笔记

概述

  • 实现MVC设计模式的框架
  • Spring框架的分支,以Spring IoC容器为基础,用容器特性来简化配置
  • 为Spring的子模块,不用和Spring整合,直接搞就行

MVC

  • Model:数据库存取,获取模型数据
  • Controller:调用业务模型
  • View:展示模型,人机交互

总结起来就是:Controller接收客户端请求,调用Model生成业务数据,传递给View

核心组件

  • DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性【总指挥官】

  • Handler:处理器,完成具体的业务逻辑,相当于Servlet、Action…

  • HandlerMapping:DispatcherServlet 接收到请求后,通过HandlerMapping映射到不同的Handler

  • HandlerInterceptor:处理器拦截器,是个接口,做拦截处理

  • HandlerExecutionChain:处理器执行链,包括两部分:Handler 和 HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)

  • HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到JavaBean等,这些操作都是由HandlerAdapter来完成的,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet通过HandlerAdapter执行不同的Handler

  • ModelAndView:装载模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet

  • ViewResolver:视图解析器,DispatcherServlet通过它将逻辑视图解析为物理视图,最终将渲染结果相应给客户端

实际上我们开发者只要集中精力处理HandlerView就可以了

工作流程

  • 客户端请求被DispatcherServlet接收

  • 根据HandlerMapping映射到Handler

  • 生成Handler 和 HandlerInterceptor

  • Handler 和 HandlerInterceptor 以 HandlerExecutionChain的形式一并返回给DispatcherServlet

  • DispatcherServlet 通过HandlerAdapter 调用 Handler 的方法完成业务逻辑处理

  • Handler 返回一个 ModelAndView 给 DispatcherServlet

  • DispatcherServlet 将获取的 ModelAndView 传给 ViewResolver 做视图解析

  • ViewResolver 返回一个View 给 DispatcherServlet

  • DispatcherServlet 根据 View 进行视图渲染(将Model模型数据填充到视图View中)

  • DispatcherServlet 将渲染的结果响应给客户端

流程复杂,但是开发简单,因为大部分不需要开发者创建、管理,只需要通过配置文件的方式完成配置即可

特点

  • 清晰角色划分
  • 灵活的配置功能
  • 提供了大量的控制器接口和实现类
  • 分离View层的实现
  • 国际化支持
  • 面向接口编程【策略模式】

启动项目

  • 创建Maven工程,勾中archetype,选择archetype-webapp【看到BUILD SUCCESS才算成功】

    • 创建的慢的话,把archetype-catalog.xml弄在本地
    • 在conf/setting.xml里面配置一下景象
  • 在main下创建java文件夹,选择Sources Root,放Java代码

  • 在main下创建resources文件夹,选择Resources Root,放配置文件

  • 配置pom.xml

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.0.11.RELEASE</version>
</dependency>
  • 在web.xml中配置DispatcherServlet
<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <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>
  
</web-app>
  • 在resources下新建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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"
       default-autowire="byName">

    <!-- 自动扫描 -->
    <context:component-scan base-package="com.microsoft"></context:component-scan>

    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>
  • 创建Handler
package com.microsoft.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/hello")	//访问的时候要"/hello/index"
public class HelloHandler {

    @RequestMapping("/index")
    public String index(){
        System.out.println("执行了index...");
        return "index";
    }
}
  • 配置Tomcat
    • 选择好Tomcat
    • Deployment下要选择+,勾选springmvc:war

注解

  • @RequestMapping:将URL请求与业务方法进行映射,在Handler的类定义处(前),方法定义处都可以添加(后),类定义处添加,相当于多一层访问路径

    • value【默认值】:指定URL请求的实际地址
    • method:指定请求的method类型(指定后只能接收这种请求),GET POST PUT DELETE
    • params:制定请求中必须包含某些参数(可以是int之类的基本数据类型[HandlerAdapter完成数据类型转换],但是参数名要一致,实在不一样在传入参数前用@RequestParam),否则无法调用该方法 params = {"name","id=1"}
    package com.microsoft.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    @Controller
    @RequestMapping("/hello")
    public class HelloHandler {
    
        @RequestMapping(value = "/index",method = RequestMethod.GET,params = {"id"})
        public String index(int id){
            System.out.println(id);
            System.out.println("执行了index...");
            return "index";
        }
    }
    
  • @Contrller:在类定义处添加,将该类交给IoC容器管理,(结合xml中的自动扫描配合使用)同时使其成为控制器,可以接受客户端请求

  • @CookieValue:获取cookie的值(用的不多)

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

过滤器

  • 解决中文乱码(不用自己写filter,在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>

数据绑定

  • 定义:在后端的业务方法中直接获取客户端HTTP中的请求参数,将请求参数映射到业务方法的形参中

  • 使用JavaBean绑定参数

  • 根据请求参数名和JavaBean属性名进行自动匹配,自动为对象填充属性值,同时支持级联属性(对象套娃)

  • User实体类

package com.microsoft.entity;

import lombok.Data;

@Data
public class User {
    private long id;
    private String name;
    private Address address;
}
  • Address实体类
package com.microsoft.entity;

import lombok.Data;

@Data
public class Address {
    private String name;
}
  • register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>注册</title>
</head>
<body>
    <form action="/hello/save" method="post">
        用户id:<input type="text" name="id"/><br>
        用户名:<input type="text" name="name"/><br>
        地址:<input type="text" name="address.name"/><br>
        <input type="submit" value="注册"/>
    </form>
</body>
</html>
  • JSP页面的转发和重定向
  • SpringMVC默认转发
@RequestMapping("/forward")
public String forward(){
    return "forward:/index.jsp";
    // default: return "index";
}
  • 重定向
@RequestMapping("/redirect")
public String redirect(){
    return "redirect:/index.jsp";
}
  • 基本数据类型
/*
    @ResponseBody不当业务逻辑处理,直接将返回值响应给客户端;如果不加,则是给DispatcherServlet
 */
@RequestMapping("/baseType")
@ResponseBody
public String baseType(int id){
    return id+"";
}

但存在一定问题,基本数据没有null,万一没传参就直接甩你500,所以我们可以用包装类

  • 包装类
@RequestMapping("/packageType")
@ResponseBody
public String packageType(Integer id){
    return id+"";
}

解决异常了,但还有个保险的做法,我可以加个默认值,还可以指定参数名称required是是否必须填)

@RequestMapping("/packageType")
@ResponseBody
public String packageType(@RequestParam(value = "num",required = false,defaultValue = "0") Integer id){
    return id+"";
}
  • 数组
@RequestMapping("/array")
@ResponseBody
public String array(String[] name){
    String str = Arrays.toString(name);
    return str;
}

不难发现,每一个方法都有个@ResponseBody的注解,怎么优化呢???

直接在类定义处写一个@RestController就行了

package com.microsoft.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

@RestController
@RequestMapping("/data")
public class DataBindHandler {

    /*
        不当业务逻辑处理,直接返回
     */
    @RequestMapping("/baseType")
    public String baseType(int id){
        return id+"";
    }

    @RequestMapping("/packageType")
    public String packageType(@RequestParam(value = "num",required = false,defaultValue = "0") Integer id){
        return id+"";
    }

    @RequestMapping("/array")
    public String array(String[] name){
        String str = Arrays.toString(name);
        return str;
    }
}

@RestController的优缺点鲜明!!!

优点:简单方便,如果只是单纯的数据返回显示

缺点:我们一般要返回的是视图,那就莫得法子了,我们还是得@Controller交给视图解析器

  • List

SpringMVC不支持List的直接转换,需要定义包装类来包装

import lombok.Data;

import java.util.List;

@Data
public class UserList {
    private List<User> users;
}

业务方法(StringBuffer资源优化)

@RequestMapping("/list")
public String list(UserList userList){
    StringBuffer str = new StringBuffer();
    for(User user : userList.getUsers()){
        str.append(user.toString());
    }
    return str.toString();
}

JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>添加用户集合</title>
</head>
<body>
    <form action="/data/list" method="post">
        用户1编号:<input type="text" name="users[0].id"/><br>
        用户1名称:<input type="text" name="users[0].name"/><br>
        用户2编号:<input type="text" name="users[1].id"/><br>
        用户2名称:<input type="text" name="users[1].name"/><br>
        用户3编号:<input type="text" name="users[2].id"/><br>
        用户3名称:<input type="text" name="users[2].name"/><br>
        <input type="submit" value="注册"/>
    </form>
</body>
</html>

在springmvc.xml中处理@ResponseBody乱码问题

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

自定义封装类

package com.microsoft.entity;

import lombok.Data;

import java.util.Map;

@Data
public class UserMap {
    private Map<String,User> users;
}

业务方法

@RequestMapping("/map")
public String map(UserMap userMap){
    StringBuffer str = new StringBuffer();
    for(String key : userMap.getUsers().keySet()){
        User user = userMap.getUsers().get(key);
        str.append(user);
    }
    return str.toString();
}

JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>添加用户map</title>
</head>
<body>
    <form action="/data/map" method="post">
        用户1编号:<input type="text" name="users['a'].id"/><br>
        用户1名称:<input type="text" name="users['a'].name"/><br>
        用户2编号:<input type="text" name="users['b'].id"/><br>
        用户2名称:<input type="text" name="users['b'].name"/><br>
        用户3编号:<input type="text" name="users['c'].id"/><br>
        用户3名称:<input type="text" name="users['c'].name"/><br>
        <input type="submit" value="注册"/>
    </form>
</body>
</html>
  • JSON(需要在webapp下创建js目录,并导入jQuery文件)

开始必然会404,是因为前置控制器DispatcherServlet把它拦截了,静态资源无法加载

解决方法:在web.xml中加入这串代码

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

JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script type="text/javascript" src="js/jquery.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>

</body>
</html>

业务方法

@RequestMapping("/json")
public User json(@RequestBody User user){
    System.out.println(user);
    user.setId(6);
    user.setName("张六");
    return user;
}

我们会发现对象数据无法封装传回来,直接甩了415???

这时候我们要用工具啦!!!!!!——fastjson,JSON和JavaBean的转换就靠它

pom.xml中添加依赖

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

配置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=UTF-8"></property>
        </bean>
        <!-- 配置fastjson -->
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
    </mvc:message-converters>
</mvc:annotation-driven>

模型数据解析

JSP 四大作用域对应的内置对象:pageContext、request、session、application

  • 定义:把模型数据绑定到域对象里,再从域对象传到jsp,并解析出来

  • 实现:添加模型数据后,交给ViewResolver来绑定

  • 方式

    • Map
    • Model
    • ModelAndView
    • @SessionAttribute
    • @ModelAttribute
  • Map

业务方法

@RequestMapping("/map")
public String map(Map<String,User> map){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    // 类似request的key,value
    map.put("user",user);
    return "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(和map及其相似,jsp内容同上)
@RequestMapping("/model")
public String model(Model model){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    model.addAttribute("user",user);
    return "view";
}
  • ModelAndView(前两者的结合,jsp同上)
// 方式一
@RequestMapping("/modelAndView")
public ModelAndView modelAndView(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("user",user);
    modelAndView.setViewName("view");
    return modelAndView;
}
// 方式二
@RequestMapping("/modelAndView2")
public ModelAndView modelAndView2(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("user",user);
    View view = new InternalResourceView("/view.jsp");
    modelAndView.setView(view);
    return modelAndView;
}
// 方式三
@RequestMapping("/modelAndView3")
public ModelAndView modelAndView3(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView("view");
    modelAndView.addObject("user",user);
    return modelAndView;
}
// 方式四
@RequestMapping("/modelAndView4")
public ModelAndView modelAndView4(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view);
    modelAndView.addObject("user",user);
    return modelAndView;
}
// 方式五
@RequestMapping("/modelAndView5")
public ModelAndView modelAndView5(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    Map<String,User> map = new HashMap<>();
    map.put("user",user);
    ModelAndView modelAndView = new ModelAndView("view",map);
    return modelAndView;
}
// 方式六
@RequestMapping("/modelAndView6")
public ModelAndView modelAndView6(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    Map<String,User> map = new HashMap<>();
    map.put("user",user);
    View view = new InternalResourceView("/view.jsp");
    ModelAndView modelAndView = new ModelAndView(view,map);
    return modelAndView;
}
// 方式七
@RequestMapping("/modelAndView7")
public ModelAndView modelAndView7(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    ModelAndView modelAndView = new ModelAndView("view","user",user);
    return modelAndView;
}

像上面类似的组合还有很多很多…可以自己排列组合一下

  • HttpServletRequest
@RequestMapping("/request")
public String request(HttpServletRequest request){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    request.setAttribute("user",user);
    return "view";
}
  • @ModelAttribute(定义一个方法,该方法专门用来返回要填充到模型数据中的对象)
// 先于业务方法执行,执行后就会向request中添加模型
@ModelAttribute
public User getUser(){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    return user;
}

// 业务方法中无需再处理模型数据,只需返回视图就行
@RequestMapping("/modelAttribute")
public String modelAttribute(){
    return "view";
}

当然啦,也可以结合map

@ModelAttribute
public void getUser(Map<String,User> map){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    map.put("user",user);
}

可以map,就说明可以model

@ModelAttribute
public void getUser(Model model){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    model.addAttribute("user",user);
}
  • 再看看session
@RequestMapping("/session")
public String session(HttpServletRequest request){
    HttpSession session = request.getSession();
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    session.setAttribute("user",user);
    return "view";
}

还能简化一下

@RequestMapping("/session2")
public String session2(HttpSession session){
    User user = new User();
    user.setId(3L);
    user.setName("张三");
    session.setAttribute("user",user);
    return "view";
}

当然啦,还能更简便!!!直接在类定义处淦@SessionAttributes(value = "user"),相当于把对象塞进session里面。只要我在业务方法里面对value的值(可能是map的key)加载到域对象,那么就会进到session。

除此之外,还可以加载类@SessionAttributes(types = User.class),但尽量不要去用这类全局的,毕竟用request用的多,session的注释会造成一种资源浪费,用上述代码块就行了

  • 当然啦,少不了最大的application
@RequestMapping("/application")
public String application(ServletContext application){
    User user = new User();
    user.setId(1L);
    user.setName("张三");
    application.setAttribute("user",user);
    return "view";
}

哎呀??500了??为什么??

因为HandlerAdapter对ServletContext的创建要无参构造,而这边不是,我们只能去间接创建

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

自定义数据转换器

自定义数据转换器呀,就是将HTTP请求中的参数,转化为业务方法中定义的形参,自定义表示开发者可以自主设计转换的方式(HandlerAdapter已经提供了通用的转换,String转int,String转double,表单数据的封装)但是在特殊的业务场景下,HandlerAdapter无法转换,需要开发者自定义转换器。

客户端输入String类型的数据"2020-04-06",自定义转换器将该数据转为Date类型的对象

  • 创建DateConverter转换器,实现Converter接口
package com.microsoft.converter;

import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateConverter implements Converter<String, Date> {

    private String pattern;

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

    /*
        String 转成 Date
     */
    @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;
    }
}
  • 在springmvc.xml配置转换器,并在mvc驱动中注册
<!-- 配置自定义转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.microsoft.converter.DateConverter">
                <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
            </bean>
        </list>
    </property>
</bean>

<mvc:annotation-driven conversion-service="conversionService">
    <!-- 消息转换器 -->
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
        </bean>
        <!-- 配置fastjson -->
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
    </mvc:message-converters>
</mvc:annotation-driven>
  • JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="/converter/date" method="post">
        Please input the date:<input type="text" name="date"/>(yyyy-MM-dd)<br>
        <input type="submit" value="submit"/>
    </form>
</body>
</html>
  • 业务方法
@RequestMapping("/date")
public String date(Date date){
    return date.toString();
}

来看另一个例子,客户端输入String类型的数据"1-张三-18",自定义转换器将该数据转为Student类型的对象

  • 首先写个转换器
package com.microsoft.converter;

import com.microsoft.entity.Student;
import org.springframework.core.convert.converter.Converter;

public class StudentConverter implements Converter<String, Student> {

    @Override
    public Student convert(String s) {
        String[] args = s.split("-");
        Student student = new Student();
        student.setId(Long.parseLong(args[0]));
        student.setName(args[1]);
        student.setAge(Integer.parseInt(args[2]));
        return student;
    }
}
  • springmvc.xml的配置
<!-- 配置自定义转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.microsoft.converter.DateConverter">
                <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
            </bean>
            <bean class="com.microsoft.converter.StudentConverter"></bean>
        </list>
    </property>
</bean>
  • JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="/converter/student" method="post">
        请输入学生信息:<br>
        <input type="text" name="student"/>(id-name-age)<br>
        <input type="submit" value="提交"/>
    </form>
</body>
</html>
  • 业务方法
@RequestMapping("/student")
public String student(Student student){
    return student.toString();
}

RESTful架构

  • REST:Representational State Transfer,资源表现层状态转换

  • 特点:结构清晰、标准规范、易于理解、便于拓展

  • 资源(Resource)

    • 网络上的一个实体/具体信息(一首歌、一张图、一个视频等等)
    • 可以用一个URI(统一资源定位符)指向它,每个资源都有一个特定的URI,获取即访问
  • URI比较

    • 传统类型:http://localhost:8080/hello/index?name=zhangsan&id=1
    • REST:http://localhost:8080/hello/index/zhangsan/1
  • 数据绑定

@RequestMapping("/rest/{name}/{id}")
public String rest(@PathVariable("name") String name, @PathVariable("id") int id){
    System.out.println(name);
    System.out.println(id);
    return "index";
}

通过 @PathVariable 完成请求参数与形参的映射

  • 表现层(REST:Representation)

资源具体呈现形式(比如HTML、JSON、txt等等)

  • 状态转换(State Transfer)

客户端如果希望操作服务器中的某个资源,就需要通过某种方式让服务端发生状态转换,而这种状态转换是建立在表现层之上的,所以叫“表现层状态转换”

  • 特点

    • URL更加简洁
    • 有利于不同系统间的资源共享,只需要遵守一定的规范,不需要进行其他配置即可实现资源共享
  • 使用(类似CRUD)

    • GET 获取资源
    • POST 新建资源
    • PUT 修改资源
    • DELETE 删除资源

看个例子

  • Student实体类(保证自动扫描扫得到)
package com.microsoft.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private long id;
    private String name;
    private int age;
}
  • CRUD接口
package com.microsoft.repository;

import com.microsoft.entity.Student;

import java.util.Collection;

public interface StudentRepository {
    public Collection<Student> findAll();
    public Student findById(long id);
    public void saveOrUpdate(Student student);
    public void deleteById(long id);

}
  • CRUD实现类
package com.microsoft.repository.impl;

import com.microsoft.entity.Student;
import com.microsoft.repository.StudentRepository;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

@Repository
public class StudentRepositoryImpl implements StudentRepository {

    private static Map<Long,Student> studentMap;

    static {
        studentMap = new HashMap<>();
        studentMap.put(1L,new Student(1L,"王帅真",20));
        studentMap.put(2L,new Student(2L,"王帅假",21));
        studentMap.put(3L,new Student(3L,"王丑真",22));
    }

    @Override
    public Collection<Student> findAll() {
        return studentMap.values();
    }

    @Override
    public Student findById(long id) {
        return studentMap.get(id);
    }

    @Override
    public void saveOrUpdate(Student student) {
        studentMap.put(student.getId(),student);
    }

    @Override
    public void deleteById(long id) {
        studentMap.remove(id);
    }
}
  • 再写业务方法
package com.microsoft.controller;

import com.microsoft.entity.Student;
import com.microsoft.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;

@RestController
@RequestMapping("/rest")
public class RESTHandler {

    @Autowired
    private StudentRepository studentRepository;

    @RequestMapping("/findAll")
    public Collection<Student> findAll(){
        return studentRepository.findAll();
    }

}

哈哈,跑一下

是不是对象的中文字符乱码了???

诶,我不是配置了消息转换器了吗???

@GetMapping("/findAll")
public Collection<Student> findAll(HttpServletResponse response){
    response.setContentType("text/json;charset=UTF-8");
    return studentRepository.findAll();
}

只好手动自己设置咯…记住REST要写的规范一点,查询就是GET

  • 补全CRUD(调试用postman)
package com.microsoft.controller;

import com.microsoft.entity.Student;
import com.microsoft.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.util.Collection;

@RestController
@RequestMapping("/rest")
public class RESTHandler {

    @Autowired
    private StudentRepository studentRepository;

    //@RequestMapping(value = "/findAll",method = RequestMethod.GET)
    @GetMapping("/findAll")
    public Collection<Student> findAll(HttpServletResponse response){
        response.setContentType("text/json;charset=UTF-8");
        return studentRepository.findAll();
    }

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

    @PostMapping("/save")
    public void save(@RequestBody Student student){
        studentRepository.saveOrUpdate(student);
    }

    @PutMapping("/update")
    public void update(@RequestBody Student student){
        studentRepository.saveOrUpdate(student);
    }

    @DeleteMapping("/deleteById/{id}")
    public void deleteById(@PathVariable("id") long id){
        studentRepository.deleteById(id);
    }


}

文件上传/下载

单文件上传

  • 底层是用Apache的fileupdate组件完成上传,springMVC对这种方式进行了封装
  • 添加依赖
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.3</version>
</dependency>
  • 配置springmvc.xml
<!-- 配置上传组件 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
  • JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>上传</title>
</head>
<body>
    <form action="/file/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="img"/>
        <input type="submit" value="上传"/>
    </form>
</body>
</html>
  • 在tomcat目录下的webapps\ROOT内创建一个file文件夹(在tomcat运行时创建,每次重启文件夹就没了)

  • 业务方法

package com.microsoft.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

@Controller
@RequestMapping("/file")
public class FileHandler {

    @PostMapping("/upload")
    public String upload(MultipartFile img, HttpServletRequest request){
        if(img.getSize()>0){
            // 获取保存文件的绝对路径
            String path = request.getServletContext().getRealPath("file");
            // 获取上传文件名
            String name = img.getOriginalFilename();
            File file = new File(path,name);
            try {
                // IO流被封装了,其实就是把img的内容转给file
                img.transferTo(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "upload";
    }
}

我们如果要改进一下,上传后显示呢???

  • 业务方法
@PostMapping("/upload")
public String upload(MultipartFile img, HttpServletRequest request){
    if(img.getSize()>0){
        // 获取保存文件的路径
        String path = request.getServletContext().getRealPath("file");
        // 获取上传文件名
        String name = img.getOriginalFilename();
        File file = new File(path,name);
        try {
            // IO流被封装了
            img.transferTo(file);
            // 保存上传之后的文件路径
            request.setAttribute("path","/file/"+name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return "upload";
}
  • JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>上传</title>
</head>
<body>
    <form action="/file/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="img"/>
        <input type="submit" value="上传"/>
    </form>
    <img src="${path}">
</body>
</html>

哎呀,图片404,怎么回事???

是因为tomcat这边默认是动态资源,而我们访问jpg是静态资源,那怎么做呢???

记得jQuery我怎么怎么操作的吗??对!!!就是web.xml中配置default

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

是不是就行了??

多文件上传

  • 因为要在JSP中遍历,所以直接用JSTL
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.1.2</version>
</dependency>

<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>
  • 业务方法
@PostMapping("/uploads")
public String uploads(MultipartFile[] imgs, HttpServletRequest request){
    List<String> files = new ArrayList<>();
    for (MultipartFile img : imgs){
        if(img.getSize()>0){
            // 获取保存文件的路径
            String path = request.getServletContext().getRealPath("file");
            // 获取上传文件名
            String name = img.getOriginalFilename();
            File file = new File(path,name);
            try {
                // IO流被封装了
                img.transferTo(file);
                // 保存上传之后的文件路径
                files.add("/file/"+name);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    request.setAttribute("files",files);
    return "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>
</head>
<body>
    <form action="/file/uploads" method="post" enctype="multipart/form-data">
        file1:<input type="file" name="imgs"/><br>
        file2:<input type="file" name="imgs"/><br>
        file3:<input type="file" name="imgs"/><br>
        <input type="submit" value="上传"/><br>
    </form>
    <c:forEach items="${files}" var="file">
        <img src="${file}" width="300px">
    </c:forEach>

</body>
</html>

下载

  • JSP
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>下载</title>
</head>
<body>
    <a href="/file/download/1">1.jpg</a>
    <a href="/file/download/2">2.jpg</a>
</body>
</html>
  • 业务方法
@GetMapping("/download/{name}")
public void download(@PathVariable("name") String name, HttpServletRequest request, HttpServletResponse response){
    if (name != null){
        name += ".jpg";
        String path = request.getServletContext().getRealPath("file");
        File file = new File(path,name);
        OutputStream outputStream = null;
        if(file.exists()){
            response.setContentType("application/forc-download");
            response.setHeader("Content-Disposition","attachment;filename="+name);
            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();
                    }
                }
            }
        }
    }
}

表单标签库

  • 对表单的简化

  • 业务方法

@GetMapping("/get")
public ModelAndView get(){
    ModelAndView modelAndView = new ModelAndView("tag");
    Student student = new Student(1L,"张三",18);
    modelAndView.addObject("student",student);
    return modelAndView;
}
  • 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>
    <h1>学生信息</h1>
    <!-- modelAttribute和模型数据的key对应 -->
    <form:form modelAttribute="student">
        学生ID:<form:input path="id"/><br>
        学生姓名:<form:input path="name"/><br>
        学生年龄:<form:input path="age"/><br>
        <input type="submit" value="提交"/>
    </form:form>
</body>
</html>

常用的表单标签

  • form
<form:form modelAttribute="student">

渲染的是HTML中的<form>,通过modelAttribute绑定模型数据

  • input
<form:input path="id"/>

渲染的是HTML中的<input type="text">,input绑定模型数据中的具体属性,通过path来绑定(支持级联)

<form:input path="address.name"/>
  • password
<form:password path="password"/>

渲染的是HTML中的<input type="password">

  • checkbox复选框(选中即默认勾选)
<form:checkbox path="hobby" value="读书"/>

渲染的是HTML中的<input type="checkbox">,可以绑定boolean、数组和集合

若绑定boolean,true为选中,false为不选中

若绑定数组/集合,数组/集合中的元素等于checkbox的value值为选中,否则不选中【取交集】

但是前端代码较多,所以有了checkboxes

  • checkboxes【多选】
<form:checkboxes items="${student.hobby}" path="selectHobby"/>

渲染的是HTML中的一组<input type="checkbox">,是对<form:checkbox/>的一种简化,需要结合items和path属性来使用,items为全部可选集合/数组【可选的全部选项】,path为默认选中集合/数组【打勾的】

  • rabiobutton
<form:rabiobutton path="radioId" value=0/>

渲染的是HTML中的一个<input type="radio">,绑定的数据与value相等就选中,否则不选

  • rabiobuttons【单选】
<form:rabiobuttons items="${student.grade}" path="selectGrade"/>

渲染的是HTML中的一组<input type="radio">,是对<form:radio/>的一种简化,需要结合items和path属性来使用,items为全部可选选项【可选的全部选项】,path为默认选中的选项属性【打勾的】

  • select 下拉框
<form:select items="${student.cities}" path="selectCity"/>

渲染的是HTML中的一个<select/>,需要结合items和path属性来使用,items为全部可选选项【可选的全部选项】,path为默认选中的选项属性【选中的】,和rabiobuttons类似

  • options

form:select结合form:options的使用,select只要定义个path,不写items,在select加个子标签options

所在城市:<form:select path="selectCity"/>
	<form:options items="${student.cities}"></form:options>
</form:select>
  • option

也是和select搭配,value相等就选中

所在城市:<form:select path="selectCity"/>
	<form:option value="1">厦门</form:options>
    <form:option value="2">福州</form:options>
    <form:option value="3">泉州</form:options>
</form:select>
  • textarea【一个比较大的文本输入框】

渲染的是HTML中的一个<textarea/>,path绑定模型数据的属性值,作为文本输入域的默认值

  • errors

处理错误信息,一般用在数据校验,比如密码输入错误要告知用户,要在前台显示出来,该标签需要和springmvc的验证器结合起来使用

数据校验

两种方式:1.Validator接口 2.使用Annotation JSR - 303标准

  • 基于Validator接口的方式需要自定义Validator验证器,每一条数据的验证规则需要开发者手动完成
  • 使用Annotation JSR - 303标准不需要自定义,用注解的方式可以直接在实体类中添加每个属性的验证规则(推荐这种,更方便)

基于Validator接口

前端+后端双重验证,从后台进入login界面,需要先数据绑定(这样才能从)

  • 定义实体类
package com.microsoft.entity;

import lombok.Data;

@Data
public class Account {
    private String name;
    private String password;
}
  • 自定义验证器 AccountValidator,实现Validator接口
package com.microsoft.validator;

import com.microsoft.entity.Account;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

public class AccountValidator 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,"密码不能为空");
    }
}
  • 配置springmvc.xml
<!-- 基于Validator的配置 -->
<bean id="accountValidator" class="com.microsoft.validator.AccountValidator"></bean>
<mvc:annotation-driven validator="accountValidator"></mvc:annotation-driven>
  • 控制器
package com.microsoft.controller;

import com.microsoft.entity.Account;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/validator")
public class ValidatorHandler {

    // 创建空对象,传给前台,主要是要绑定一个模型数据
    @GetMapping("/login")
    public String login(Model model){
        model.addAttribute("account",new Account());
        return  "login";
    }

    @PostMapping("/login")
    public String login(@Validated Account account, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            return  "login";
        }
        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>
</head>
<body>
    <h1>用户信息</h1>
    <form:form modelAttribute="account" action="/validator/login" method="post">
        姓名:<form:input path="name"/><form:errors path="name"></form:errors><br>
        密码:<form:input path="password"/><form:errors path="password"></form:errors><br>
        <input type="submit" value="提交"/>
    </form:form>
</body>
</html>

使用Annotation JSR - 303标准

  • 不用谢验证器,注解就行

  • 添加依赖,我们这里使用Hibernate Validator

<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>
  • 实体类
package com.microsoft.entity;

import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.constraints.Size;

@Data
public class Person {
    @NotEmpty(message = "用户名不能为空")
    private String username;
    @Size(min = 6,max = 12,message = "密码是6-12位")
    private String password;
}
  • 业务方法
@GetMapping("/register2")
public String register(Model model){
    model.addAttribute("person",new Person());
    return "register2";
}

@PostMapping("/register2")
public String register(@Valid Person person, BindingResult bindingResult){
    if(bindingResult.hasErrors()){
        return  "register2";
    }
    return "index";
}
  • springmvc.xml的配置(让注解生效即可,啥也不用写,但需要单独再写一次
<mvc:annotation-driven></mvc:annotation-driven>
  • 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>
</head>
<body>
    <h1>用户信息</h1>
    <form:form modelAttribute="person" action="/validator/register2" method="post">
        姓名:<form:input path="username"/><form:errors path="username"/><br>
        密码:<form:password path="password"/><form:errors path="password"/><br>
        <input type="submit" value="提交"/>
    </form:form>
</body>
</html>

校验规则

  • @Null:被注解的元素必须为null

  • @NotNull:被注解的元素不能为null

  • @Min:被注解的元素必须是一个数字,且值必须大于等于指定的最小值

  • @Max:被注解的元素必须是一个数字,且值必须小于等于指定的最大值

  • @Email:被注解的元素必须是电子邮箱地址

  • @Pattern:被注解的元素必须符合对应的“正则表达式”

  • @Length:被注解的元素的大小必须在指定范围内

  • @NotEmpty:被注解的字符串的值必须非空

Null和Empty不是一个东西,String str = null,str是null,String str = “”,str不是null,但其值是空

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值