Nice!终于有人把SpringMVC讲明白了【前端控制器、处理响应、转发与重定向、参数处理、文件上传下载】

举报
XiaoLin_Java 发表于 2022/01/31 17:43:44 2022/01/31
【摘要】 三、前端控制器 3.1、什么是前端控制器   在 MVC 框架中都存在一个前端控制器,在 WEB 应用的前端(Front)设置一个入口控制器(Controller),是用来提供一个集中的请求处理机制,所有的请求都被发往该控制器统一处理,然后把请求分发给各自相应的处理程序。一般用来做一个共同的处理,如权限检查,授权,日志记录等。因为前端控制的集中处理请求的能力,因此提高了可重用性和可拓展性。...

三、前端控制器

3.1、什么是前端控制器

   在 MVC 框架中都存在一个前端控制器,在 WEB 应用的前端(Front)设置一个入口控制器(Controller),是用来提供一个集中的请求处理机制,所有的请求都被发往该控制器统一处理,然后把请求分发给各自相应的处理程序。一般用来做一个共同的处理,如权限检查,授权,日志记录等。因为前端控制的集中处理请求的能力,因此提高了可重用性和可拓展性。

    在没有前端控制器的时候,我们是这样传递和处理请求的。

image-20210303204840516

    有了前端控制器之后,我们变成了这样。

image-20210303205548577

3.2、代码实现

    Spring MVC 已经提供了一个 DispatcherServlet 类作为前端控制器,所以要使用 Spring MVC 必须在web.xml 中配置前端控制器。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">
  <!-- Spring MVC 前端控制器-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-
      class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 指定 Spring 容器启动加载的配置文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:mvc.xml</param-value>
    </init-param>
    <!-- Tomcat 启动初始化 -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

3.3、注意

    load-on-startup 元素是可选的:若值为 0 或者大于 0 时,表示容器在应用启动时就构建 Servlet 并调用其 init 方法做初始化操作(非负数的值越小,启动该 Servlet 的优先级越高);若值为一个负数时或者没有指定时,则在第一次请求该 Servlet 才加载。配置的话,就可以让 SpringMVC 初始化的工作在容器启动的时候完成,而不是丢给用户请求去完成,提高用户访问的体验性。

