Spring MVC详解

一、Spring MVC Web框架

1. Spring MVC的核心组件

  • DispatcherServlet:作为前端控制器,接收所有的HTTP请求并将其分发给其他组件进行处理。

  • HandlerMapping:根据请求的URL路径,将请求映射到相应的处理器(Controller)。

  • HandlerAdapter:调用处理器的处理方法来处理请求,并获取处理结果。

  • Handler:包含处理请求的业务逻辑,开发者需要编写Controller类定义处理方法,并在其中实现业务逻辑。

  • ViewResolver:将逻辑视图名称解析为实际的视图对象。

  • View:将模型数据渲染到具体的视图上,生成最终的响应结果。

  • Model:封装业务数据,通过Controller传递给View进行渲染。

  • HandlerExceptionResolver:处理Controller中抛出的异常,并返回相应的异常视图。

  • LocaleResolver:解析请求中的区域信息,用于国际化支持。

2. Spring MVC工作原理

​ 每当用户在Web浏览器中点击链接或提交表单的时候,请求就开始工作了。在请求离开浏览器时,会带有用户所请求内容的信息,至少会包含请求的URL,还可能会带有表单信息等。

​ Spring MVC所有的请求都会通过一个前端控制器Servlet(DispatcherServlet)。DispatcherServlet的任务是将请求发送给Spring MVC控制器,DispatcherServlet通过查询一个或多个处理器映射(Handler Mapping)来确定请求发送给那个控制器。而处理器适配器(Handler Adapter)负责将请求发送给处理器(Controller)进行处理。

​ 到了控制器,请求会写下欺负在并耐心等待控制器处理这些信息。控制器在完成逻辑处理之后,通常会产生一些信息,这些信息(Model)需要返回给用户并在浏览器上显示。同时,这些信息也需要以用户友好的方式进行格式化,所以,信息需要发送一个视图(View),通常是jsp。控制器最后需要将模型数据打包,并且标示出用于渲染输出的视图名,连同模型和视图名发送给DispatcherServlet。

​ DispatcherServlet会使用视图解析器(View Resolver)来讲逻辑视图名匹配为一个特定的视图实现。最后,视图对象负责将模型数据渲染到具体的视图上生成最终的响应结果,DispatcherServlet将视图生成的响应结果返回给客户端。

3. 搭建Spring MVC

首先导入依赖:

xml 复制代码
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.3.19</version>
</dependency>
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.3.19</version>
  </dependency>
1)使用XML文件配置
  1. 在resources目录中添加springmvc.xml

    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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    </beans>
  2. 在web.xml 配置Spring MVC的DispatcherServlet

    xml 复制代码
    <!DOCTYPE web-app PUBLIC
            "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <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>
            <!-- springmvc配置文件加载路径
                 1)默认情况下,读取WEB-INF下面的文件
                 2)可以改为加载类路径下(resources目录),加上classpath:
             -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc.xml</param-value>
            </init-param>
            <!--
               DispatcherServlet对象创建时间问题
                  1)默认情况下,第一次访问该Servlet的创建对象,意味着在这个时间才去加载springMVC.xml
                  2)可以改变为在项目启动时候就创建该Servlet,提高用户访问体验。
                      <load-on-startup>1</load-on-startup>
                            数值越大,对象创建优先级越低! (数值越低,越先创建)
            -->
            <load-on-startup>1</load-on-startup>
        </servlet>
        
        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <!--/ 匹配所有的请求;(不包括.jsp)-->
            <!--/* 匹配所有的请求;(包括.jsp)-->
            <!--*.do拦截以do结尾的请求-->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
  3. springmvc.xml进行配置

    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"
           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">
        <!-- 配置自动扫包 -->
        <context:component-scan base-package="com.shiftycat.controller" />
    
        <!-- 视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!--给逻辑视图加上前缀和后缀 -->
            <!--前缀-->
            <property name="prefix" value="/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
  4. 创建Controller控制器Handler,在里面编写接收参数,调用业务方法,返回视图页面等逻辑

    java 复制代码
    @Controller
    @RequestMapping(value = "/home")
    public class HelloHandler {
        /**
         * 当客户端访问index请求时
         * 直接自动关联到这个方法
         * 执行这个方法后,会返回结果
         *
         * @return
         */
        @RequestMapping(value = "/index")
        public String index() {
            System.out.println("接收到了请求");
            //返回逻辑视图 逻辑视图相当于视图的别名 通过这个找到物理视图,也就是真正的视图
            //这里返回的只是页面的名称,不是完整的页面访问路径
            return "index";
        }
    }
