springmvc4 (1) 基础入门

247 阅读8分钟

1. 概念入门

概念: springmvc是spring框架的控制层技术,核心是前端控制器 DispatcherServlet,负责管理和调用其它组件以处理用户的http请求,降低组件间的耦合度:

  • 当浏览器请求符合前端控制器规则时,WEB服务器会将其转交给前端控制器。
  • 前端控制器调用处理器映射器 HandlerMapping
    • 根据请求URL找到某自定义Handler,将其和拦截器(若有)封装成执行链并返回。
  • 前端控制器调用处理器适配器 HandlerAdapter
    • 执行执行链中拦截器和Handler并返回一个ModelAndView对象。
  • 前端控制器调用视图解析器 ViewResolver
    • 将ModelAndView中的逻辑视图名拼接前后缀后解析为物理视图名,并返回View对象。
  • 前端控制器根据View组装HTML页面响应给浏览器,浏览器渲染页面:
    • 渲染:浏览器解析HTML,构建DOM树,整合CSS和JS,布局,绘制等过程。

z-image/一个核心三个组件.png

1.1 配通

流程: 添加 spring-webmvc 依赖:

  • 在web.xml中配置前端控制器类 o.s.w.s.DispatcherServlet
    • 使用 <init-param> 注入 contextConfigLocation=springmvc主配文件
      • 默认加载 /WEB-INF/配对名-servlet.xml
    • 使用 <url-pattern> 设置前端控制器拦截规则,可设置多个:
      • *.do/.action:仅拦截以.do或.action结尾的请求。
      • /:拦截除JSP外的一切请求,静态资源请求需在web.xml中手动放行。
      • /*:拦截包括JSP的一切请求,静态资源请求需在web.xml中手动放行,不建议。
  • 开发springmvc主配文件:
    • 使用 <mvc:annotation-driven /> 驱动管理处理器映射器类和处理器适配器类。
    • 使用 <context:component-scan /> 包扫描 @Component
    • 管理视图解析器类:o.s.w.s.v.InternalResourceViewResolver
      • 注入 prifix 以配置响应路径前缀,可选。
      • 注入 suffix 以配置响应路径后缀,可选。
  • 开发动作类:添加 @Controller
  • 开发动作方法:添加 @RequestMapping 设置方法路由,可省略两端 /.do 后缀:
    • 修饰符必须 public,方法名随意。
    • 若返回字符串,会自动拼接视图解析器前后缀并执行请求转发。
  • 测试:请求URL中不可省略 .do 后缀(若有)。

源码: /springmvc4/

  • res: pom.xml
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <!--spring-webmvc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.1.3.RELEASE</version>
    </dependency>

  • res: WEB-INF/web.xml

    <!--其他请求限定-->
   <!-- <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>-->

    <!--dispatcherServlet-->
    <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/springmvc.xml</param-value>
        </init-param>
           <!--      Tomcat版本支持-->
        <init-param>
            <param-name>readonly</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
                <!--        <url-pattern>*.action</url-pattern>-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
  • res: classpath:spring/springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
   <!--注解驱动:管理处理器映射器和处理器适配器-->
    <mvc:annotation-driven/>

  <!--管理视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/view/"/>
        <property name="suffix" value=".html"/>
    </bean>
    
  <!--包扫描-->
    <context:component-scan base-package="com.yap.controller"/>

</beans>
  • src: c.y.controller.StartController
package com.yap.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * @author yap
 */

@Controller
public class StartController {
     @RequestMapping("/api/start")
    public void start() {
        System.out.println("start()...");
    }
}

1.2 @RequestMapping

概念: 注解式处理器映射器会 @ResquestMapping 标记的方法进行映射和寻找:

  • value:设置1或N个方法路由,自动以类上的路由为前缀(若有):
    • ?:匹配一个字符,如 /?/one
    • *:匹配任意字符,如 /*/two
    • **:匹配多层路径,如 /**/three
  • method:限定1或N种请求方式,值为枚举类 RequestMethod
    • RequestMethod.GET:仅接收GET请求,其余请求报错,其余同理。
    • 特殊请求限定方式参考 z-res/特殊请求限定.md
  • params:限定1或N种请求参数,不满足直接报错:
    • age/!age:必须有/没有 name 属性,值无所谓。
    • age=18/age!=18:必须有 age=18 键值对,若有 age 则值必须不能为 18

