SpringMVC基础知识-01-C-2020-10-15

日志编号说明
C-2020-10-15第一次创建

SpringMVC 基础知识

浅谈MVC

VC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。就是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。

SpringMVC

SpringMVC简介

Spring Web MVC是构建在Servlet API上的原始Web框架,从一开始就包含在Spring Framework中。 正式名称 “Spring Web MVC,” 来自其源模块(spring-webmvc)的名称,但它通常被称为“Spring MVC”。
简而言之,springMVC是Spring框架的一部分,是基于java实现的一个轻量级web框架。
学习SpringMVC框架最核心的就是DispatcherServlet的设计,掌握好DispatcherServlet是掌握SpringMVC的核心关键。

SpringMVC的优点

  1. 清晰的角色划分:控制器(controller)、验证器(validator)、命令对象(command obect)、表单对象(form object)、模型对象(model object)、Servlet分发器(DispatcherServlet)、处理器映射(handler mapping)、试图解析器(view resoler)等等。每一个角色都可以由一个专门的对象来实现。
  2. 强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器(validator)的引用。
  3. 可适配、非侵入:可以根据不同的应用场景,选择何事的控制器子类(simple型、command型、from型、wizard型、multi-action型或者自定义),而不是一个单一控制器(比如Action/ActionForm)继承。
  4. 可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
  5. 可定制的绑定(binding)和验证(validation):比如将类型不匹配作为应用级的验证错误,这可以保证错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。
  6. 可定制的handler mapping和view resolution:Spring提供从最简单的URL映射,到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
  7. 灵活的model转换:在Springweb框架中,使用基于Map的键/值对来达到轻易的与各种视图技术集成。
  8. 可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
  9. 简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(theme)之类的许多功能。他提供在标记方面的最大灵活性。
  10. JSP表单标签库:在Spring2.0中引入的表单标签库,使用在JSP编写表单更加容易。
  11. Spring Bean的生命周期:可以被限制在当前的HttpRequest或者HttpSession。准确的说,这并非Spring MVC框架本身特性,而应归属于Spring MVC使用的WebApplicationContext容器。

SpringMVC的实现原理

当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。下面给出了一个简易的流程图。

SpringMVC简易过程图从上面那个简易流程图往深钻,配合着一些源码的知识,可以得到如下这张图。
匹配着源码的讲解

  1. DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求
  2. HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler
  3. 返回处理器执行链,根据url查找控制器,并且将解析后的信息传递给DispatcherServlet
  4. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler
  5. 执行handler找到具体的处理器
  6. Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView
  7. HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet
  8. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名
  9. 视图解析器将解析的逻辑视图名传给DispatcherServlet
  10. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图,进行试图渲染
  11. 将响应数据返回给客户端

SpringMVC demo

基于XML文件的SpringMVC demo

首先给出项目的pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.phl</groupId>
    <artifactId>springmvc_csdn</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/jsp-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
    </dependencies>

</project>

在上面的pom中,在使用springMVC的时候,需要引入springMVC对应的pom信息,之后maven会根据pom信息去引入他依赖的其他包,引入spring-webmvc之后,实际上导入了如下包:
引入spring-webmvc之后的结果在pom中还引入了两个,分别是javax.servlet和javax.servlet.api这两个,写demo的时候不要忘记引入。

下面是web.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指明springMVC的配置信息-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:mvcApplication.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--/*和/都是拦截所有请求,/会拦截的请求不包含*.jsp,而/*的范围更大,还会拦截*.jsp这些请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

这里面主要是写了servlet和servlet-mapping配置。其中,通过servlet信息导入了springmvc对应的配置文件。
下面是springmvc的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <!--配置后缀-->
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!--此时的id,就是访问链接-->
    <bean id="/hello" class="com.phl.controller.HelloController"></bean>
</beans>

在springmcv的配置文件中,首先定义了视图解析器。针对视图解析器的具体作用,如何自定义,后续再讲,这里只是简单应用。出去这个,就配置了controller的信息。
在配置controller的时候,bean标签内的id属性,就变成了controller对应的URL,其他没什么区别。

下面给出controller的代码

package com.phl.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

/**
 * 不是用注解的情况下, 需要实现Controller接口
 */
public class HelloController implements Controller {