2)使用JavaConfig进行配置
  1. 实现AbstractAnnotationConfigDispatcherServletInitializer并重写三个方法

    java 复制代码
    public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
      
      // 指定根配置类
      @Override
      protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
      }
    
      // 指定配置类
      @Override
      protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class };
      }
    
      // 将DispatcherServlet映射到"/"
      @Override
      protected String[] getServletMappings() {
        return new String[] { "/" };
      }
    
    }
  2. 配置WebConfig

    java 复制代码
    @Configuration
    @EnableWebMvc //启动Spring MVC
    @ComponentScan("spittr.web") // 启动组件扫描
    public class WebConfig extends WebMvcConfigurerAdapter {
    
      // 配置JSP视图解析器
      @Bean
      public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
      }
      
      // 配置静态资源的处理
      @Override
      public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
      }
      
      @Override
      public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // TODO Auto-generated method stub
        super.addResourceHandlers(registry);
      }
    
    }
  3. 配置RootConfig

    java 复制代码
    @Configuration
    @ComponentScan(basePackages={"spittr"}, 
        excludeFilters={
            @Filter(type=FilterType.CUSTOM, value=WebPackage.class)
        })
    public class RootConfig {
      public static class WebPackage extends RegexPatternTypeFilter {
        public WebPackage() {
          super(Pattern.compile("spittr\\.web"));
        }    
      }
    }
  4. 编写基本的控制器

    java 复制代码
    @Controller //声明为控制器
    public class HomeController {
    
      @RequestMapping(value="/", method = GET)
      public String home(Model model) {
        return "home";
      }
    
    }
  5. home.jsp

    jsp 复制代码
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ page session="false" %>
    <html>
      <head>
        <title>Spitter</title>
        <link rel="stylesheet"
              type="text/css"
              href="<c:url value="/css/style.css" />" >
      </head>
      <body>
        <h1>Welcome to Spitter</h1>
    
        <a href="<c:url value="/spittles" />">Spittles</a> |
        <a href="<c:url value="/spitter/register" />">Register</a>
      </body>
    </html>

4. 常用注解

1)@RequestMapping

​ Spring MVC通过@RequestMapping注解把URL请求和业务方法进行映射,在控制器的类定义处以及方法定义处都可以添加@RequestMapping,在类定义处添加相当于多了一层访问路径。

  • value:指定URL请求的实际地址,是@RequestMapping的默认值。

  • method:指定请求的method类型,包括GET、POST、PUT、DELETE等。

  • params:指定request请求中必须包含的参数值,如果不包含的话,就无法调用该方法。

java 复制代码
@RequestMapping(value = "/params", method = RequestMethod.GET,
            params = {"id=1", "name=tom"})
public String params1(Integer id, String name) {
    System.out.println("接收到了请求,参数是:id=" + id + ",name=" + name);
    return "index";
}
2)接受请求的输入

​ Spring MVC允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:

  • 查询参数(Query Parameter)
  • 表单参数(Form Parameter)
  • 路径变量(Path Variable)

(1)处理查询参数:处理查询参数

​ @RequestMapping中的params是对URL请求参数进行限制,不满足条件的URL无法访问该方法,需要在业务方法中获取URL的参数值。另一种处理方式是:

java 复制代码
@RequestMapping(value = "/params", method = RequestMethod.GET)
public String params2(@RequestParam("id") Integer id, @RequestParam("str") String name) {
    System.out.println("接收到了请求,参数是:id=" + id + ",name=" + name);
    return "index";
}

Spring MVC可以自动完成数据类型转换,该工作是由HandlerAdapter来完成的。

(2)处理查询参数:处理RESTful风格的查询参数

  • 传统的URL:localhost:8080/hello/index?id=1&name=tom
  • RESTful URL:localhost:8080/hello/index/1/tom
java 复制代码
@RequestMapping(value = "/params/{id}/{str}", method = RequestMethod.GET)
public String params3(@PathVariable("id") Integer id, @PathVariable("str") String name) {
    System.out.println("接收到了请求,参数是:id=" + id + ",name=" + name);
    return "index";
}