源码: /springmvc4/

  • src: c.y.controller.RequestMappingController
package com.yap.controller;

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

/**
 * @author yap
 */
@RequestMapping("/api/request-mapping")
@Controller
public class RequestMappingController {

    @RequestMapping({"value-a", "value-b"})
    public String value() {
        System.out.println("value()...");
        return "success";
    }

    @RequestMapping("/**/user-*/?")
    public String valueMatch() {
        System.out.println("valueMatch()...");
        return "success";
    }

    @RequestMapping(value = "/method", method = RequestMethod.GET)
    public String method() {
        System.out.println("method()...");
        return "success";
    }

    @RequestMapping(value = "/params", params = {"!name", "age=18", "gender!=1"})
    public String params() {
        System.out.println("params()...");
        return "success";
    }
}

2. 接值

概念: 因默认值问题,不建议使用任何基本类型接值:

  • Cookie参数:对方法参数使用 @CookieValue 可以获取Cookie中指定key值信息。
  • 请求头参数:对方法参数使用 @RequestHeader 可以获取请求头指定key值信息。
  • REST参数:对方法参数使用 @PathVariable 可以获取方法路由中的指定占位符对应的信息:
    • 路由中需使用 {} 标记占位符,对应请求URL的REST参数,与 @PathVariable 指定key值同名。
    • REST参数和请求后缀如 .do 写法不兼容。
  • 键值对简单参数:对方法参数使用 @RequestParam 可以获取指定key的键值对请求参数:
    • 使用简单类型如 Integer/Double/Boolean/String 等接收请求参数在同名时可省略注解。
    • 使用布尔类型接收请求参数时可自动将 1/0 转型为 true/false
    • 使用数组类型如 Integer[] 可批量接收同名请求参数。
    • 使用实体类接收请求参数时,要求实体类属性和请求参数同名。
    • 使用VO中实体类可接收格式为 VO中实体类属性名.key 的请求参数。
    • 使用VO中List可接收格式为 VO中List属性[下标].key 的请求参数,注意GET请求不识别 []
  • POST请求中文乱码:在web.xml中配置编码过滤器 o.s.w.f.CharacterEncodingFilter
    • 使用 <init-param> 注入 encoding=utf-8

以上注解均可使用 requireddefaultValue 设置必填和默认值。

源码: /springmvc4/

  • src: `c.y.pojo
package com.yap.pojo;

import lombok.Data;

/**
 * @Author Yap
 */
@Data
public class User {
    private String name;
    private Integer age;
    private Boolean gender;
}
  • src: `c.y.vo
package com.yap.vo;

import com.yap.pojo.User;
import lombok.Data;

import java.util.List;

/**
 * @Author Yap
 */
@Data
public class UserVo {
    private User user;
    private Integer[] ids;
    private List<User> users;
}
  • src: c.y.controller.ParamController
package com.yap.controller;

import com.yap.pojo.User;
import com.yap.vo.UserVo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;

/**
 * @author yap
 */
@RequestMapping("/api/param")
@Controller
public class ParamController {

    @RequestMapping("cookie-value")
    public String cookieValue(
            @CookieValue(value = "name", required = false, defaultValue = "admin") String cookieName) {
        System.out.println("name: " + cookieName);
        return "success";
    }

    @RequestMapping("request-header")
    public String requestHeader(
            @RequestHeader(value = "host") String requestHeaderHost) {
        System.out.println("host: " + requestHeaderHost);
        return "success";
    }

    @RequestMapping("path-variable/{id}/{age}")
    public String pathVariable(
            @PathVariable("id") Integer restA,
            @PathVariable("age") Integer restB) {
        System.out.println("id: " + restA);
        System.out.println("age: " + restB);
        return "success";
    }

    @RequestMapping("request-param")
    public String requestParam(
            @RequestParam("username") String name,
            Integer age, Boolean gender, Integer[] ids,
            User userA, User userB,
            UserVo userVoA, UserVo userVoB) {
        System.out.println("name: " + name);
        System.out.println("age: " + age);
        System.out.println("gender: " + gender);
        System.out.println("ids: " + Arrays.toString(ids));
        System.out.println("userA: " + userA);
        System.out.println("userB: " + userB);
        System.out.println("userVoA: " + userVoA);
        System.out.println("userVoB: " + userVoB);
        return "success";
    }
}