    @Override
    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        //将需要的值传递到modelAndView中
        modelAndView.addObject("hello","Hello SpringMVC");
        //设置要跳转的视图
        modelAndView.setViewName("hello");
        return modelAndView;
    }
}

在上面的代码实例中,唯一要注意的是,在不使用注解的时候,controller类需要实现controller接口,并重写里面的方法handleRequest,这个方法就是处理请求的。从这里就看出来,在使用注解的情况下,不会在因为多个处理请求的方法而产生对个实现类。
最后,给出对应页面的代码,页面位置就是视图解析器中配置的前缀位置。

<%--
  Created by IntelliJ IDEA.
  User: PANHANLIN
  Date: 2020/10/16
  Time: 10:04
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello</title>
</head>
<body>
${hello}
</body>
</html>

综上,就是基于XML的springmvc demo

基于注解的SpringMVC demo

基于注解的SpringMVC例子,在开发过程中,需要引入的POM,以及在web.xml中要写的内容是不变的,在Spring配置文件中,还是需要配置上对应的视图解析器。但是,这里不再声明Controller对应的Bean,转而,开启注解扫描,也就是component-scan。

<?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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--自动扫描包,由IOC容器进行控制管理-->
    <context:component-scan base-package="com.phl"></context:component-scan>
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

在给出demo的Controller之前,先将几个常用的注解,一些基础知识进行讲解。

如何给请求传递参数

默认情况下,可以直接在方法的参数中填写跟请求一样的名称,此时会默认接受参数。如果有值,直接赋值,如果没有,则赋值为null。

@RequestParam,获取请求中的参数值,在使用此注解之后,参数的名称不必与请求中的名称一致。
比如

//请求是:http://localhost:8080/user?user_id=1
public String getUser(@RequestParam("user_id") Integer id){
	...
}

在上面的例子中,请求中的参数是user_id,方法对应的参数是id,由于@RequestParam的存在,这两个参数名不再必须相同。
对于@RequestParam注解,有三个参数可以进行配置。

  1. value:表示要获取的参数值
  2. required:表示此参数是否必须,默认是true,如果请求时没有这个参数,就会报错,如果值为false,那么不写参数不会有任何错误
  3. defaultValue:如果在使用的时候没有传递参数,那么定义默认值即可

@RequestHeader注解,可以获取请求头中的信息

public String header(@RequestHeader("User-Agent") String agent){
	...
}
//相当于
request.getHeader("User-Agent");

@CookieValue,可以获取cookie中的信息。

public String cookie(@CookieValue("JSESSIONID") String id){
  ...
}

//相当于
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
	cookie.getValue();
}	

如果要获取cookie中没有的信息,那么此时会报错,同样,此注解中也包含三个参数,跟@RequestParam一样。

@PathValue,用于获取请求中通配符对应的值。
例如:

//请求是:http://localhost:8080/user/1
@RequestMapping("/user/{user_id}")
public String getUser(@PathValue("user_id") Integer id){
	...
}
乱码问题的解决