3.4、映射路径

    配置前端控制器的映射路径一般有以下的三种形式:

  1. 配置如 .do、.htm 是最传统方式,可以访问静态文件(图片、 JS、 CSS 等),但不支持 RESTful风格。
  2. 配置成 /,可以支持流行的 RESTful 风格,但会导致静态文件(图片、 JS、 CSS 等)被拦截后不能访问。
  3. 配置成 /*,是错误的方式,可以请求到 Controller 中,但跳转到调转到 JSP 时被拦截,不能渲染JSP 视图,也会导致静资源访问不了。

3.4.1、访问静态资源和 JSP 被拦截的原因

    Tomcat 容器处理静态资源是交由内置 DefaultServlet 来处理的(拦截路径是 /),处理 JSP 资源是交由内置的 JspServlet 处理的(拦截路径是*.jsp | *.jspx)。
    启动项目时,先加载容器的 web.xml,而后加载项目中的 web.xml。当拦截路径在两者文件中配置的一样,后面会覆盖掉前者。
    所以前端控制器配置拦截路径是 / 的所有静态资源都会交由前端控制器处理,而拦截路径配置 /*,所有静态资源和 JSP 都会交由前端控制器处理。

3.4.2、如何解决

3.4.2.1、方式一

    在 web.xml 中修改,修改前端控制器的映射路径修改为*.do但注意,访问控制器里的处理方法时,请求路径须携带 .do。

<servlet-mapping>
	<servlet-name>dispatcherServlet</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>	

3.4.2.2、方式二

   在 mvc.xml中加入一段配置,这个配置会在 Spring MVC 上下文中创建存入一个 DefaultServletHttpRequestHandler 的 bean,它会
对进入DispatcherServlet的请求进行筛查,若不是映射的请求,就将该请求交由容器默认的 Servlet处理。

<mvc:default-servlet-handler/>

3.5、@ModelAttribute 注解

    在形参中的对象(必须是自定义类型),SpringMVC会默认将他存入Model中,名称是参数的类名首字母小写,有些时候,这个类会显得格外长,但是我们又有这种需求,比方说:查询条件的回显。我们只需在自定义类的前面加@ModelAttribute,里面写我们需要修改的key的名称即可。

package cn.wolfcode.web.controller;
	@Controller
	public class RequestController {
		@RequestMapping("/req7")
			public String resp7(@ModelAttribute("u") User user) {
			return "m";
		}
}

四、处理响应

    SpringMVC的作用是请求和处理响应,响应处理是指怎么编写控制器里面的处理方法接受请求做响应,找视图文件和往作用域中存入数据。要处理方法要做响应,一般处理方法返回的类型为 ModelAndView 和 String。

4.1、返回 ModelAndView

方法中返回 ModelAndView 对象,此对象中设置模型数据并指定视图。前端依旧是使用JSTL+CgLib来进行取值。他有两个常用方法:

  1. addObject(String key, Object value):设置共享数据的 key 和 value。
  2. addObject(Object value):设置共享数据的 value,key 为该 value 类型首字母小写。
package cn.linstudy.web.controller;

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

@Controller
public class ResponseController {
  // 提供方法处理请求,localhost/resp1
  @RequestMapping("/resp1")
  public ModelAndView resp1() {
// 通过创建这个类对象,告诉 Spring MVC 找什么视图文件, 往作用域或者说往模型中存入什么数据
    ModelAndView mv = new ModelAndView();
// 往作用域或者模型中存入数据
    mv.addObject("msg", "方法返回类型是 ModelAndView");
// 找视图
    mv.setViewName("/WEB-INF/views/resp.jsp");
    return mv;
  }

4.2、返回String

    返回 String 类型(使用广泛),此时如果我们需要共享数据,那么就需要用到HttpServlet对象,Spring帮我们封装好了一个对象:Model 。组合使用,用其往作用域或模型中存入数据。

package cn.instudy.web.controller;

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

@Controller
public class ResponseController {
  // 提供方法处理请求,localhost/resp2
  @RequestMapping("/resp2")
  public String resp2(Model model) {
// 往作用域或者模型中存入数据
    model.addAttribute("msg", "方法返回类型是 String");
// 返回视图名
    return "/WEB-INF/views/resp.jsp";
  }
}

4.3、改进

    我们会发现,如果我们需要写返回界面的话需要不断的写前缀和后缀,这个时候需要进行消除消除视图前缀和后缀,我们只需在Spring中进行配置视图解析器即可。

<!--
配置视图解析器 配置这个Spring MVC 找视图的路径就是:前缀 + 逻辑视图名(处理方法设置或返回视图名)+ 后缀名
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<!-- 视图前缀 -->
	<property name="prefix" value="/WEB-INF/views/"/>
	<!-- 视图后缀 -->
	<property name="suffix" value=".jsp"/>
</bean>

五、请求转发和重定向

5.1、请求转发和重定向的区别

几次请求 地址栏 WEB-INF中资源 共享请求数据 有无表单重复提交
请求转发 1次 不改变 可以访问 可以共享
重定向 多次 改变 不可访问 不可共享

5.2、请求转发

    加上forward 关键字,表示请求转发,相当于request.getRequestDispatcher().forward(request,response),转发后浏览器地址栏不变,共享之前请求中的数据。加了关键字后,配置的视图解析器就不起作用了。如果返回视图必须写全路径

package cn.linstudy.web.controller;
@Controller
public class ResponseController {

	@RequestMapping("/TestForward")
	public String forward() {
		return "forward:/WEB-INF/views/welcome.jsp";
	}
}

5.3、重定向

    加上 redirect 关键字,表示重定向,相当于 response.sendRedirect(),重定向后浏览器地址栏变为重定向后的地址,不共享之前请求的数据。

package cn.linstudy.web.controller;
@Controller
public class ResponseController {
	// localhost/r
	@RequestMapping("/TestRedirect")
	public String redirect() {
		return "redirect:/static/demo.html";
	}
}

5.4、请求路径

    在请求转发和重定向的时候,我们一般有两种方式来写请求路径:

  1. /:使用是绝对路径(推荐使用),从项目根路径找。(/response/test6 —> “redirect:/hello.html” —> localhost:/hello.html)
  2. 不加/:使用是相对路径,相对于上一次访问上下文路径的上一级找。(/response/test6 —> “redirect:hello.html” —> localhost:/response/hello.html)

六、参数处理

6.1、处理简单类型的请求参数

    我们在控制器的如何获取请求中的简单数据类型的参数参数?简单数据类型包含基本数据类型及其包装类、String 和BigDecimal 等形参接收。

6.1.1、请求参数名和控制器方法参数列表形参同名

    如果前台传递过来的参数名和控制器方法中参数列表的形参参数名相同就无需做任何操作,SpringMVC会自动帮我们进赋值。

 // 请求路径为:/req1?username=zs&age=18
package cn.linstudy.web.controller;
	@Controller
	public class RequestController {
		@RequestMapping("/req1")
		public ModelAndView resp1(String username, int age) {
			System.out.println(username);
			System.out.println(age);
			return null;
		}
}

6.1.2、请求参数名和控制器方法参数列表形参不同名

    如果前台传递过来的参数名和控制器方法中参数列表的形参参数名不相同的话,我们需要使用一个注解@RequestParam("前台携带的参数名")来告诉SpringMVC我们任何对数据来进行赋值。

 // 请求路径为:/req1?username=zs&age=18
package cn.linstudy.web.controller;
	@Controller
	public class RequestController {
		@RequestMapping("/req1")
		public ModelAndView resp1(@RequestParam("username") String username1, @RequestParam("age") int age1) {
			System.out.println(username);
			System.out.println(age);
			return null;
		}
}

6.2、处理复杂类型的请求参数

6.2.1、数组类型

    对于数组类型参数,我们只需在方法参数的形参列表中定义一个同名的数组类型进行接收即可。

// 请求路径 /req3?ids=1&ids=2&ids=3
package cn.linstudy.web.controller;
	@Controller
	public class RequestController {
		@RequestMapping("/req3")
		public ModelAndView resp3(Long[] ids) {
			System.out.println(Arrays.toString(ids));
			return null;
		}
}

6.2.2、自定义类型

    我们在很多的时候,需要接收的是一个自定义类型的对象。比如说我们进行保存用户,需要将前台传递的数据进行封装成一个自定义的用户类型,那么这个时候,只需要保证自定义的类型里面的字段和前端传过来的字段相同(注意传递参数名与封装对象的属性名一致),SpringMVC即可自动进行封装。

// /req4?username=hehe&password=666
package cn.linstudy.web.controller;	
	@Controller
	public class RequestController {
		@RequestMapping("/req4")
		public ModelAndView resp4(User user) {
			System.out.println(user);
			return null
		}
}

    底层 Spring MVC 根据请求地址对应调用处理方法,调用方法时发现要传递 User 类型的实参,SpringMVC 会反射创建 User 对象,之后通过请求参数名找对应的属性,给对象的属性设置对应的参数值。

6.3、处理日期类型的请求参数

6.3.1、日期在请求参数上

    如果日期在请求参数上,那么我们需要在处理方法的 Date 类型的形参贴上 @DateTimeFormat注解。

package cn.linstudy.controller;
	@Controller
	public class RequestController {
		@RequestMapping("/req5")
		// 注意形参的类型为 java.util.Date
		public ModelAndView resp5(@DateTimeFormat(pattern="yyyy-MM-dd")Date date) {
			System.out.println(date.toLocaleString());
			return null;
		}
}

6.3.2、在封装的对象上

    如果日期在封装对象的字段,那么我们需要在字段的上贴@DateTimeFormat注解。

package cn.linstudy.domain;
	public class User {
	private Long id;
	private String Username;
	private String password;
	// 增加下面这个字段,并贴注解
	@DateTimeFormat(pattern="yyyy-MM-dd")
	private Date date;
	// 省略 setter getter toString
}
	package cn.linstudy.controller;
	@Controller
	public class RequestController {
		@RequestMapping("/req6")
		public ModelAndView resp6(User user) {
			System.out.println(user);
			return null;
		}
}

七、文件上传与下载

7.1、文件上传

    回顾之前使用 Servlet3.0 来解决文件上传的问题,编写上传表单(POST、multipart/form-data),还在处理方法 doPost 中编写解析上传文件的代码。但是在SpringMVC是可以帮我们简化文件上传的步骤和代码。

7.1.1、编写表单

    注意请求数据类型必须是:multipart/form-data,且请求方式是POST。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
	<form action="/upload" method="POST" enctype="multipart/form-data">
		文件:<input type="file" name="pic"><br>
		<input type="submit" value="提交"/>
	</form>
</body>
</html>

7.1.2、修改web.xml

    我们可以在web.xml中指定上传文件的大小。

<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:mvc.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
	<multipart-config>
		<max-file-size>52428800</max-file-size>
		<max-request-size>52428800</max-request-size>
	</multipart-config>
</servlet>
<servlet-mapping>
	<servlet-name>dispatcherServlet</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>

7.1.3、配置上传解析器

    在mvc.xml中配置上传解析器,使用springmvc中multipartfile接收客户端上传的文件必须配置文件上传解析器且解析的id必须为multipartResolver

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <!--控制文件上传大小单位字节 默认没有大小限制 这里是2-->
  <property name="maxUploadSize" value="2097152"/>
</bean>

7.1.4、配置上传控制器

package cn.linstudy.controller;
	@Controller
	public class UploadController {
	// Spring 容器存在 ServletContext 类型的对象,所以定义好 ServletContext 类型字段贴@Autowired 注解即可获取到
	@Autowired
	private ServletContext servletContext;
	@RequestMapping("/upload")
	public ModelAndView upload(Part pic) throws Exception {
		System.out.println(pic.getContentType()); // 文件类型
		System.out.println(pic.getName()); // 文件参数名
		System.out.println(pic.getSize()); // 文件大小
		System.out.println(pic.getInputStream()); // 文件输入流
		// FileCopyUtils.copy(in, out),一个 Spring 提供的拷贝方法
		// 获取项目 webapp 目录下 uploadDir 目录的绝对路径
		System.out.println(servletContext.getRealPath("/uploadDir"));
		return null;
	}
}

7.2、文件下载

    文件下载:将服务器上的文件下载到当前用户访问的计算机的过程称之为文件下载

7.2.1、开发控制器

    下载时必须设置响应的头信息,指定文件以何种方式保存,另外下载文件的控制器不能存在返回值,代表响应只用来下载文件信息`

	/**
     * 测试文件下载
     * @param fileName 要下载文件名
     * @return
     */
    @RequestMapping("download")
    public String download(String fileName, HttpServletRequest request, HttpServletResponse response) throws IOException {
        //获取下载服务器上文件的绝对路径
        String realPath = request.getSession().getServletContext().getRealPath("/down");
        //根据文件名获取服务上指定文件
        FileInputStream is = new FileInputStream(new File(realPath, fileName));
        //获取响应对象设置响应头信息
        response.setHeader("content-disposition","attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
        ServletOutputStream os = response.getOutputStream();
        IOUtils.copy(is,os);
        IOUtils.closeQuietly(is);
        IOUtils.closeQuietly(os);
        return null;
    }
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。