(3)处理查询参数:映射Cookie

java 复制代码
//    往浏览器写入cookie
@RequestMapping(value = "/getMiss.do")
public String getMiss(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
    String str = "这是一个cookie";
    String value = URLEncoder.encode(str, "UTF-8");
    Cookie cookie = new Cookie("cname", value);
    cookie.setMaxAge(60 * 60 * 24 * 30);
    cookie.setPath("/");
    response.addCookie(cookie);
    return "index";
}

//    获取cookie
@RequestMapping("/getCookie.do")
public String getCookie(@CookieValue("cname") String value) throws UnsupportedEncodingException {
    System.out.println("value" + URLDecoder.decode(value, "UTF-8"));
    return "index";
}

(4)处理查询参数:使用POJO绑定参数

java 复制代码
public class User {

    private Integer id;
    private String name;
    private Address address;

    public User() {

    }

    public User(Integer id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}

public class Address {
    private Integer code;
    private String value;

    public Address(){

    }
    public Address(Integer code, String value) {
        this.code = code;
        this.value = value;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Address{" +
                "code=" + code +
                ", value='" + value + '\'' +
                '}';
    }
}
java 复制代码
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String addUser(User user) {
    System.out.println(user);
    return "index";
}

(5)处理查询参数:JSP页面的转发和重定向

​ Spring MVC默认是通过转发的形式响应JSP,可以手动进行修改。

java 复制代码
@RequestMapping("/restful/{id}/{name}")
public String restful(@PathVariable("id") Integer num, @PathVariable("name") String name){
    System.out.println(num+"-"+name);
    return "index";
}

重定向 :设置重定向的时候不能写逻辑视图,必须写明资源的物理路径,比如rediect:/index.jsp

java 复制代码
@RequestMapping("/restful2/{id}/{name}")
public String restful2(@PathVariable("id") Integer num, @PathVariable("name") String name){
    System.out.println(num+"-"+name);
    return "redirect:/index.jsp";
}

转发

java 复制代码
@RequestMapping("/restful3/{id}/{name}")
public String restful3(@PathVariable("id") Integer num, @PathVariable("name") String name){
    System.out.println(num+"-"+name);
    return "forward:/index.jsp";
}

5. 遇到的问题

1)请求传到后端遭遇中文乱码问题

解决方案:使用filiter过滤器

java 复制代码
<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>
2)后端将消息传至前端遭遇中文乱码问题

解决方案:springmvc.xml中配置转换器

xml 复制代码
<mvc:annotation-driven>
    <!--      消息转换器  -->
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
3)JS文件被拦截

​ 刚开始在web.xml中,配置的是所有的请求都会被DispatcherServlet拦截映射,但是现在我们访问的是实际存在的资源,逻辑请求需要映射,但是物理请求是不需要映射的。这个时候,它会把我们的物理请求也进行映射。

解决方案:在web.xml文件中配置

xml 复制代码
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>
4)自定义数据类型转换器

​ Spring MVC默认情况下可以对基本类型进行类型转换,例如可以将String转换为Integer,Double,Float等。但是Spring MVC并不能转换日期类型(java.util.Date),如果希望把字符串参数转换为日期类型,必须自定义类型转换器。

springmvc.xml

​ 在springmvc.xml中配置conversionService bean,这个bean是org.springframework.context.support.ConversionServiceFactoryBean的实例化对象,同时bean中必须包含一个converters属性,在其中注册所有需要使用的自定义转换器。

xml 复制代码
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.shiftycat.converter.DateConverter">
                <constructor-arg type="java.lang.String" value="yyyy/MM/dd"/>
            </bean>
        </list>
    </property>
</bean>

DateConverter类

​ 创建DateConverter类,并且实现org.springframework.core.convert.converter.Converter接口,这样它就成为了一个自定义数据类型转换器,需要指定泛型<String,Date>,表示把String类型转为Date类型

java 复制代码
public class DateConverter implements Converter<String, Date> {
    private String pattern;

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