我们在表单或者发送请求的时候,经常会遇到中文乱码的问题。
GET请求:在server.xml文件中,添加URIEncoding=“UTF-8”
上面说的server.xml,是指Tomcat中的配置文件。在server.xml中,给两处Connector中增加 URIEncoding 属性。
POST请求:编写过滤器进行实现,如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--配置DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--
        关联springmvc的配置文件:
        此配置文件的属性可以不添加,但是需要在WEB-INF的目录下创建 前端控制器名称-servlet.xml文件
        -->

        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
    </servlet>
    <!--匹配servlet的请求,
    /:标识匹配所有请求,但是不会jsp页面
    /*:拦截所有请求,拦截jsp页面

     但是需要注意的是,当配置成index.html的时候,会发现请求不到
     原因在于,tomcat下也有一个web.xml文件,所有的项目下web.xml文件都需要继承此web.xml
     在服务器的web.xml文件中有一个DefaultServlet用来处理静态资源,但是url-pattern是/
     而我们在自己的配置文件中如果添加了url-pattern=/会覆盖父类中的url-pattern,此时在请求的时候
     DispatcherServlet会去controller中做匹配,找不到则直接报404
     而在服务器的web.xml文件中包含了一个JspServlet的处理,所以不过拦截jsp请求
    -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!--解决post请求乱码-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <!--解决响应乱码-->
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

注意:如果配置了多个过滤器,那么字符编码过滤器一定要在最前面,否则会失效

SpringMVC对原生API的支持

在SpringMVC中也可以在参数上使用原生的Servlet API。

package com.phl.controller;

import com.phl.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.PrintWriter;

@Controller
public class UserController {

    @RequestMapping("/addUser")
    public String addUser(User user){
        System.out.println(user);
        return "success";
    }

    /**
     * SpringMVC也可以在参数上使用原生的Servlet API
     *
     *  HttpSession
     *  HttpServletRequest
     *  HttpServletResponse
     *
     *  java.security.Principal 安全协议相关
     *  Locale:国际化相关的区域信息对象
     *  InputStream:
     *      ServletInputStream inputStream = request.getInputStream();
     *  OutputStream:
     *      ServletOutputStream outputStream = response.getOutputStream();
     *  Reader:
     *      BufferedReader reader = request.getReader();
     *  Writer:
     *      PrintWriter writer = response.getWriter();
     * @param session
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("api")
    public String api(HttpSession session, HttpServletRequest request, HttpServletResponse response){
        request.setAttribute("requestParam","request");
        session.setAttribute("sessionParam","session");
        return "success";
    }
}
使用Model,Map,ModelMap进行数据传递

在SpringMVC中,除过使用原生API之外,还可以在方法的参数上传入Model,ModelMap,Map类型,此时都能够将数据传送回页面。

package com.phl.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Map;

@Controller
public class OutputController {

    @RequestMapping("/output1")
    public String output1(Model model){
        model.addAttribute("msg","hello,Springmvc");
        return "output";
    }

    @RequestMapping("/output2")
    public String output2(ModelMap model){
        model.addAttribute("msg","hello,Springmvc");
        return "output";
    }

    @RequestMapping("/output3")
    public String output1(Map map){
        map.put("msg","hello,Springmvc");
        return "output";
    }
}

**当使用此方式进行设置之后,会发现所有的参数值都设置到了request作用域中。**这三者对象之间的关系如下:

Model,ModelMap,Map之间的关系

使用ModelAndView进行数据传递

ModelAndView对象在使用的时候,更习惯把ModelAndView对象设置成返回值,使用实例如下:

package com.phl.controller;

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

@Controller
public class OutputController {

    @RequestMapping("/mv")
    public ModelAndView mv(){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("output");
        mv.addObject("msg","hello.modelAndView");
        return mv;
    }
}
使用session进行数据传递

@SessionAttribute:此注解可以表示,当向request作用域设置数据的时候同时也要向session中保存一份,此注解有两个参数,一个value(表示将哪些值设置到session中),另外一个type(表示按照类型来设置数据,一般不用,因为有可能会将很多数据都设置到session中,导致session异常)。

@Controller
@SessionAttributes(value = "msg")
public class OutputController {

    @RequestMapping("output1")
    public String output1(Model model){
        model.addAttribute("msg","hello,Springmvc");
        System.out.println(model.getClass());
        return "output";
    }
}

这个例子中,最终在页面去取值的时候会发现,在RequestScope(Request作用域)里有一个msg,并且在SessionScope(Session作用域)里也有一个msg,这个就是上面@SessionAttributes的意思。并且,这个例子中,是通过Model对象,将参数放置在Request作用域中,同理,还可以使用Map,ModelMap,ModelAndView这三个。

使用@ModelAttribute获取请求中参数

@ModelAttribute注解用于将方法的参数或者方法的返回值绑定到指定的模型属性上,并返回给web视图。
首先来介绍一个业务场景,来帮助大家做理解,在实际工作中,有些时候我们在修改数据的时候可能只需要修改其中几个字段,而不是全部的属性字段都获取,那么当提交属性的时候,从form表单中获取的数据就有可能只包含了部分属性,此时再向数据库更新的时候,肯定会丢失属性,因为对象的封装是springmvc自动帮我们new的,所以此时需要先将从数据库获取的对象保存下来,当提交的时候不是new新的对象,而是在原来的对象上进行属性覆盖,此时就可以使用@ModelAttribute注解。
@ModelAttribute注解,既可以用在方法上,也可以用在方法的入参上。当这个注解使用在方法上的时候,不需要去配置name或者value,这两个属性在修饰参数的时候才需要指定。
在使用在方法上的时候,请求一旦定位到了Controller上,那么Controller这个类上所有的,被标注了@ModelAttribute注解的方法都会自动调用。在这些方法调用完成之后,才会继续执行URL对应的方法。
下面用一个简单的例子进行描述。

package com.phl.controller;

import com.phl.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.support.BindingAwareConcurrentModel;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;

import java.util.Map;

/**
 * 这个类是用来学习和练习如何将服务端的值返回给页面。
 * 总共写了三部分内容。
 * 第一,二部分。涉及请求output1~6.
 * 其中1~3是正确的,4~6是错误的。
 * 着重演示了Map,Model,ModelMap这三类对象都是如何将数据带到页面里面去的。推荐使用Model,目前我也不知道为什么推荐使用Model
 *
 * 同时,在类上加了给SessionScope里面放置参数的注解。
 *
 * 第三部分,这个部分是在讲解,假设一个对象里面有4个属性,但是页面一开始传递的时候只给了4个属性。此时直接进行更新的话,就会出现属性丢失的情况。
 * 这类场景经常在页面中的编辑功能上出现。但是在使用mybatis之后,他提供了配置的那种方式,就不复存在这个问题了。
 * 现在讲这个,纯粹就是学习知识。
 *
 * @SessionAttributes
 * 这个注解是作用在类上的。
 * 起到的作用是,方法在给RequestScope里面添加返回值之后,顺手也往SessionScope里面放置一份。
 *
 * 也就是说,用这个注解的话,如果RequestScope里面没有的,SessionScope也不会有,如果不做其他操作的话。
 *
 * SessionAttributes注解里面有三个属性。
 * String[] value():第一个,第二个都是用来描述,有哪些参数要放置到Session中。这个属性置是数组
 * String[] names():第一个,第二个都是用来描述,有哪些参数要放置到Session中。这个属性置是数组
 * String[] types():那些类型的参数要放置到Session中,这个也是个数组,eg:Integer.class
 *
 * 这三个属性的作用是,从RequestScope中,找出同样名称的参数,或者数据类型符合的参数再在SessionScope中放置一份儿。
 *
 * @ApplicationScope
 * 这个注解从名字看,就是往Application作用域里面放置属性的。但是我没有做具体的演示。
 *
 */