3. 存值

概念: 动作方法形参中可直接使用原生请求和会话对象存值,除此外:

  • 请求域存值:动作方法形参中可直接使用 ModelAndView/Model/ModelMap/Map 实例:
    • ModelAndView 支持手动实例化,且可以获取 Model/ModelMap 实例。
    • ModelAndView 存值的生效前提是必须配合 ModelAndView 转页。
  • 会话域存值:在类上使用 @SessionAttributes 可将请求域中的指定属性上升到会话域:
    • value="name":将请求域中名为 name 的属性复制到会话域,支持数组。
    • types=String.class:将请求域中所有 String 类型的属性复制到会话域,支持数组。
  • 前置方法:@ModeAttribute 标记的方法会在本类所有业务方法前执行:
    • 前置方法可直接获取请求参数,返回值 void
    • 前置方法和业务方法处于同次请求中,且和所有业务方法共享请求域。

源码: /springmvc4/

  • src: c.j.controller.ScopeController
package com.yap.controller;

import com.yap.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * @author yap
 */
@SessionAttributes(value = {"name", "info"}, types = Integer.class)
@RequestMapping("/api/scope")
@Controller
public class ScopeController {

    @RequestMapping("request-scope")
    public ModelAndView requestScope(
            HttpServletRequest req, ModelAndView mv, Model model,
            ModelMap modelMap, Map<String, Object> map) {

        req.setAttribute("key-request", "value-request");
        mv.addObject("key-mv", "value-mv");
        model.addAttribute("key-model", "value-model");
        modelMap.addAttribute("key-model-map", "value-model-map");
        map.put("key-map", "value-map");
        // forward:避免拼接前后缀
        mv.setViewName("forward:/view/request-value.jsp");
        return mv;
    }

    @RequestMapping("session-scope")
    public ModelAndView sessionScope(HttpSession session, ModelAndView mv) {
        session.setAttribute("id", "9527");
        mv.addObject("name", "admin");
        mv.addObject("gender", 1);
        mv.addObject("age", 18);
        mv.addObject("info", "管理员");
        mv.setViewName("forward:/view/session-value.jsp");
        return mv;
    }

    @RequestMapping("model-attribute")
    public void modelAttribute(User user, Map<String, Object> map) {
        System.out.println("modelAttribute(): " + user);
        System.out.println("map: " + map.get("key"));
    }

    @ModelAttribute
    public void before(User user, Map<String, Object> map) {
        System.out.println("before(): " + user);
        map.put("key", "before()");
    }

}
  • web: view/request-value.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<section>
    <h1>request域测试结果</h1>
    <p>request: <%=request.getAttribute("key-request")%>
    <p>request: <%=request.getAttribute("key-mv")%>
    <p>request: <%=request.getAttribute("key-model")%>
    <p>request: <%=request.getAttribute("key-model-map")%>
    <p>request: <%=request.getAttribute("key-map")%>
</section>
</body>
</html>

  • web: view/session-value.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<section>
    <h1>session域测试结果</h1>
    <p>request: <%=request.getAttribute("id")%>
    <p>request: <%=request.getAttribute("name")%>
    <p>request: <%=request.getAttribute("gender")%>
    <p>request: <%=request.getAttribute("age")%>
    <p>request: <%=request.getAttribute("info")%>
    <p>session: <%=session.getAttribute("id")%>
    <p>session: <%=session.getAttribute("name")%>
    <p>session: <%=session.getAttribute("gender")%>
    <p>session: <%=session.getAttribute("age")%>
    <p>session: <%=session.getAttribute("info")%>
</section>
</body>
</html>

4. 转页