    @Override
    public Date convert(String s) {
        System.out.println("运行DateConverter");
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.pattern);
        try {
            return simpleDateFormat.parse(s);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

JSP

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/converter/date" method="post">
    <input type="text" name="date" />(2023/12/06)
    <input type="submit" name="提交" />

</form>

</body>
</html>
java 复制代码
@Controller
@RequestMapping("/converter")
public class ConverterHandler {

    @RequestMapping("/date")
    @ResponseBody
    public String date(Date date) {
        System.out.println(date.toString());
        return date.toString();
    }
}

实例2

user.jsp

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>User</title>
</head>
<body>
<form action="/converter/user" method="post">
    <input type="text" name="user"/>(1-张三)<br/>
    <input type="submit" value="提交"/>
</form>
</body>
</html>

UserConverter

java 复制代码
public class UserConverter implements Converter<String, User> {

    @Override
    public User convert(String source) {
        String[] args = source.split("-");
        User user = new User();
        user.setId(Integer.valueOf(args[0]));
        user.setName(args[1]);
        return user;
    }
}

springmvc.xml

xml 复制代码
<!--  自定义数据类型转换器  -->
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.shiftycat.converter.DateConverter">
                <constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
            </bean>
            <bean class="com.shiftycat.converter.UserConverter"/>
        </list>
    </property>
</bean>


<mvc:annotation-driven conversion-service="conversionService">
    <!--      消息转换器  -->
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
        </bean>
        <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>

接口

java 复制代码
@RequestMapping("/user")
@ResponseBody
public String user(User user) {
    System.out.println(user.toString());
    return user.toString();
}

二、Spring MVC 数据绑定

1. 基本数据类型

​ 客户端HTTP请求中必须包含id参数,否则抛出500异常,因为id不能为null,null是引用类型。同时,id的值必须为数值,而且必须为整数,否则抛出400异常。

java 复制代码
@RequestMapping("/baseType")
@ResponseBody
public String baseType(int id){
    return "id:"+id;
}

2. 包装类

​ 如果HTPP请求中没有包含id参数,不会报错,id的值就是null,会直接返回id:null给客户端,但是如果id=a,或者id=1.2,同样会抛出400异常 ,因为数据类型无法转换

java 复制代码
@RequestMapping("/packageType1")
@ResponseBody
public String packageType1(Integer id){
    //如果不是包装类,就会因为请求中无此参数而报错。包装类的情况下,请求中无此参数就是null。
    System.out.println("id:"+id);
    return "id:"+id;
}

​ 那么我们如何处理这种情况呢?

  • value="id":把HTTP请求中名字为id的参数和Handler业务方法中的形参进行映射。
  • required:true表示id参数必须填,false表示非必填。
  • defaultValue="0":表示当HTTP请求中没有id参数的时候,形参的默认值是0。
java 复制代码
@RequestMapping("/packageType2")
@ResponseBody
public String packageType2(@RequestParam(name = "id", required = true) Integer id){
    //如果不是包装类,就会因为请求中无此参数而报错。包装类的情况下,请求中无此参数就是null。
    System.out.println("id:"+id);
    return "id:"+id;
}
@RequestMapping("/packageType3")
@ResponseBody
public String packageType3(@RequestParam(name = "id", required = true, defaultValue = "0") Integer id){
    //如果不是包装类,就会因为请求中无此参数而报错。包装类的情况下,请求中无此参数就是null。
    System.out.println("id:"+id);
    return "id:"+id;
}

3. 数组类型

java 复制代码
// localhost:8080/arrayType?names=tom&names=jack
@RequestMapping("/arrayType")
@ResponseBody
public String arrayType(String[] names){
    StringBuffer buffer = new StringBuffer();
    for (String str:names){
        buffer.append(str).append(" ");
    }
    return "names:"+buffer.toString();
}

4. POJO(Java对象)

java 复制代码
public class User {
    private Integer id;
    private String name;
    private Address address;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address=" + address +
                '}';
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
java 复制代码
public class Address {
    private Integer code;
    private String value;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Address{" +
                "code=" + code +
                ", value='" + value + '\'' +
                '}';
    }
}
java 复制代码
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String addUser(User user) {
    System.out.println(user);
    return "index";
}
java 复制代码
@RequestMapping(value = "/user2", method = RequestMethod.POST)
@ResponseBody
public String addUser2(User user) {
    System.out.println(user);
    return user.toString();
}

5. List

java 复制代码
public class UserList {
    private List<User> userList;

    public List<User> getUserList() {
        return userList;
    }

    public void setUserList(List<User> userList) {
        this.userList = userList;
    }
}
java 复制代码
@RequestMapping("/listType")
@ResponseBody
public String listType(UserList userList){
    StringBuffer buffer = new StringBuffer();
    for (User user:userList.getUserList()){
        buffer.append(user);
    }
    return "用户:"+buffer.toString();
}

注意:User类一定要有无参构造,否则抛出异常。

6. JSON

  • JSON数据必须用JSON.stringfy()方法转换成字符串
  • contentType:application/json;charset=UTF-8不能省略
  • @RequestBody注解
    • 读取HTTP请求参数,通过Spring MVC提供的HttpMessageConverter接口把读取的参数转换为JSON、XML格式的数据,绑定到业务方法的形参。
    • 需要使用组件结合@RequestBody注解把JSON转为JavaBean,这里使用FastJson,其优势是如果属性为空,就不会将其转为JSON。
  • @ResponseBody注解
    • 把业务方法返回的对象,通过HttpMessageConverter接口转为指定格式的数据,JSON、XML等,响应给客户端。
xml 复制代码
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>2.0.32</version>
</dependency>
xml 复制代码
<mvc:annotation-driven>
     <!--消息转换器 -->
     <mvc:message-converters>
         <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.FastJsonHttpMessageConverter"></bean>
     </mvc:message-converters>
 </mvc:annotation-driven>
java 复制代码
@RequestMapping(value = "/jsonType")
@ResponseBody
public String jsonType(@ResponseBody User user) {
    System.out.println(user);
    return user;
}

三、Spring MVC 视图层解析

​ 业务数据的绑定是指把业务数据绑定给JSP域对象,业务数据的绑定是由ViewResolver来完成的,开发时,我们先添加业务数据,再交给ViewResolver来绑定,我们重点是学习如何添加业务数据,Spring MVC提供了下面几种方式来添加业务数据:

  • Map
  • Model
  • ModelAndView
  • @SessionAttribue
  • @ModelAttribute
  • Servlet的API

​ Spring MVC在调用业务方法之前会先创建一个隐含对象作为业务数据的存储容器,设置业务方法的入参为Map类型,Spring MVC会把隐含对象的引用传递给入参。

1. 业务数据绑定到request域对象

1)Map

​ Spring MVC在调用业务方法之前会先创建一个隐含对象作为业务数据的存储容器,设置业务方法的入参为Map类型,Spring MVC会把隐含对象的引用传递给入参

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%--  EL表达式  --%>
    ${user}
</body>
</html>
java 复制代码
@Controller
@RequestMapping(value = "/resolver")
public class ViewResolverController {

    @RequestMapping(value = "/map")
    public String map(Map<String, Object> objectMap) {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        objectMap.put("user",user);
        return "show";
    }
}
2)Model

Model和Map类似,业务方法通过入参来完成业务数据的绑定:

java 复制代码
@RequestMapping(value = "/model")
public String model(Model model) {
    User user = new User();
    user.setId(2);
    user.setName("李四");
    model.addAttribute("user", user);
    return "show";
}
3)ModelAndView