@Controller
@SessionAttributes(names="msg",types = Integer.class)
public class ClassReturnAttController {

    private final String SUCCESS_PAGE="success";

    /**
     * 第三部分内容。
     * 这种操作最长发生在更新操作里面。
     * 当在页面完成编辑之后,页面上会把编辑后的数据返回回来。这里就会出现属性缺失。这样要是直接进行更新,就会出现属性丢失。
     * 所以需要进行补充缺失的值。
     * 自己用代码去实现的话,太冗余,并且效率不一定高。
     * 在springMVC里面,就针对这种情况提出了如下注解。
     *
     *@ModelAttribute
     * 这个注解可以使用在方法上,也可以使用在参数上。
     * 当使用在方法上的时候,不需要配置name或者value,这两个属性是在修饰参数的时候才需要指定。
     * 使用在方法上的时候,请求一旦定位到当前Controller,那么这里所有的,标注了@ModelAttribute的方法都会自动执行。
     *
     * 这些方法执行完成之后,才会去继续执行URL对应的方法。
     * 这里需要注意的点:
     *  执行被标注的方法时,其实做的就是从数据源里面得到一个被编辑的数据之前的内容,实例代码中是u
     *  然后把这个u放置在可以在请求里进行传递的对象Model中。
     *  这样,当执行到URL对应的方法中的时候,那个方法可以从Model中得到对应的这一步查询出来的数据,并且把查询的数据和修改的数据进行完美的结合。
     */

    @ModelAttribute
    public void query(@RequestParam("name") String name, Model model){
        //这里没有去做数据库环境,仅仅用一个new的过程模拟查询数据的过程。
        System.out.println("其实之前我一直不知道,这里能不能得到请求中的参数------------    "+name);
        //从输出结果来看,这里是可以拿到请求中的参数的,只要我想拿。
        User u = new User("张三",12,"男");
        model.addAttribute("msg","fix");
        model.addAttribute("user",u);
    }