概念: 对视图解析器的 <bean> 注入 prefix/suffix 以配置某些响应路径的前/后缀:

  • 返回 void:需配合原生请求/响应进行请求转发,重定向或IO回写,不拼接前后缀。
  • 返回 String/ModelAndView:直接return请求转发路径或 ModelAndView 对象,拼接前后缀:
    • forward::请求转发到页面或动作类,不拼接前后缀,支持查询串。
    • redirect::重定向到页面或动作类,不拼接前后缀,不支持查询串。
  • 返回JSON:添加 jackson-core/jackson-databind/jackson-annotations 依赖:
    • 在主配文件中使用 <mvc:annotation-driven /> 驱动jackson框架。
    • 对动作方法添加 @ResponseBody 后返回的实体类和集合均可自动转为JSON格式。
  • 实体类属性注解:生效于将数据转为JSON字符串的过程中:
    • @JsonIgnore 标记的属性不参与装换过程。
    • @JsonFormat 标记的属性可设置自定义格式,常用于日期数据。
    • @JsonInclude(JsonInclude.Include.NON_NULL) 标记的属性若为null则不参与序列化。
    • @JsonProperty 标记的属性可设置别名,以保护数据库字段隐私。
  • @ResponseBody 返回中文字符串乱码:
    • 局部设置:设置 @RequestMappingproduces="text/html;charset=UTF-8
    • 全局设置:设置 <mvc:annotation-driven /> 添加子标签 <mvc:message-converters >
      • 添加内部 <bean class="o.s.h.c.StringHttpMessageConverter">
      • 内部 <bean> 注入 supportedMediaTypes=text/html;charset=UTF-8

源码: /springmvc4/

  • res: pom.xml
    <!--jackson-core-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.7</version>
    </dependency>

    <!--jackson-annotations-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.7</version>
    </dependency>

    <!--jackson-databind-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.7</version>
    </dependency>
  • res: spring/springmvc.xml
    <!--配置@ResponseBody直接返回字符串的编码-->
    <!--
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    -->
  • src: c.y.controller.ForwardController
package com.yap.controller;

import com.yap.pojo.Student;
import com.yap.util.JsonData;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author yap
 */
@RequestMapping("/api/forward")
@Controller
public class ForwardController {

    @RequestMapping("request")
    public void requestForward(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("requestForward()...");
        req.getRequestDispatcher("/api/forward/redirect-to-response-writer").forward(req, resp);
    }

    @RequestMapping("redirect-to-response-writer")
    public void responseRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("responseRedirect()...");
        resp.sendRedirect(req.getContextPath() + "/api/forward/response-writer");
    }

    @RequestMapping("response-writer")
    public void responseWriter(HttpServletResponse resp) throws IOException {
        System.out.println("responseWriter()...");
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().print("response-writer");
    }

    @RequestMapping("string")
    public String stringForward() {
        System.out.println("stringForward()...");
        return "forward:redirect-to-model-and-view";
    }

    @RequestMapping("redirect-to-model-and-view")
    public String stringRedirect() {
        System.out.println("stringRedirect()...");
        return "redirect:/api/forward/model-and-view";
    }

    @RequestMapping("model-and-view")
    public ModelAndView modelAndView(ModelAndView modelAndView) {
        System.out.println("modelAndView()...");
        modelAndView.setViewName("success");
        return modelAndView;
    }

    @ResponseBody
    @RequestMapping("response-body-json")
    public JsonData responseBodyJson() {
        System.out.println("responseBodyJson()...");
        List<Student> students = new ArrayList<>();

        Student zhaosi = new Student();
        zhaosi.setId(1);
        zhaosi.setName("赵四");
        zhaosi.setBirthday(new Date(1_111_111_111L));

        Student liuneng = new Student();
        liuneng.setId(2);
        liuneng.setBirthday(new Date(9_999_999_999L));

        students.add(zhaosi);
        students.add(liuneng);
        return new JsonData(students);
    }

    @ResponseBody
    @RequestMapping(value = "response-body-string", produces = "text/html;charset=utf-8")
    public String responseBodyString() {
        System.out.println("responseBodyString()...");
        return "中文";
    }

}
  • src: c.y.util.JsonData
package com.yap.util;

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

import java.io.Serializable;

/**
 * @author yap
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonData implements Serializable {

    private Object data;
    private Integer status;
    private String msg;

    public JsonData(Object data) {
        if (data != null) {
            status = 200;
            msg = "ok";
        } else {
            status = 500;
            msg = "err";
        }
        this.data = data;
    }
}
  • src: c.y.pojo.Student
package com.yap.pojo;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.util.Date;

/**
 * @author yap
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {

    @JsonIgnore
    private static final long serialVersionUID = 1L;

    @JsonProperty("primary-key")
    private Integer id;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String name;

    @JsonFormat(pattern="yyyy-MM-dd hh:mm:ss", locale="zh", timezone="UTC")
    private Date birthday;
}