MapModel不同的是,ModelAndView不仅包含业务数据,同时也封装了视图信息,如果使用ModelAndView来处理业务数据,业务方法的返回值必须是ModelAndView对象。业务方法中对ModelAndView进行两个操作:

  • 填充业务数据
  • 绑定视图信息
java 复制代码
// 第一种方式
@RequestMapping(value = "/modelandview1")
public ModelAndView modelandview1() {
    ModelAndView modelAndView = new ModelAndView();
    User user = new User();
    user.setId(3);
    user.setName("王五");
    modelAndView.addObject("user", user);
    modelAndView.setViewName("show");
    return modelAndView;
}
// 第二种方式
@RequestMapping(value = "/modelandview2")
public ModelAndView modelandview2() {
    ModelAndView modelAndView = new ModelAndView();
    User user = new User();
    user.setId(3);
    user.setName("王五");
    modelAndView.addObject("user", user);
    // 绑定视图信息
    View view = new InternalResourceView("/show.jsp");
    modelAndView.setView(view);
    return modelAndView;
}
// 第三种方式
@RequestMapping(value = "/modelandview3")
public ModelAndView modelandview3() {
    ModelAndView modelAndView = new ModelAndView("show");
    User user = new User();
    user.setId(3);
    user.setName("王五");
    modelAndView.addObject("user", user);
    return modelAndView;
}
// 第四种方式
@RequestMapping(value = "/modelandview4")
public ModelAndView modelandview4() {
    View view = new InternalResourceView("/show.jsp");
    ModelAndView modelAndView = new ModelAndView(view);
    User user = new User();
    user.setId(3);
    user.setName("王五");
    modelAndView.addObject("user", user);
    return modelAndView;
}
// 第五种方式
@RequestMapping(value = "/modelandview5")
public ModelAndView modelandview5() {
    HashMap<String, Object> map = new HashMap<>();
    User user = new User();
    user.setId(3);
    user.setName("王五");
    map.put("user", user);
    return new ModelAndView("show", map);
}
4)Servlet中的API