    /**
     * 对于这个方法的详解。
     * 1,参数。
     *  这里的name,其实是为了在上一步,就是进入这个URL对应的方法之前的,query,这个方法里因为模拟了数据库的情况,因此需要一个标识符进行查询。
     *
     *  这里的,u,仔细看,在这个参数之前添加了ModelAttribute注解。
     *  这里说的意思是,这个u,是在query的过程里,往Model对象里面放置的那个u,并且往Model里放置u的时候,对应的键是user。
     *
     *  如果在这里把@ModelAttribute("user") 改成任意一个不是user的,比如user2
     *  那么在这个方法里输入的u就是:User{name='phl', age=null, gender='null'}
     *  这是因为没有在Model里面找到理应出现的对象。
     *
     *  在页面中,我只传递了name进来,另外两个属性是不存在的。
     *  但是通过ModelAttribute注解,我可以在进入真正的方法之前先获取到数据源中user 的数据,并且将获取的数据与请求里传递的数据进行结合,
     *  得到我最终想要的结果。
     *  这样的做法很方便。
     *
     *  页面传入的参数:User{name='phl', age=null, gender='null'}
     *  ModelAttribute初次生成的参数:User{name='张三', age=12, gender='男'}
     *
     *  两者结合后的参数:User{name='phl', age=12, gender='男'}
     *
     * 2,需要注意的点:
     *  在入参上使用@ModelAttribute注解的时候,这里配置的name或者value必须是在之前一步往Model里面add时候的KEY
     *
     *
     * @param name
     * @param u
     * @return
     */
    @RequestMapping("/fixAtts")
    public String fixAtts(@RequestParam("name") String name,@ModelAttribute("user") User u){
        System.out.println(u);
        return this.SUCCESS_PAGE;
    }

}

上面的例子是这么运行的。
首先,页面发送一个fixAtts请求,请求里带有一个参数(name),对应的参数值是phl
然后通过通过URL定位到对应的Controller中。
进入到指定的Controller中之后,首先会自动调用所有被标注了@ModelAttribute注解的方法,在这个例子里是“query”这个方法。
在query方法中,传入了fixAtts请求中本身就存在的name参数,以及内置参数Model。
传入name并进行输出是为了说明,在被@ModelAttribute标注的方法中,是允许获取请求参数的。
在query方法内部,new了一个User对象,这一步是在模拟数据库操作。我们可以仔细看到,new出来的User对象中,name属性对应的值是“张三”,并没有进行更改,就这样加入到了Model对象中。
执行完@ModelAttribute注解方法之后,会去执行RequestMapping对应的方法。在这个方法中,我只是进行了对象输出。从输出结果看,此时的User对象,name是入参phl,其他属性都是上一步在query方法中new出来的值。
因此可以看出来,@ModelAttribute注解的作用是,将查询出来的对象,与传入的入参进行拼接。如果对象中的属性是入参,则使用入参传入进来的值,其他没有传进来的值就直接使用我们查询出来的值。
这种方法和操作就是对在进行数据更新操作过程里,页面只传递了更新了的属性,其他属性缺失。而使用了@ModelAttribute之后,这部分的值可以被拼接完整。

注意,如果@ModelAttribute的方法上,没有放置在Model等对象中,而是进行了return操作,那样就类似如下写法了,此时,@ModelAttribute在方法上就写了name或者value这个值。并且,这个值也要与参数注解@ModelAttribute上配置的一样。

package com.phl.controller;

import com.phl.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;

@Controller
public class UserController {

    Object o1 = null;
    Object o2 = null;
    Object o3 = null;

    @RequestMapping("update")
    public String update(@ModelAttribute("u") User user,Model model){
        System.out.println(user);
        o2 = model;
        //可以看到所有的model都是同一个对象
        System.out.println(o1==o2);
        //可以看到存储的user对象也是同一个
        System.out.println(user == o3);
        return "output";
    }

    @ModelAttribute("u")
    public User MyModelAttribute(Model model){
        o1 = model;
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setAge(12);
        user.setPassword("123");
//        model.addAttribute("user",user);
        System.out.println("modelAttribute:"+user);
        o3 = user;
        return user;
    }
}
使用forward实现页面转发

在SpringMVC中,除了使用原生API进行转发和重定向之外,还提供快捷方式。
return “forward:/index.jsp”;

使用redirect实现页面重定向

同理,springmvc也提供了快捷的页面重定向的方式
return “redirect:/redirect”;

