• 04-SpringMVC(1)


    SpringMVC 概述

    概述

    1. Spring 为展现层提供了基于 MVC 设计理念的优秀的 web 框架,是目前最主流的 MVC 框架之一
    2. Spring 3.0 后全面超越 struts2,成为最优秀的 MVC 框架
    3. Spring MVC 通过一套 MVC 注解,让 POJO(Plain Old Java Object) 成为处理请求的控制器,而无需实现任何接口
    4. 支持 REST(Restful) 风格的RUL
    5. 采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性

    是什么

    一种轻量级、基于 MVC 的 web 层应用框架。偏前端而不是基于业务逻辑层。Spring 框架的一个后续产品。

    Spring 框架结构图:

    能干啥

    1. 天生与 Spring 框架集成,如:IOC、AOP
    2. 支持 Restful 风格
    3. 进行更简洁的 web 层开发
    4. 支持灵活的 URL 到页面控制器的映射
    5. 非常容易与其他视图技术集成,如:Velocity、FreeMarker 等等
    6. 因为模型数据不存放在特定的 API 里,而是放在一个 Model 里(Map 数据结构实现,因此很容易被其它框架使用)
    7. 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的 API
    8. 更加简单、强大的异常处理
    9. 对静态资源的支持
    10. 支持灵活的本地化、主题等解析

    怎么用

    将 web 层进行了职责解耦,基于请求-响应模型。

    常用主要组件:

    HelloWorld

    开发过程

    (1) 新建 web 工程,导入需要的 jar 包

    (2) 在 web.xml 中配置 SpringMVC 的核心(前端)控制器 DispatcherServlet

    DispatcherServlet 会自动从 contextConfigLocation 所指定的位置加载 SpringMVC 的配置文件。

    public class DispatcherServlet extends FrameworkServlet {...}
    public abstract class FrameworkServlet ... {
        /** Explicit context config location */
        private String contextConfigLocation;
    }
    

    配置 SpringMVC 核心控制器:

    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置 DispatcherServlet 的初始化参数:设置文件的路径和文件名称 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- 项目启动时加载;值越小,优先级越高 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    

    【补充】也可以不通过初始化参数 contextConfigLocation 来配置 SpringMVC 的配置文件, 而使用默认的:/WEB-INF/<servlet-name>-servlet.xml

    (3) 加入 Spring MVC 的配置文件:springmvc.xml

    <!-- 扫描组件,将带有 @Controller 注解的类作为 SpringMVC 的控制层加载 -->
    <context:component-scan base-package="cn.edu.nuist.test"></context:component-scan>
    
    <!--
        配置 [视图解析器]
            作用:将 prefix + 视图名称 + suffix 拼接得到实际的物理视图,然后做转发操作
            举例:/WEB-INF/views/success.jsp
    -->
    <bean id="viewResolver"
            class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    

    (4) 需要创建一个入口页面:index.jsp

    <a href="${pageContext.request.contextPath }/helloworld">Hello World</a>
    

    (5) 编写处理请求的处理器,并标识为处理器

    @Controller // 声明 Bean 对象为一个控制器组件
    public class TestController {
        // 使用 @RequestMapping 注解来映射请求的 URL
        // 请求的资源要与注解的 value 属性值一致 (与方法名无关)
        @RequestMapping("helloWorld")
        public String helloWorld() {
            System.out.println("SUCCESS");
            // 返回值会通过 [视图解析器] 解析为实际的物理视图
            return "success";
        }
    }
    

    (6) 编写视图:/WEB-INF/views/success.jsp

    <h1>SUCCESS!</h1>
    

    执行流程


    1. 客户端请求提交到 DispatcherServlet
    2. 由 DispatcherServlet 控制器查询一个或多个 HandlerMapping,找到处理请求的 Controller
    3. DispatcherServlet 将请求提交到 Controller(也称为Handler)
    4. Controller 调用业务逻辑处理后,返回 ModelAndView
    5. DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,找到 ModelAndView 指定的视图
    6. 视图负责将结果显示到客户端

    @RequestMapping

    简述

    • 是映射请求的注解,SpringMVC 使用 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求
    • 在控制器的类定义及方法定义处都可标注 @RequestMapping
      • 标记在类上:提供初步的请求映射信息。相对于 WEB 应用的根目录
      • 标记在方法上:提供进一步的细分映射信息。相对于标记在类上的 URL。若类上未标注 @RequestMapping,则方法处标记的 URL 相对于 WEB 应用的根目录
    • 【作用】DispatcherServlet 截获请求后,就通过控制器上 @RequestMapping 提供的映射信息确定请求所对应的处理方法

    一个 HTTP 请求报文:

    源码

    package org.springframework.web.bind.annotation;
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Mapping
    public @interface RequestMapping {
        String[] value() default {};
        RequestMethod[] method() default {};
        String[] params() default {};
        String[] headers() default {};
        String[] consumes() default {};
        String[] produces() default {};
    }
    

    映射参数、方法和头

    @RequestMapping 除了可以使用请求 URL 映射请求外,还可以使用请求方法、请求参数及请求头映射请求。

    @RequestMapping 的 value、method、params 及 headers 分别表示请求 URL、请求方法、请求参数及请求头的映射条件,他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。

    method

    用来设置请求方式,只有客户端发送请求的方式 IN {method 属性值数组} 中,才会处理请求。常用的 4 个:GET(查询)、POST(添加)、PUT(修改)、DELETE(删除)

    public enum RequestMethod {
        GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
    }
    
    1. 定义控制器方法
      @Controller
      @RequestMapping("/springmvc")
      public class SpringMVCController {
          @RequestMapping(value="/testMethod", method=RequestMethod.POST)
          public String testMethod(){
              System.out.println("testMethod...");
              return "success";
          }
      }
      
    2. 以 GET 方式请求
      <a href="springmvc/testMethod">testMethod</a>
      
    3. 发生错误;当以 POST 方式请求,则 200

    params

    • 用来设置客户端传到服务器的数据,支持表达式。
    • 举例:@RequestMapping(value="teset", params={"username", "!sex", "score=100", "age!=18"})
      • 要求请求中必须包含名为 username 的请求参数
      • 要求请求中不能带有名为 sex 的请求参数
      • 要求请求中必须包含名为 score 的请求参数,且其值必须等于 100
      • 要求请求中必须包含名为 age 的请求参数,但其值不能等于 18

    headers

    用来设置请求头信息,所发送的请求的请求头信息一定要和 headers 属性中设置的一致。

    支持 Ant 路径风格

    • Ant 风格资源地址支持 3 种匹配符:
      ?: 匹配文件名中的一个字符
      *: 匹配文件名中的任意字符
      **: 匹配多层路径
      
    • @RequestMapping 还支持 Ant 风格的 URL

    占位符 @PathVariable

    带占位符的 URL 是 Spring 3.0 新增功能,该功能在 SpringMVC 向 REST 目标挺进发展中具有里程碑的意义。通过使用 @PathVariable 注解可以将 URL 中占位符参数绑定到控制器处理方法的形参中

    /*
     * 之前: localhost:8080/pName/testREST?id=1101&username=TREE
     * 之后: localhost:8080/pName/testREST/1101/TREE
     */
    
    @RequestMapping("/testREST/{id}/{username}")
    public String testREST(@PathVariable("id")Integer id
            , @PathVariable("username")String username) {
        System.out.println(id + ":" + username);
        return "success";
    }
    

    REST

    REST 简述

    REST —— (Resource) REpresentational State Transfer。(资源)表现层状态转化。通俗来讲就是:资源在网络中以某种表现形式进行状态转移

    • Resource:资源,即数据(前面说过网络的核心)。可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。获取这个资源,访问它的 URI 就可以,因此 URI 即为每一个资源的独一无二的识别符
    • Representational:把资源具体呈现出来的某种表现形式。比如用 HTML,JSON,XML,JPEG 等
    • State Transfer:状态转换。每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”。通过 HTTP 动词实现

    首先为什么要用 RESTful 结构呢?大家都知道"古代"网页是前端后端融在一起的,比如之前的 PHP,JSP 等。在之前的桌面时代问题不大,但是近年来移动互联网的发展,各种类型的 Client 层出不穷,RESTful 可以通过一套统一的接口为 Web、iOS 和 Android 提供服务。另外对于广大平台来说,比如 Facebook platform,微博开放平台,微信公共平台等,它们不需要有显式的前端,只需要一套提供服务的接口,于是 RESTful 更是它们最好的选择。在 RESTful 架构下:


    • REST 描述的是在网络中 Client 和 Server 的一种交互形式;REST 本身不实用,实用的是如何设计 RESTful API(REST 风格的网络接口)
    • Server 提供的 RESTful API 中,URL 中只使用名词来指定资源,原则上不使用动词。“资源”是 REST 架构或者说整个网络处理的核心。比如:http://api.qc.com/v1/newsfeed 获取某人的新闻;http://api.qc.com/v1/friends 获取某人的好友列表;http://api.qc.com/v1/profile 获取某人的详细信息
    • 用 HTTP 协议里的动词来实现资源的添加,修改,删除等操作。即通过 HTTP 动词来实现资源的状态扭转:GET 用来获取资源;POST 用来新建资源(也可以用于更新资源);PUT 用来更新资源;DELETE 用来删除资源
      • [DELETE] http://api.qc.com/v1/friends 删除某人的好友(在 HTTP parameter 指定好友 id)
      • [POST] http://api.qc.com/v1/friends 添加好友
      • [UPDATE] http://api.qc.com/v1/profile 更新个人资料
    • Server 和 Client 之间传递某资源的一个表现形式,比如用 JSON,XML 传输文本,或者用 JPG,WebP 传输图片等。当然还可以压缩 HTTP 传输时的数据(on-wire data compression)
    • 用 HTTP Status Code 传递 Server 的状态信息。比如最常用的 200 表示成功,500 表示 Server 内部错误等。主要信息就这么点。最后是要解放思想,Web 端不再用之前典型的 PHP 或 JSP 架构,而是改为前端渲染和附带处理简单的商务逻辑(比如 AngularJS 或者 BackBone 的一些样例)。Web 端和 Server 只使用上述定义的 API 来传递数据和改变数据状态。格式一般是 JSON。iOS 和 Android 同理可得。由此可见,Web,iOS,Android 和第三方开发者变为平等的角色通过一套 API 来共同消费 Server 提供的服务。

    [总结] URL 定位资源,用 HTTP 动词(GET,POST,DELETE,PUT)描述操作

    • 看 URL 就知道要什么
    • 看 HTTP Method 就知道干什么
    • 看 HTTP status code 就知道结果如何

    HiddenHttpMethodFilter

    浏览器 <form> 表单只支持 GET 与 POST 请求,而 DELETE、PUT 等 method 并不支持,Spring 3.0 添加了一个过滤器,可以将这些请求转换为标准的 HTTP 方法,使得支持 GET、POST、PUT 与 DELETE 请求。

    图解

    源码

    public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    
        public static final String DEFAULT_METHOD_PARAM = "_method";
    
        private String methodParam = DEFAULT_METHOD_PARAM;
    
        public void setMethodParam(String methodParam) {
            Assert.hasText(methodParam, "'methodParam' must not be empty");
            this.methodParam = methodParam;
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response
                , FilterChain filterChain) throws ServletException, IOException {
            String paramValue = request.getParameter(this.methodParam);
            if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
                filterChain.doFilter(wrapper, response);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    
        private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
    
            private final String method;
    
            public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
                super(request);
                this.method = method;
            }
    
            @Override
            public String getMethod() {
                return this.method;
            }
        }
    }
    

    配置

    <!-- 配置支持 REST 风格的过滤器:可以将 POST 请求转换为 PUT/DELETE 请求 -->
    <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>
    

    测试

    测试 REST 风格的 GET,POST,PUT,DELETE 操作:

    • 新增:/order → POST
    • 修改:/order/1101 → PUT ? id=1101
    • 获取:/order/1101 → GET ? id=1101
    • 删除:/order/1101 → DELETE ? id=1101

    rest.jsp

    <!-- Test1: 测试 REST 风格 GET 请求 -->
    <a href="testRESTGet/1101">testREST GET</a><br/>
    
    <!-- Test2: 测试 REST 风格 POST 请求 -->
    <form action="testRESTPost" method="POST">
        <input type="submit" value="testRESTPost">
    </form>
    
    <!-- Test3: 测试 REST 风格 PUT 请求 -->
    <form action="testRESTPut/1101" method="POST">
        <input type="hidden" name="_method" value="PUT">
        <input type="submit" value="testRESTPut">
    </form>
    
    <!-- Test4: 测试 REST 风格 DELETE 请求 -->
    <form action="testRESTDelete/1101" method="POST">
        <input type="hidden" name="_method" value="DELETE">
        <input type="submit" value="testRESTDelete">
    </form>
    
    <!-- 超链接肯定是 GET 方式,条件1 都不满足! -->
    <a href="testRESTDelete?_method=DELETE&id=1101">错误方式</a>
    

    RestController.java

    @RequestMapping(value="/testRESTGet/{id}", method=RequestMethod.GET)
    public String testRESTGet(@PathVariable(value="id") Integer id){
        System.out.println("testRESTGet id=" + id);
        return "success";
    }
    
    @RequestMapping(value="/testRESTPost", method=RequestMethod.POST)
    public String testRESTPost(){
        System.out.println("testRESTPost");
        return "success";
    }
    
    @RequestMapping(value="/testRESTPut/{id}", method=RequestMethod.PUT)
    public String testRESTPut(@PathVariable("id") Integer id){
        System.out.println("testRESTPut id=" + id);
        return "success";
    }
    
    @RequestMapping(value="/testRESTDelete/{id}", method=RequestMethod.DELETE)
    public String testRESTDelete(@PathVariable("id") Integer id){
        System.out.println("testRESTDelete id=" + id);
        return "success";
    }
    

    处理请求数据

    Review:JavaWEB 处理请求数据

    • 请求参数:Request.getParameter(paramName); / Request.getParameterMap();
    • Cookie 信息:Request.getCookies();
    • 请求头信息:Request.getHeader();

    请求处理方法签名

    1. Spring MVC 通过分析处理方法的签名(方法名+ 参数列表),将 HTTP 请求信息绑定到处理方法的相应形参中
    2. Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任何方式对方法进行签名
    3. 必要时可以对方法及方法形参标注相应的注解(@PathVariable 、@RequestParam、@RequestHeader 等)
    4. Spring MVC 会将 HTTP 请求的信息绑定到相应的方法形参中,并根据方法的返回值类型做出相应的后续处理

    @RequestParam

    在处理方法形参处使用 @RequestParam 可以把请求参数传递给请求方法(若方法形参名和请求参数名一致,那么就算不使用该注解,也能实现自动赋值)。

    • value:请求参数的参数名
    • required:是否必须。默认为 true,表示请求参数中必须包含对应的参数;若不存在,将抛出异常
    • defaultValue:默认值。当没有传递该参数或者该参数的值为空字符串时使用该值进行赋值
    @RequestMapping(value="/param", method=RequestMethod.POST)
    public String registUser(@RequestParam(value="name", required=false
            , defaultValue="admin")String username, String password) {
        System.out.println(username + "=" + password);
        return "success";
    }
    

    @RequestHeader

    • 使用 @RequestHeader 绑定请求报头的属性值
    • 请求头包含了若干个属性,服务器可据此获知客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法形参中
    • 用法同 @RequestParam 一致
    @RequestMapping(value="/testRequestHeader")
    public String testRequestHeader(@RequestHeader(value="Accept-Language") String al){
        System.out.println("testRequestHeader - Accept-Language:" + al);
        return "success";
    }
    

    @CookieValue

    使用 @CookieValue 绑定请求中的 Cookie 值。

    @RequestMapping("/testCookieValue")
    public String testCookieValue(@CookieValue("JSESSIONID") String sessionId) {
        System.out.println("testCookieValue: sessionId: " + sessionId);
    ##### return "success";
    }
    

    使用 POJO 作为参数

    Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。支持级联属性,如:dept.deptId、dept.address.tel 等。

    public class User {
        private Integer id ;
        private String username;
        private String password;
        private String email;
        private int age;
        private Address address;
    }
    
    public class Address {
        private String province;
        private String city;
    }
    

    使用Servlet原生API作为参数

    MVC 的 Handler 方法可以接受哪些 ServletAPI 类型的参数:HttpServletRequest、HttpServletResponse、HttpSession、java.security.Principal、Locale、InputStream、OutputStream、Reader 和 Writer。

    @RequestMapping("/param")
    public String registUser(User user, HttpServletRequest request) {
        System.out.println(user);
        System.out.println(request.getParameter("username"));
        return "success";
    }
    

    AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker 部分源码:

    进入 resolveHandlerArguments(),找到其中 resolveCommonArgument() 并进入,进而再找到 resolveStandardArgument() 并进入。如此,便可知道 SpringMVC 本质上还是从原生 Servlet API 中获取相应的对象。

    处理响应数据

    Review:JavaWeb 处理响应数据

    • request.setAttribute(xxx)
    • request.getRequestDispatcher(地址).forward(req, resp);

    如下途径输出模型数据

    1. ModelAndView:处理方法返回值类型为 ModelAndView 时, 方法体即可通过该对象添加模型数据
    2. Map 或 Model:若形参为 org.springframework.ui.Modelorg.springframework.ui.ModelMapjava.uti.Map 类型,则当处理方法返回时,其中的数据会自动添加到模型中

    ModelAndView

    控制器处理方法的返回值如果为 ModelAndView,则其既包含视图信息,也包含模型数据信息。

    @RequestMapping("/param")
    public ModelAndView registUser(User user) {
        ModelAndView mav = new ModelAndView();
        mav.addObject("attr1", "value1"); // 往 request 作用域中放值
        mav.setViewName("success"); // 设置视图名称,实现请求转发
        return mav;
    }
    

    ModelAndView 源码

    • 两个重要的成员变量
      private Object view; // 视图信息
      private ModelMap model; // 模型数据
      
    • 添加模型数据
      MoelAndView addObject(String attributeName, Object attributeValue)
      ModelAndView addAllObject(Map<String, ?> modelMap)
      
    • 设置视图
      void setView(View view) // 设置视图对象
      void setViewName(String viewName) // 设置视图名字
      
    • 获取模型数据
      protected Map<String, Object> getModelInternal()
      public ModelMap getModelMap()
      public Map<String, Object> getModel()
      

    过程源码

    1. 在 DispatcherServlet 中的 945 行, 开始调用请求处理器中的请求处理方法,方法执行完成后,返回一个 ModelAndView 对象
    2. 在 DispatcherServlet 的 959 行, 准备开始处理 ModelAndView
    3. 在 DispatcherServlet 的 1012 行,准备处理 ModelAndView 中的 视图信息 还有 模型数据
    4. 在 DispatcherServlet 的 1204 行,根据 ModelAndView 中的视图信息,通过 [视图解析器] 解析得到一个 View 视图对象
    5. 在 DispatcherServlet 的 1225 行, 开始处理模型数据(说明:render()View<I> 中定义的,在 AbstractView 类中进行了实现,所有具体的视图类都使用 AbstractView 中实现的 render()
    6. 在 AbstractView 中的 266 行,整合输出模型数据(说明:renderMergedOutputModel() 是 AbstractView 中定义的抽象方法, 在每个具体的视图实现类中进行实现)
    7. 在 InternalResourceView 中的 180 行,将模型数据设置到 Request 域对象中
    8. 在 InternalResourceView 中的 189 行, 获取转发器
    9. 在 InternalResourceView 中的 209 行, 请求转发

    Map & Model

    Spring MVC 在内部使用了一个 org.springframework.ui.Model<I> 存储模型数据。

    具体使用步骤:

    1. Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器
    2. 如果方法的形参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传递给这个形参
    3. 在方法体内,开发者可以通过这个形参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据

    @RequestMapping(value="/param", method=RequestMethod.POST)
    public String registUser(Map<String, Object> map) {
        map.put("attr2", "attr2"); // 向作用域中放值
        return "success"; // 返回视图名称
    }
    
    @RequestMapping(value="/param", method=RequestMethod.POST)
    public String registUser(Model model) {
        model.addAttribute("attr3", "value3");
        return "success";
    }
    

    执行过程同 ### 7.2.2 相同。

  • 相关阅读:
    NET Core-TagHelper实现分页标签
    NET Core-学习笔记(三)
    NET Core-学习笔记(二)
    NET Core-学习笔记(一)
    MVC默认路由实现分页-PagerExtend.dll
    Tomcat优化
    JVM参数配置大全
    tomcat8+memcached session共享
    Tomcat+Nginx+Redis+MySQL实现反向代理、负载均衡、session共享
    搭建Tomcat应用服务器、tomcat虚拟主机及Tomcat多实例部署
  • 原文地址:https://www.cnblogs.com/liujiaqi1101/p/13569707.html
Copyright © 2020-2023  润新知