​ Spring MVC可以在业务方法种直接获取Servlet原生Web资源,只需要在方法定义时添加HttpServletRequest输入参数就可以,在方法体种直接使用request对象。

导入Servlet依赖

xml 复制代码
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
</dependency>
java 复制代码
@RequestMapping(value = "/servlet")
public String servlet(HttpServletRequest request) {
    User user = new User();
    user.setId(4);
    user.setName("赵六");
    request.setAttribute("user", user);
    return "show";
}
5)@ModelAttribute

Spring MVC还可以通过@ModelAttribute注解的方式添加业务数据,具体使用步骤如下:

  • 定义一个方法,这个方法用来填充到业务数据中的对象;
  • 给该方法添加@ModelAttribute注解,不是响应请求的业务方法;
  • @ModelAttribute注解的作用,将请求参数绑定到Model对象。被@ModelAttribute注释的方法会在Controller每个方法执行前被执行(如果在一个Controller映射到多个URL时,要谨慎使用)。

@ModelAttribute的作用是当Handler接收到一个客户端请求以后,不管调用哪一个业务方法,都会优先调用被@ModelAttribute注解修饰的方法,并且把其返回值作为业务数据,再到业务方法,此时业务方法只需要返回视图信息就可以了,不需要返回业务数据,即使返回业务数据,也会被@ModelAttribute注解修饰的方法返回的数据所覆盖

​ 域中的对象以key-value的形式存储,此时key默认值是业务数据所对应的类的类名首字母小写以后的结果

如果getUser没有返回值,则必须手动在该方法中填充业务数据,使用Map或者Model均可。

java 复制代码
@RequestMapping("/modelAttribute")
public String modelAttribute(){
    return "show";
}


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

@ModelAttribute
public void getUser(Model model){
    User user=new User();
    user.setId(2);
    user.setName("李四");
    model.addAttribute("user",user);
}

如果同时存在两个@ModelAttribute,那么直接给Model对象进行装载的方法优先级更高,返回的对象是这个优先级更高的方法返回的对象。

2. 业务数据绑定到session域对象

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%--  EL表达式  --%>
    ${sessionScope.user}
</body>
</html>
1)使用原生的Servlet API
java 复制代码
@RequestMapping(value = "/session")
public String session(HttpSession httpSession) {
    User user = new User();
    user.setId(7);
    user.setName("齐七");
    httpSession.setAttribute("user", user);
    return "show";
}
2)SessionAttribute

@SessionAttribute这个注解不是给方法添加的,而是给类添加的。只要给类添加了@SessionAttribute注解以后,不管类中的哪一个业务方法被访问,再把业务业务数据绑定到request域对象的同时,也会把业务数据绑定到session域中,前提是request域中的key值要和@SessionAttribute注解中的value一致。

java 复制代码
@Controller
@RequestMapping(value = "/resolver")
@SessionAttributes(value = "user")
// @SessionAttributes除了可以通过key值绑定,也可以通过业务数据的数据类型进行绑定
// @SessionAttributes(type=User.class)
// @SessionAttributes可以同时绑定多个业务数据
// @SessionAttributes(type={User.class,Address.class})
public class ViewResolverController {
    
}

三、Spring MVC 文件上传和下载

pom.xml

xml 复制代码
 <dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.8.0</version>
</dependency>
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.4</version>
</dependency>
<dependency>
  <groupId>org.apache.taglibs</groupId>
  <artifactId>taglibs-standard-impl</artifactId>
  <version>1.2.5</version>