注意,转发和重定向是允许在多个请求之间进行跳转。
转发和重定向的简单解释如下:

区别转发重定向
根目录包含项目访问地址没有项目访问地址
地址栏不会发生变化会发生变化
哪里跳转服务器端进行的跳转浏览器端进行的跳转
请求域中数据不会丢失会丢失
静态资源的访问

因为DispatcherServlet会拦截所有的请求,如果此时没有对应的静态资源处理方法,那即使资源文件路径正确,也请求不到。在这个时候需要在Springmvc配置文件中增加这个配置。

<!--
此配置表示  我们自己配置的请求由controller来处理,但是不能请求的处理交由tomcat来处理
静态资源可以访问,但是动态请求无法访问
-->
<mvc:default-servlet-handler/>

但是加上此配置之后,大家又发现此时除了静态资源无法访问之外,我们正常的请求也无法获取了,因此还需要再添加另外的配置

<!--保证静态资源和动态请求都能够访问-->
<mvc:annotation-driven/>

自定义视图解析器

在讲自定义视图解析器之前,先说一下视图解析器。简单来说,视图解析器就是将Controller中返回的信息识别成对应的视图。在很多例子中我们都用过如下的一个配置:

<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="suffix" value=".jsp"></property>
        <property name="prefix" value="/WEB-INF/jsp/"></property>
    </bean>

上面就是对一个SpringMVC自带的视图解析器进行配置,这个视图解析器是InternalResourceViewResolver,内部资源视图解析器。他的内部配置了前缀和后缀。这样情况下,他会把Controller中返回类型是String的返回参数识别成对应的视图对象。
不光如此,回忆一下上面两个内容。
在SpringMVC中,为什么能够提供forward和redirect这两个的便捷方式?这其实也是视图解析器进行的操作。
下面先给出通用的Controller,这样更利于理解。

package com.phl.controller;

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

/**
 * 这个类是用来校验自定义视图解析器的。
 * 我定义了两个视图解析器。一个是基于XML完成的,解析phl:开头的请求。
 * 另一个是用注解完成的,解析 phl12345:开头的请求。
 * 因此,在下面的Controller方法里,总共会出现两组,每组两个形式的请求。
 */
@Controller
public class ViewResolverController {

    @RequestMapping("/resolveString")
    public String resolve(){

        return "phl:testResolve";
    }

    @RequestMapping("/resolveObject")
    public ModelAndView resolveObject(ModelAndView modelAndView){
        modelAndView.setViewName("phl:testResolve");
        return modelAndView;
    }

    @RequestMapping("/resolveAnnoString")
    public String resolveAnnotation(){

        return "phl12345:testResolve";
    }

    @RequestMapping("/resolveAnnoObject")
    public ModelAndView resolveAnnotationObject(ModelAndView modelAndView){
        modelAndView.setViewName("phl12345:testResolve");
        return modelAndView;
    }
}

在上面的代码中可以看出来,基于XML或者基于注解,如果返回String形式,那这个返回值会被当做View的名称来进行解析。如果使用ModelAndView的话,需要setViewName,之后被设置好的View名称也会通过视图解析器进行解析。

这里强调一点,在之前的Map,Model,ModelMap这三者的例子中(上面讲的,如何把值返回给页面),那里给出的例子都是返回String类型,也就是说,那些方法返回了一个视图的名称,而这三个在只是单纯的起到了数据传递的作用。
而ModelAndView不单单传递值,还指明了对应的View名称。

基于XML的视图解析器

虽然现在很少会直接去写基于XML的种种,但是这种形式不能说不会。在使用XML的形式下,自定义视图解析器需要实现两个核心 接口,分别是ViewResolver和Order接口。

package com.phl.view.viewresolver;

import com.phl.view.MyView;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;

import java.util.Locale;

/**
 * 在编写自己的视图解析器的时候,需要实现两个接口。
 * 第一个接口是 ViewResolver 视图解析器接口。 另一个是Ordered接口,Ordered接口是用来确定执行顺序的。
 *
 * 视图解析器的例子会写两种。一种是用springMVC配置文件的,一组是通过annotation的。
 * 下面的代码是第一组,通过配置文件。
 *
 * ViewResolver:
 *  根据传入的URL信息(viewName),返回指定的View对象(这个对象,在这里通过自己创建的View对象进行测试)
 *
 * Ordered:
 * Ordered接口里面只有一个方法,getOrder()。
 *  这个接口应该在很多地方都被实现过。
 *  这里用这个接口的原因是,在SpringMVC的核心类DispatcherServlet中,进行doDispatcher方法的时候,是从一个视图解析器的List中循环遍历里面的视图
 * 解析器。
 *  因此当存在多个视图解析器的时候,是存在实际执行顺序之分的。
 *  在InternalResourceViewResolver中,默认的Order(继承自父类)是最大的Integer数。对于Order来说,数字越大,执行越靠后。
 *  因此,当想让自己的视图解析器在Internal之前执行的话,只需要将Order的值设置的比它小即可。
 *
 *  注:如果自己的视图解析器比Internal执行靠后的话,那么有特殊URL部件的请求会进入到Internal中,最终的结果就是请求错误,500.
 *
 */
public class MyViewResolver implements ViewResolver, Ordered {
    private Integer order;//定义一个自己的order。如果不定义的话,在实例化的时候,会默认设置成最大的Integer。

    //增加set方法,其实是为了可以通过ApplicationContext容器进行赋值。

    public void setOrder(Integer order) {
        this.order = order;
    }

    //简易的逻辑,只是把我们自己的view 对象进行返回,完成视图解析的过程。没有匹配上的话,会返回null
    //并且,因为这里的Order属性,会通过ApplicationContext容器赋值,因此这个解析器执行后,会执行通用的解析器(如果在这里没有成功进行视图返回)
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        System.out.println(viewName);
        MyView myView = null;
        if(viewName.startsWith("phl:")){
            myView = new MyView();
        }
        return myView;
    }

    @Override
    public int getOrder() {
        return this.order;
    }
}

在Order接口里,需要实现getOrder方法,这个方法是获取order的值。之所以需要这个order,是因为在SpringMVC中,默认提供了多个视图解析器,并且还可能存在用户自定义的,因此这是一个集合,集合中的视图解析器是按照指定的order大小顺序进行执行。
在ViewResolver接口里,需要实现resolveViewName这个核心方法,这个方法的作用就是对自己定义的试图解析规则进行解析的,并且这个方法返回了一个View类型的对象,这个View就是说的视图。这里,再自定义一个View对象即可。

package com.phl.view;

import org.springframework.web.servlet.View;

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

/**
 * 自定义 View 对象。
 * 在编写自己的视图解析器的时候,需要返回一个View对象,因此自己定义一个自己的View对象
 */

public class MyView implements View {
    @Override
    public String getContentType() {
        return "text/html";
    }

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html");
        response.getWriter().write("欢迎~");
    }
}

自定义视图的时候,需要实现View接口,并且实现里面两个核心方法,getContentType和render。其中,getContentType,我没有做过研究,每次只是按照SpringMVC中其他实现了该接口的类的内容进行粘贴。

写到这里,代码方面的内容已经写完,接着就是在SpringMVC的配置文件中加上刚刚写好的视图解析器。

<?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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.phl"></context:component-scan>
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="suffix" value=".jsp"></property>
        <property name="prefix" value="/WEB-INF/jsp/"></property>
    </bean>
    
<!--
    配置自己的视图解析器
-->
    <bean class="com.phl.view.viewresolver.MyViewResolver">
        <!--这里配置的就是Order的值。这个值会通过代码的Set方法进行注入-->
        <property name="order" value="2"></property>
    </bean>
</beans>

写到这一步,就可以进行测试了。

基于注解的视图解析器

基于注解的写法就更加简单了。
下面就是基于注解的视图解析器的代码。写完之后就可以直接进行测试。

package com.phl.view.viewresolver;

import com.phl.view.MyView;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;

import java.util.Locale;

/**
 * 这个例子是想说,通过注解的话,需要怎么做。
 * 在通过注解的例子里,我不在实现Ordered接口,这个排序会通过Order注解来完成.
 * @Component , 把这个类交由SpringIoC容器
 * @Order(0) , 作用于XML那一版例子一样,指定当前视图解析器的执行顺序。数字越小,越优先
 */
@Component
@Order(1)
public class MyAnnotationResolver implements ViewResolver {
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        System.out.println(viewName);
        MyView myView = null;
        if(viewName.startsWith("phl12345:")){
            myView = new MyView();
        }
        return myView;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值