</dependency>
<dependency>
  <groupId>org.apache.taglibs</groupId>
  <artifactId>taglibs-standard-spec</artifactId>
  <version>1.2.5</version>
</dependency>

springmvc.xml

xml 复制代码
<!--    文件的上传-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

JSP页面

web.xml

xml 复制代码
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
</servlet-mapping>

1. 单文件上传

JSP页面

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/file/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="img"/>
    <input type="submit" value="提交">
    <!-- 加上/代表从根目录也就是8080开始找 -->
</form>
<img src="${src}"/>
</body>
</html>

Handler

java 复制代码
@Component
@RequestMapping("/file")
public class FileHandler {
    /**
     * 文件是以二进制流传输的
     * @param img
     * @return
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("img") MultipartFile img, HttpServletRequest request){
        if (img.getSize()>0){
            String path = request.getSession().getServletContext().getRealPath("file");
            System.out.println(path);
            String filename = img.getOriginalFilename();
            File descFile = new File(path, filename);
            try {
                img.transferTo(descFile);
                request.setAttribute("src", "/file/" + filename);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "upload";
    }
}

2. 多文件上传

JSP页面

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="提交"/>
</form>
<c:forEach items="${pathList}" var="path">
<img src="${path}" width="300px">
</c:forEach>

Handler

java 复制代码
@Component
@RequestMapping("/file")
public class FileHandler {
    @PostMapping("/uploads")
    public String uploads(@RequestParam("imgs") MultipartFile[] imgs,HttpServletRequest request){
        List<String> pathList=new ArrayList<>();
        for (MultipartFile img:imgs){
            if (img.getSize()>0){
                String path = request.getSession().getServletContext().getRealPath("file");
                String filename = img.getOriginalFilename();
                File descFile=new File(path, filename);
                try {
                    img.transferTo(descFile);
                    pathList.add("/file/"+filename);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        request.setAttribute("pathList", pathList);
        return "uploads";
    }
}

3. 文件下载

JSP页面

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
        <a href="/file/download?fileName=av.jpg">av.jpg</a>
        <a href="/file/download?fileName=算了.png">算了.png</a>
        <a href="/file/download?fileName=一天.png">一天.png</a>
</body>
</html>

Handler

java 复制代码
@Component
@RequestMapping("/file")
public class FileHandler {
    /**
     * 根据文件的名字进行下载
     */
    @GetMapping("/download")
    public void download(String fileName,
                         HttpServletRequest request,
                         HttpServletResponse response) {
        if (fileName!=null){
            String path = request.getSession().getServletContext().getRealPath("file");
            File file=new File(path,fileName);
            OutputStream out=null;
            if (file.exists()){
                //设置下载文件
                response.setContentType("applicationContext/force-download");
                //设置文件名
                response.setHeader("Context-Disposition", "attachment;filename="+fileName);
                try {
                    out=response.getOutputStream();
                    out.write(FileUtils.readFileToByteArray(file));
                    out.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    if (out!=null){
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

    }
}

四、拦截器

1. 过滤器、监听器、拦截器的对比

1)过滤器 (Filter)

​ 过滤器 (Filter) 对Request请求起到过滤作用,作用在Servlet之前,如果配置为/*可以为所有的资源(servlet、js/css静态资源等)进行过滤处理。

2)监听器 (Listener)

​ 监听器 (Listener) 实现了javax.servlet.ServletContextListener接口的服务器端组件,它随Web应用的启动而启动,只初始化一次,然后一直监视,随Web应用的停止而销毁。它的作用有两个:

  • 做初始化工作,web应用中spring容器启动ContextLoaderListener;
  • 监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修改等可以在某些动作 前后增加处理,实现监控,比如说统计在线人数,利用HttpSessionListener等。
3)拦截器 (Interceptor)

​ 拦截器 (Interceptor) 是Spring MVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image等的访问,只会拦截访问的控制器方法(Handler)

  • servlet、filter、listener是配置在web.xml中,interceptor是配置在表现层框架自己的配置文件中;
  • 在Handler业务逻辑执行之前拦截一次;
  • 在Handler逻辑执行完但是还没有跳转页面之前拦截一次;
  • 在跳转页面后拦截一次。

2. 拦截器基本概念

​ Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。

要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义。

  • 通过实现HandlerInterceptor接口;
  • 继承HandlerInterceptor接口的实现类(如:HandlerInterceptorAdapter)来定义。

3. 拦截器的实现

实现HandlerInterceptor接口

java 复制代码
public class MyInterceptor implements HandlerInterceptor {
    /**
     * 在目标Handler(方法)执行前执行
     * 返回true:执行Handler方法
     * 返回false:阻止目标Handler方法执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("目标Handler执行前执行MyInterceptor---->preHandle方法...");
        return true;
    }

    /**
     * 在目标Handler(方法)执行后,视图生成前执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("目标Handler执行后,视图执行前执行MyInterceptor---->postHandle方法...");
    }

    /**
     * 在目标方法执行后,视图生成后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("目标Handler执行后,视图执行后执行MyInterceptor---->afterCompletion方法...");
    }
}

拦截器配置

xml 复制代码
<!-- 拦截器配置1 -->
<mvc:interceptors>
    <!--
        使用bean定义一个Interceptor
        直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求
        -->
    <bean class="com.shiftycat.interceptor.MyInterceptor"/>
</mvc:interceptors>

<!--  拦截器配置2 -->
<mvc:interceptors>
    <!--定义在mvc:interceptor下面,可以自定义需要拦截的请求
        如果有多个拦截器满足拦截处理的要求,则依据配置的先后顺序来执行
        -->
    <mvc:interceptor>
        <!--通过mvc:mapping配置需要拦截的资源。支持通配符,可以配置多个 -->
        <mvc:mapping path="/**"/> <!-- /**表示拦截所有的请求-->
        <!--通过mvc:exclude-mapping配置不需要拦截的资源。支持通配符,可以配置多个 -->
        <!--            <mvc:exclude-mapping path="/pojo/*"/> &lt;!&ndash; /hello/*表示放行hello路径下的请求 &ndash;&gt;-->
        <bean class="com.shiftycat.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

4. 多个拦截器的实现

​ Spring MVC框架支持多个拦截器的配置,从而构成拦截器链,对客户端进行多次拦截操作。

再配置一个拦截器

java 复制代码
public class MyInterceptor2 implements HandlerInterceptor {
    /**
     * 在目标Handler(方法)执行前执行
     * 返回true:执行Handler方法
     * 返回false:阻止目标Handler方法执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("目标Handler执行前执行MyInterceptor---->preHandle方法...");
        return true;
    }

    /**
     * 在目标Handler(方法)执行后,视图生成前执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("目标Handler执行后,视图执行前执行MyInterceptor---->postHandle方法...");
    }

    /**
     * 在目标方法执行后,视图生成后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("目标Handler执行后,视图执行后执行MyInterceptor---->afterCompletion方法...");
    }
}

更新配置文件

xml 复制代码
<mvc:interceptors>
    <!-- 拦截器配置 -->
    <!--
        使用bean定义一个Interceptor
        直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求
        -->
    <bean class="com.shiftycat.interceptor.MyInterceptor"/>
    <bean class="com.shiftycat.interceptor.MyInterceptor2"/>
</mvc:interceptors>

《Spring实战(第四版)》

不断前进的皮卡丘-Spring MVC详解(学习总结):https://blog.csdn.net/qq_52797170/article/details/125591705

相关推荐
xlsw_3 分钟前
java全栈day21--Web后端实战之利用Mybaits查询数据
java·开发语言
什么想法都无17 分钟前
stream
java·java stream
m0_7482336418 分钟前
WebService简介
java
love静思冥想19 分钟前
Stream `Collectors.toList()` 和 `Stream.toList()` 的区别(Java)
java·stream
Ch.yang37 分钟前
【Spring】 Bean 注入 HttpServletRequest 能保证线程安全的原理
java·spring·代理模式
web1508509664139 分钟前
基于Mysql、JavaScript、PHP、ajax开发的MBTI性格测试网站(前端+后端)
java
昙鱼1 小时前
springboot创建web项目
java·前端·spring boot·后端·spring·maven
eternal__day1 小时前
数据结构(哈希表(中)纯概念版)
java·数据结构·算法·哈希算法·推荐算法
天之涯上上1 小时前
JAVA开发 在 Spring Boot 中集成 Swagger
java·开发语言·spring boot
2402_857583491 小时前
“协同过滤技术实战”:网上书城系统的设计与实现
java·开发语言·vue.js·科技·mfc