1、SpringMVC概述
概念
SpringMVC也叫Spring web mvc。是Spring内置的一个MVC框架, 在Spring3.0后发布。SpringMVC框架解决了WEB开发中常见的问题(参数接收、文件上传、表单验证等等),而且使用简单,与Spring无缝集成。支持RESTful风格的URL请求。采用了松散耦合可插拔组件结构,比其他MVC框架更具扩展性和灵活性。
原理
在没有使用SpringMVC之前我们都是使用Servlet在做Web开发。但是使用Servlet开发在接收请求参数,数据共享,页面跳转等操作相对比较复杂。servlet是java进行web开发的标准,既然springMVC是对servlet的封装, 那么很显然SpringMVC底层就是Servlet, SpringMVC就是对Servlet进行深层次的封装。
优势
1、基于MVC架构,功能分工明确。解决页面代码和后台代码的分离。
2、简单易用。SpringMVC 也是轻量级的,jar 很小。不依赖的特定的接口和类就可以开发一个注解的 SpringMVC项目。
3、作为Spring框架一部分,能够使用Spring的IoC和Aop.方便整合MyBatis,HiberateJPA等其他框架。
4、springMVC的注解强大易用。
2、传统的MVC模式
模型1: jsp+javabean模型--在jsp页面中嵌入大量的java代码
模型2: jsp+servlet+javabean模型--jsp页面将请求发送给servlet,由servlet调用javabean, 再由serVlet将制定jsp页面响应给用户。
模型2一般就是现在的MVC模式,也是我们一直使用的。
-
Model-View-Controller:模型——视图——控制器
-
Model:模型层javaBean负责数据访问和业务处理dao service pojo
-
View:视图 JSP技术 负责收集和展示数据
-
Controller:控制器servlet技术 中间调度
-
控制器的工作:
1、接受客户端的请求(包括请求中携带的数据)
2、处理请求:调用后台的模型层中的业务逻辑
3、页面导航:处理完毕给出响应: JSP页面
3、编写SpringMVC程序
创建maven项目
添加web项目module
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope><!--由于tomcat中提供了servlet,这里的servlet打包的时候不需要打包进去,所以用添加scope说明-->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8080</port>
</configuration>
</plugin>
</plugins>
</build>
在main/reources下添加spring的配置文件:applicationContext.xml和springmvc的配置文件:springmvc.xml
<!--配置文件中的配置都是需要添加的,自动生成的这里没有写-->
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--spring的配置文件:除了控制器之外的bean对象都在这里扫描-->
<context:component-scan base-package="com.zx.dao,com.zx.service"/>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--springmvc的配置文件:控制器的bean对象都在这里扫描-->
<context:component-scan base-package="com.zx.controller"/>
<!--注解驱动:此时程序就知道了@controller标注的类是可以用于处理用户请求的-->
<mvc:annotation-driven/>
<!-- annotation-driven是一种简写形式,也可以手动配置替代这种简写形式,简写形式可以让初学者快速应用默认配置方案。
该注解会自动注册DefaultArhotafionHandlerMapping与AnnotationMethodHandlerAdapter两个bean,是springMVC为@Controller分发用户请求所必须的,解决了@Controller注解使用的前提配置。
同时它还提供了:数据绑定支持,@NumberFormatannotation支持,DateTimeFormat支持,@Valid支持,读写XML的支持(JAXB,读写JSON的支持(Jackson)。我们
处理响应ajax请求时,就使用到了对json的支持(配置之后,在加入了jackson的core和mapper包之后,不写配置文件也能自动转换成json)。
-->
<!--视图解析器-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
配只web.xml文件,在web,xml中添加如下配置:
<!--开始配置:当web服务器启动时:创建下面配置的spring容器和springmvc容器-->
<!--1、spring配置-->
<!--上下文(spring)配置文件的位置配置:如果这里不写,监听器默认会去找:WEB-INF下的名为applicationContext.xml的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--2、springmvc配置:配置springmvc的核心(/前端/中央)控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定一下读取配置文件的路径,因为这里默认读取的位置也不是我们设置的位置,默认查找名为:"[servlet-name]-servlet.xml"的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--bean对象的创建是否在容器启动时,1表示启动时创建,缺省默认什么时候用才创建-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
编写测试页面和代码
@Service
public class TeamService {
public void add(){
System.out.println("TeamService--------add---------");
}
}
@Controller
public class TeamController {
@Autowired
private TeamService teamService;
@RequestMapping("/hello.do")
public ModelAndView add(){
System.out.println("TeamController--------add---------");
teamService.add();
ModelAndView mv = new ModelAndView();
mv.addObject("teamName","湖人");//相当于request.setAttribute("","");
//下面的代码表示转到index.jsp页面,传入的字符串"index"的前缀和后缀的封装由springmvc.xml中的bean对象:internalResourceViewResolver完成
mv.setViewName("index");//经过springmvc视图解析器处理转换为物理路径,相当于request.getRequestDispatcher("index.jsp").forward();
//上面的经过InternalResourceViewResolver对象处理之后加上前后缀就变成了/jsp/index.jsp
return mv;
}
}
webapp下创建jsp文件夹,下创建index.jsp页面
页面内容:
<body>
<h1>index---------${teamName}</h1>
</body>
此时,右侧maven-Plugins-tomcat7-tomcat7-run启动tomcat,然后浏览器访问localhost:8080/hello.do,页面跳转到显示index.jsp中设置的内容,并显示了controller传回的数据,说明程序已经跑通了
总结(解析)
当Spring和SpringMVC同时出现,我们的项目中将存在两个容器,一个是Spring容器,另一个是SpringMVC容器,Sprng容器通过ContextLoaderListener来加载,SpringMVC 容器则通过DispatcherServlet来加载,这两个容器不-样:
如图所示:
- ContextLoaderListener初始化的上下文加载的Bean是对于整个应用程序共享的,不管是使用什么表现层技术,一般如dao层、service层 的bean;
- DispatcherServlet初始化的上下文加载的bean是只对Spring Web MVC有效的bean,如Controller、HandlerMapping、HandlerAdapter 等等,该初始化上下文应该只加载Web相关组件。
Spring容器中为什么不能扫描所有的bean
1. Spring容器中不能扫描所有Bean嘛?
不可以。当用户发送的请求达到服务端后,会寻找前端控制器DispatcherServlet去处理,只在SpringMVC容器中找,所以Controller必须在SpringMVC容器中扫描。
2. SpringMVC容器中可以扫描所有Bean嘛?
可以。可以在SpringMVC容器中扫描所有Bean.但是实际开发中一般不会这么做,原因如下:
(1)为了方便配置文件的管理
(2)未来在Spring+SpringMVC+Mybatis组合中,要写的配置内容很多,一般都会根据功能分开编写
4、SpringMVC工作流程
工作流程分析
(1)用户通过浏览器发送请求到前端控制器DispatcherServlet.
(2)前端控制器直接将请求转给处理器映射器HandleMapping.
(3)处理器映射器HandleMapping会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链HandlerExecutionChina后返回给前端控制器DispatcherServlet.
(4)前端控制器DispatcherServlet根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器HandlerAdaptor.
(5)处理器适配器HandlerAdaptor调用执行处理器Controller.
(6)处理器Controller将处理结果及要跳转的视图封装到一个对象 ModelAndView中,并将其返回给处理器适配器HandlerAdaptor.
(7)处理器适配麓器直接将结果返回给前端控制器DispatcherServlet,
(8)前端控制器调用视图解析器,将ModelAndView中的视图名称封装为视图对象。
(9)视图解析器ViewResolver将封装了的视图View对象返回给前端控制器DispatcherServlet.
(10)前端控制器DispatcherServlet调用视图对象, 让其自己进行渲染,即进行数据填充,形成响应对象。
(11)前端控制器响应浏览器。
SpringMVC组件
1.DispatcherServlet:前端控制器也称为中央控制器或者核心控制器。
用户请求的入口控制器,它就相当于mvc模式中的C,DispatcherServlet 是整个流程控制的中心,相当于是SpringMVC的大脑,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。SpringMVC框架提供的该核心控制器需要我们在web.xml文件中配置。
2.HandlerMapping:处理器映射器
HandlerMapping也是控制器,派发请求的控制器。我们不需要自己控制该类,但是他是springmv运转历程中的重要的一个控制器。HandlerMapping负责根据用户请求找到Handler即处理器(也就是我们所说的Controller), SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等,在实际开发中,我们常用的方式是注解方式。
3.Handler:处理器
Handler是继DispatcherServlet 前端控制器的后端控制器,在DispatcherServlet 的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以-般情况需要程序员根据业务需求开发Handler, (这里所说的 Handler就是指我们的Controller)
4.HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展处理器适配器,支持更多类型的处理腊,调用处理器传递参数等工作。
5.ViewResolver:视图解析器
ViewResolver负责将处理结果生成View视图,ViewResolver 首先根据逻辑视图名解析成物理视图名称,即具体的页面地址,再生成View视图对象,最后对
View进行渲染将处理结果通过页面展示给用户。SpringMVC 框架提供了很多的View视图类型,包括: jstView. freemarkerView. pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
5、@RequestMapping注解
@RequestMapping作用的位置
@RequestMapping注解定义了处理器对于请求的映射规则。该注解可以定义在类上,也可以定义在方法上,但是含义不同。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
一个@Controller所注解的类中。可以定义多个处理器方法。当然,不同的处理器方法所匹配的URI是不同的。这些不同的URI被指定在注解于方法之上的@RequestMapping的value属性中。但若这些请求具有相同的URI部分,则这些相同的URI,可以被抽取到注解在类之上的RequestMapping的value属性中。此时的这个URI表示模块的名称。URI的请求是相对于Web的根目录。在类的级别上的注解会将一个特定请求或者请求横式映射到个控制器之 上。之后你还可以另外添加方法级别的注解来进一步指定到处理方法的映射关系。
示例:
在jsp包里添加一个team包,然后在team包里创建add.jsp和update.jsp两个测试页面,内容随便填。
TeamController修改如下:
//当@RequestMapping注解在类上时,此类下的所有方法中@RequestMapping注解的value请求地址都要在前面拼接上此处注解的value值
@RequestMapping("/team")
@Controller
public class TeamController {
@Autowired
private TeamService teamService;
@RequestMapping("/hello.do")//此时这里就会报错了,因为访问的地址将会是team/hello.do
public ModelAndView hello(){
--------
}
//浏览器请求地址localhost:8080/team/add.do,进入此方法
@RequestMapping(value = "/add.do",method = RequestMethod.GET)//只支持使用get方式提交
public ModelAndView addTeam(){
System.out.println("TeamController-------------add----------");
ModelAndView mv = new ModelAndView();
mv.setViewName("team/add");//跳转到jsp/team/add.jsp页面
return mv;
}
//浏览器请求地址localhost:8080/team/add.do,无法进入此方法,因为不支持post请求
@RequestMapping(value = "/update.do",method = RequestMethod.POST)//只支持使用post方式提交
public ModelAndView updateTeam(){
System.out.println("TeamController-------------update----------");
ModelAndView mv = new ModelAndView();
mv.setViewName("team/update");//跳转到jsp/team/uodate.jsp页面
return mv;
}
}
要验证一下method属性,只支持post和get请求,index.jsp下提交两个请求
<form action="/team/add.do" method="get">
<button type="submit">get请求/team/add.do</button>
</form>
<form action="/team/update.do" method="post">
<button type="submit">post请求/team/update.do</button>
</form>
以下表格列出了常用的提交方式:
补充:url-pattern解析
在web.xml配置SpringMVC的前端控制器时有这个节点这个节点中的值一般有两种写法:
1、*.do
在没有特殊要求的情况下,SpringMVC的前端控制器DispatcherServlet的常使用后辍匹配方式,可以写为*.do或者*.action, *.mvc 等。
2、/
可以写为/,但是DispatcherServlet会将向静态内容--例如.Css、.js、图片等资源的获取请求时,也会当作是一个普通的Controller请求。 前端控制器会调用处理器映射器为其查找相应的处理器。肯定找不到啊,所以所有的静态资源获职请求也均会报404错误。
案例:在index.jsp页面添加一张图片,如果节点中的值为* .do,图片可以正常访问,但是如果为/就不能访问。
1、项目中添加图片,同时修改index.jsp页面
添加标签:
<img src="图片位置" alt="图片简介"/>
2、修改web.xml
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!--修改为/-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
此时访问页面index.jsp,图片就无法显示
如何访问呢:
静态资源访问配置
如果的值配为/后,静态资源可以通过以下两种方法解决:
使用<mvc:default-servlet-handler/ >
在springmvc的配置文件中添加如下内容:
<mvc:default-servlet-handler/>
<!--声明了<mvc:default-servlet-handler /> 后, springmvc框架会在容器中创建DefaultServletHttpRequestHandler处理器对象。该对象会对所有进入DispatcherServlet的URL进行检查。如果发现是静态资源的请求,就将该请求转由web应用服务器默认的Servlet处理。
一般的服务器都有默认的 Servlet。 例如咱们使用的Tomcat服务器中,有一个专门用于处理静态资源访问的Servlet 名叫DefaultServlet. 其<servlet-name/>为default.可以处理各种静态资源访问请求。该Servlet注册在Tomcat 服务器的web.xml中。 在Tomcat安装目录/conf/web.xml. -->
便用<mvc:resources/ >
在springmvc的配置文件中添加如下内容:
<mvc:resources location="/images/" mapping="/images/**" />
<!--
location:表示静态资源所在目录。当然,目录不要使用/WEB-INF/及其子目录。
mapping:表示对该资源的请求。注意,后面是两个星号**. -->
在Spring3.0版本后,Spring 定义了专门用于处理静态资源访问请求的处理器ResourceHttpRequestHandler.并且添加了<mvc:resources/ >标签,专门用于解决静态资源无法访问的问题。
6、处理器方法的参数
处理器方法可以包含以下四类参数,这些参数会在系统调用的时候由系统自动赋值,所以我们可以在方法内直接使用,以下是这四类参数:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- 请求中所携带的请求参数
(1)在Controller使用方法直接接受前端的参数
要求:方法的参数名必须与用户请求传来的参数名一样
案例:
编写两个页面
页面1:webapp/jsp/hello.jsp
内容:向后端发送请求,携带参数
<h1>1、直接使用方法的参数逐个接收</h1>
<form action="/param/test01" method="post">
球队id:<input type="text" name="teamID"></br>
球队名称:<input type="text" name="teamName"></br>
球队位置:<input type="text" name="teamLocation"></br>
<button type="submit">提交</button>
</form>
页面2:webapp/jsp/ok.jsp
内容:controll处理完毕之后跳转的页面
<h1>你的操作成功跳转到了ok页面</h1>
@Controller
@RequestMapping("/param")
public class ParamController {
/**
* 1、直接使用方法,接收前端传来的参数
* 限制:方法的参数名必须与用户请求传来的参数名一样
* 好处:不需要类型转换
* @param teamID
* @param teamName
* @param teamLocation
* @return
*/
@RequestMapping("/test01")
public ModelAndView test01(int teamID,String teamName,String teamLocation){
//System.out.println("------------------------------");
//System.out.println("-------test01:直接使用方法来接受前端的参数------");
ModelAndView mv = new ModelAndView("ok");//跳转到ok.jsp
System.out.println(teamID);
System.out.println(teamName);
System.out.println(teamLocation);
//System.out.println("\n\n\n");
return mv;
}
}
(2)使用对象来接收多个参数
先创建个对象Team对象,三个属性:teamID,tName,tLocation。
还是使用(1)中的页面和类,注意,页面中的参数名要与Team类中的属性名一致,所以这里要改一下页面的参数名
<h1>2、使用对象来接收多个参数</h1>
<form action="/param/test02" method="post">
球队id:<input type="text" name="teamID"></br>
球队名称:<input type="text" name="tName"></br>
球队位置:<input type="text" name="tLocation"></br>
<button type="submit">提交</button>
</form>
/**
* 2、使用对象来接收多个参数
* 要求:用户请求中携带的参数名称必须与实体类中属性名称保持一致,否则获取不到,
* @param team
* @return
*/
@RequestMapping("/test02")
public ModelAndView test02(Team team){
//System.out.println("------------------------------");
//System.out.println("-------test02:使用对象来接收多个参数------");
ModelAndView mv = new ModelAndView("ok");
System.out.println(team);
//System.out.println("\n\n\n");
return mv;
}
(3)请求携带的参数和方法参数名不一致
<h1>3、请求携带的参数和方法参数名不一致,通过注解校正</h1>
<form action="/param/test03" method="post">
球队id:<input type="text" name="teamID"></br>
球队名称:<input type="text" name="teamName"></br>
球队位置:<input type="text" name="teamLocation"></br>
<button type="submit">提交</button>
</form>
/**
* 3、请求携带的参数和方法参数名不一致,通过注解校正
* 要求:注解的value值必须与用户请求携带的参数一致,如果不一致,会报错400
* 要解决报错问题,将required属性设置为false(默认为true),表示该参数非必须要赋值的,此时系统会将其设置为null
* @param teamID
* @param name
* @param location
* @return
*/
@RequestMapping("/test03")
public ModelAndView test03(@RequestParam(value = "teamID",required = false) int teamID,
@RequestParam(value = "teamName",required = true)String name,
@RequestParam(value = "teamLocation",required = true)String location){
ModelAndView mv = new ModelAndView("ok");
System.out.println(teamID);
System.out.println(name);
System.out.println(location);
//System.out.println("\n\n\n");
return mv;
}
(4)使用HttpServletRequest 对象获取参数(原生方式)
<h1>4、使用HttpServletRequest 对象获取参数(原生方式)</h1>
<form action="/param/test04" method="post">
球队id:<input type="text" name="teamID"></br>
球队名称:<input type="text" name="teamName"></br>
球队位置:<input type="text" name="teamLocation"></br>
<button type="submit">提交</button>
</form>
/**
* 4、使用HttpServletRequest 对象获取参数(原生方式)
* @param request
* @return
*/
@RequestMapping("/test04")
public ModelAndView test04(HttpServletRequest request){
ModelAndView mv = new ModelAndView("ok");
String teamID = request.getParameter("teamID");
String teamName = request.getParameter("teamName");
String teamLocation = request.getParameter("teamLocation");
if(teamID!=null ||teamName!=null || teamLocation!=null )
{
System.out.println(Integer.valueOf(teamID));
System.out.println(teamName);
System.out.println(teamLocation);
}
System.out.println("\n\n\n");
return mv;
}
(5)使用URL地址栏传参
<h1>5、直接使用URL地址传参</h1>
/**
* 5、直接使用URL地址传参,借助@PathVariable注解
* 例如:http://localhost:8080/param/test05/1001/aaaaa/ccccccccc
* @param teamID
* @param name
* @param location
* @return
*/
@RequestMapping("/test05/{teamID}/{teamName}/{teamLocation}")
public ModelAndView test05(@PathVariable("teamID") int teamID,
@PathVariable( "teamName")String name,
@PathVariable("teamLocation")String location){
ModelAndView mv = new ModelAndView("ok");
System.out.println(teamID);
System.out.println(name);
System.out.println(location);
System.out.println("\n\n\n");
return mv;
}
(6)获取日期类型参数
<h1>6、日期类型参数传递</h1>
<form action="/param/test06" method="post">
球队id:<input type="text" name="teamID"></br>
球队名称:<input type="text" name="teamName"></br>
球队位置:<input type="text" name="teamLocation"></br>
创建日期:<input type="text" name="createTime"></br>
<button type="submit">提交</button>
</form>
首先在Team上添加属性Date类型的createTime。然后使用注解标注
//参数为日期格式化字符串,需要在web页面中按照这种格式填写
@DateTimeFormat(pattern = "yyyy-MM-dd")
Date createTime;
/**
* 6、获取日期类型参数,跟2一样,使用对象属性接受,只需要在对象属性上添加 @DateTimeFormat(pattern = "yyyy-MM-dd")
* @param team
* @return
*/
@RequestMapping("/test06")
public ModelAndView test06(Team team){
ModelAndView mv = new ModelAndView("ok");
System.out.println(team);
System.out.println("\n\n\n");
return mv;
}
(7)数组类型参数传递
<h1>7、数组类型参数传递,这里传三个球队名称</h1>
<form action="/param/test07" method="post">
球队名称1:<input type="text" name="teamName"></br>
球队名称2:<input type="text" name="teamName"></br>
球队名称3:<input type="text" name="teamName"></br>
<button type="submit">提交</button>
</form>
/**
* 获取数组类型
* @param teamName
* @return
*/
@RequestMapping("/test07")
public ModelAndView test07(String [] teamName,HttpServletRequest request){
ModelAndView mv = new ModelAndView("ok");
//方式1
for(String s:teamName){
System.out.println(s);
}
System.out.println("--------------");
//方式2
String[] teamNames = request.getParameterValues("teamName");
for(String name:teamNames){
System.out.println(name);
}
return mv;
}
(8)集合类型参数传递
集合类型-简单类型
<h1>8、集合类型-简单类型</h1>
<form action="/param/test08" method="post">
球队名称1:<input type="text" name="teamName"></br>
球队名称2:<input type="text" name="teamName"></br>
球队名称3:<input type="text" name="teamName"></br>
<button type="submit">提交</button>
</form>
/**
* 获取集合类型参数:简单类型可以通过@RequestParam注解,使用String类型的集合来接受
* @param nameList
* @return
*/
@RequestMapping("/test08")
public ModelAndView test08(@RequestParam("teamName") List<String> nameList){
ModelAndView mv = new ModelAndView("ok");
for(String s:nameList){
System.out.println(s);
}
return mv;
}
集合类型-对象
<h1>9、集合类型-对象</h1>
<form action="/param/test09" method="post">
球队id1:<input type="text" name="teamList[0].teamID"></br>
球队id2:<input type="text" name="teamList[1].teamID"></br>
球队id3:<input type="text" name="teamList[2].teamID"></br>
球队名称1:<input type="text" name="teamList[0].tName"></br>
球队名称2:<input type="text" name="teamList[1].tName"></br>
球队名称3:<input type="text" name="teamList[2].tName"></br>
<button type="submit">提交</button>
</form>
//重新定义一个对象,将集合作为它的一个属性,通过前面2中对象的方式传递参数
public class TeamVO {
List<Team> teamList;
}
/**
* 如果需要客户端传递存储自定义对象的集合,springmvc本身不支持这种方式,但是可以参考前面的对象类型接受的方式,重现创建一个类,将集合作为这个类的一个属性来接受
* @param teamVO
* @return
*/
@RequestMapping("/test09")
public ModelAndView test09(TeamVO teamVO){
ModelAndView mv = new ModelAndView("ok");
for(Team team:teamVO.getTeamList()){
System.out.println(team);
}
return mv;
}
请求参数中文乱码的解决方案
在web.xml文件中配置编码过滤器,
原理:springmvc-web提供了编码过滤器的类,直接在配置文件中对其进行时初始化即可
<filter>
<filter-name>characterEncodingFilter</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>
<!--下面这两个参数赋值为true,含义是:如果在controller中使用了 request.setCharacterEncoding()来设置编码方式,这里也会将其覆盖掉,强制使用此处配置文件中的设置-->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping><!--过滤器映射-->
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern><!--过滤所有类型的请求-->
</filter-mapping>
7、处理器方法的返回值
(1)返回ModelAndView
(2)直接返回字符串
创建ResultController.java
@Controller
@RequestMapping("/result")
public class ResultController {
//1、返回值是ModelAndView,既有数据的携带,也有资源的跳转,可以选择这种方式
@RequestMapping("/test01")
public ModelAndView test01(){
ModelAndView mv = new ModelAndView();//模型与视图
mv.setViewName("result");//将会经过视图解析器处理:添加前后缀变成物理资源路径。
//携带数据返回
mv.addObject("teamName","湖人队");
return mv;
}
//2、直接返回字符串
@RequestMapping("test02")
public String test02(HttpServletRequest request){
Team team = new Team();
team.setTeamID(1002);
team.settName("热火");
team.settLocation("迈阿密");
request.setAttribute("team",team);
request.getSession().setAttribute("team",team);
return "result";//直接返回的字符串也将会经过视图解析器处理:添加前后缀变成物理资源路径。效果和ModelAndView.setViewName("result")一样
}
}
创建webapp/jsp/result.jsp,编写下面的代码接受后端返回值
<body>
<h1>result--------------</h1>
<h3>test01------------${teamName}-----</h3>
<h3>test02-1-----request作用域获取-------${requestScope.team.tName}---${requestScope.team.teamID}--${requestScope.team.tLocation}--</h3>
<h3>test02-2-----session作用域获取-------${sessionScope.team.tName}---${sessionScope.team.teamID}--${sessionScope.team.tLocation}--</h3>
</body>
访问
http://localhost:8080/result/test01
http://localhost:8080/result/test02
观察页面跳转和页面是否填充了controller的返回值
(3)返回对象类型
返回自定义对象
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
引入上面的依赖,使用@ResponseBody注解返回的对象会被封装为json格式然后返回给前端页面
//3、返回对象类型:Integer Double String 自定义类型 List Map
//返回的不是逻辑视图的名称,而是直接返回数据,一般与ajax请求搭配使用,将json格式的数据直接返回给响应体
//一定要与@ResponseBody搭配使用
@ResponseBody
@RequestMapping("/test03-1")
public Integer test03_1(){
return 666;//直接网页访问localhost:8080/result/test03-1,网页就会直接显示这里返回的内容
}
@ResponseBody
@RequestMapping("/test03-2")
public String test03_2(){
return "test03-2";//直接网页访问localhost:8080/result/test03-2,网页就会直接显示这里返回的内容
}
- 直接返回一个完整的Team对象
创建个js文件夹,引入jQuery.js文件,然后在result.jsp页面使用ajax发送一个请求,返回一个对象在result.jsp页面显示
在head标签中引入js文件
<script src="/js/jquery2.1.4.js"></script>
注意,这里的js文件属于静态资源,可能需要在springmvc.xml文件中添加资源扫描配置
<mvc:resources location="/js/" mapping="/js/**" />
<div>
<button type="button" id="btn1">ajax请求</button>
<h3>ajax请求自定义结果展示</h3>
<p id="res"></p>
</div>
<script>
$(function (){
$("#btn1").click(function (){
$.ajax({
type: "POST",
url: "/result/test03-3",
data: "",
success: function(msg){
alert( "Data Saved: " + msg );
var name = msg.tName;
var id = msg.teamID;
var location = msg.tLocation;
$("#res").html("name:"+name+",id:"+id+",location:"+location);
}
});
});
});
</script>
@ResponseBody
@RequestMapping("/test03-3")
public Team test03_3(){
Team team = new Team();
team.setTeamID(1002);
team.settName("热火");
team.settLocation("迈阿密");
System.out.println(team);
return team;//返回一个team对象给前端,json格式
}
返回List类型
<button type="button" id="btn2">ajax请求List(Team)类型</button>
<h3>ajax请求List(Team)类型结果展示</h3>
<p id="res2"></p>
<script>
$("#btn2").click(function (){
$.ajax({
type: "POST",
url: "/result/test03-4",
data: "",
success: function(list){
alert( "Data Saved: " + list );
var str = "";
for(var i=0;i<list.length;i++){
var obj = list[i];
str+="name:"+obj.tName+",id:"+obj.teamID+",location:"+obj.tLocation+"<br/>";
}
$("#res2").html(str);
}
});
});
</script>
@ResponseBody
@RequestMapping("/test03-4")
public List<Team> test03_4(){
List<Team> list = new ArrayList<>();
for(int i=0;i<5;i++){
Team team = new Team();
team.setTeamID(1002+i);
team.settName("热火"+i);
team.settLocation("迈阿密"+i);
list.add(team);
System.out.println(team);
}
return list;
}
返回Map类型
<button type="button" id="btn3">ajax请求Map(String,Team)类型</button>
<h3>ajax请求Map类型结果展示</h3>
<p id="res3"></p>
<script>
$("#btn3").click(function (){
$.ajax({
type: "POST",
url: "/result/test03-5",
data: "",
success: function(map){
alert( "Data Saved: " + map );
var str = "";
$.each(map,function(i,obj){
str+="name:"+obj.tName+",id:"+obj.teamID+",location:"+obj.tLocation+"<br/>";
});
$("#res3").html(str);
}
});
});
</script>
@ResponseBody
@RequestMapping("/test03-5")
public Map<String,Team> test03_5(){
Map<String,Team> map = new HashMap<>();
for(int i=0;i<5;i++){
Team team = new Team();
team.setTeamID(1000+i);
team.settName("勇士"+i);
team.settLocation("金州"+i);
map.put(String.valueOf(i),team);
System.out.println(team);
}
return map;
}
返回日期类型
还是使用前面的map类型,Team的CreateTime字段添加@JsonFormat注解,表示将返回的日期类型格式化。
public class Team {
int teamID;
String tName;
String tLocation;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd") //添加这个注解
Date createTime;
}
并在上面的test03_5()中给team的createTime赋值,在前端展示的代码中添加createTime属性的展示。
(4)无返回类型(void方法)
其实就是使用原生servlet方式,进行转发/重定向、直接使用打印流给前端传输数据、
@RequestMapping("test04-1")
public void test04_1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("直接使用HttpServletRequest进行服务器端的转发");
request.getRequestDispatcher("/jsp/ok.jsp").forward(request,response);
}
@RequestMapping("test04-2")
public void test04_2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("直接使用HttpServletResponse重定向跳转");
response.sendRedirect("/jsp/ok.jsp");
}
@RequestMapping("test04-3")
public void test04_3(HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("使用打印流给前端返回字符串");
writer.flush();
writer.close();
}
@RequestMapping("test04-4")
public void test04_4(HttpServletResponse response) throws ServletException, IOException {
response.setStatus(302);//设置响应码,302表示重定向
response.setHeader("Location","/jsp/ok.jsp");
}
//测试:
//直接在浏览器访问:
http://localhost:8080/result/test04-1
http://localhost:8080/result/test04-2
http://localhost:8080/result/test04-3
http://localhost:8080/result/test04-4
8、页面导航的方式
页面都以转发到ok.jsp为例
<body>
<h1>你的操作成功跳转到了ok页面</h1>
<h1>------------${param1}</h1><%--这里展示的是请求中携带的参数,重定向会导致请求中断,无法从request中获取此参数--%>
<%--但是使用ModelAndView重定向会将参数追加在url后面,可以从地址栏URL中获取参数--%>
<h2>----获取地址栏中的参数值---param1=${param.param1},param2=${param.param2}-----</h2><%--中文会乱码--%>
</body>
转发到页面
//1-1使用字符串转发
@RequestMapping("test01-1")
public String test01_1(HttpServletRequest request){
request.setAttribute("param1","一个参数:test1-1的");
return "ok";//默认方式:直接返回字符串,然后经过视图解析器添加前后缀。
//return "forward:/jsp/ok.jsp";//当添加了forward后,视图解析器失效,需要自己全路径地址。
}
//1-2使用ModelAndView
@RequestMapping("test01-2")
public ModelAndView test01_2(){
ModelAndView mv = new ModelAndView();
mv.addObject("param1","一个参数:test1-2的");
//mv.setViewName("ok");//默认方式:直接传入字符串,然后经过视图解析器添加前后缀。
mv.setViewName("forward:/jsp/ok.jsp");//当添加了forward后,视图解析器失效,需要自己全路径地址。
return mv;
}
重定向到页面
//1-1使用字符串重定向
@RequestMapping("test02-1")
public String test02_1(HttpServletRequest request){
request.setAttribute("param1","参数1:test2-1的");//页面上无法获取到存储在request作用域中的值,因为请求中断了。
return "redirect:/jsp/ok.jsp";//当添加了forward后,视图解析器失效,需要自己全路径地址。
}
//1-2使用ModelAndView重定向
@RequestMapping("test02-2")
public ModelAndView test02_2(){
ModelAndView mv = new ModelAndView();
mv.addObject("param1","参数1:test2-2的");
mv.addObject("param2","参数2:test2-2的");
//使用ModelAndView重定向时,存储在request作用域中的值以参数的形式追加在URL后面,如下:
//http://localhost:8080/jsp/ok.jsp?param1=参数1¶m2=参数2
mv.setViewName("redirect:/jsp/ok.jsp");//当添加了forward后,视图解析器失效,需要自己全路径地址。
return mv;
}
转发/重定向到控制器
//转发到控制器
@RequestMapping("test03-1")
public ModelAndView test03_1(){
ModelAndView mv = new ModelAndView();
mv.addObject("param1","参数1");
mv.setViewName("forward:/navigation/test01-1");
return mv;
}
//重定向到控制器
@RequestMapping("test03-2")
public ModelAndView test03_2(){
ModelAndView mv = new ModelAndView();
mv.addObject("param1","参数1");
mv.setViewName("redirect:/navigation/test01-1");
return mv;
}
9、SpringMVC中的异常处理
SpringMVC框架常用@ExceptionHandler注解处理异常。
@ExceptionHandler注解
@ExceptionHandler可以将一个方法指定为异常处理方法。
被注解的方法,其返回值可以是ModelAndView、String. 或void, 方法名随意,方法参数可以是Exception 及其子类对象、HttpServletRequest、HttpServletResponse等。系统会自动为这些方法参数赋值。
对于异常处理注解的用法,也可以直接将异常处理方法注解于Controller 之中,
当异常产生,程序会自动从容器中查找能对该类型异常进行处理的被@ExceptionHandler注解的异常处理方法(例如@ExceptionHandler(value = {TeamIDException.class}),表示此方法可以处理TeamIDException类型的异常),并将该异常交给此方法进行处理。
为了避免重复工作,通常直接定义全局异常处理类
-
该类使用@ControllerAdvice注解
-
不要忘记在配置文件中扫描该类所在的包
案例:
-
定义TeamException继承Exception,再定义连个它的子类:TeamIDException、TeamNameException
-
定义三个异常页面error.jsp、idError.jsp、nameError.jsp,分别用于发生TeamIDException、TeamNameException和其他所有类型异常数时跳转。
-
创建包+类:exceptions.GlobalExceptionHandler,并定义异常处理方法:
-
package com.zx.exceptions; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = {TeamIDException.class})//表示此类只用于处理TeamIDException异常 public ModelAndView exHandler1(Exception ex){ ModelAndView mv = new ModelAndView(); mv.addObject("msg",ex.getMessage()); mv.setViewName("idError");//跳转到idError.jsp return mv; } @ExceptionHandler(value = {TeamNameException.class})//表示此类只用于处理TeamNameException异常 public ModelAndView exHandler2(Exception ex){ ModelAndView mv = new ModelAndView(); mv.addObject("msg",ex.getMessage()); mv.setViewName("nameError");//跳转到nameError.jsp return mv; } @ExceptionHandler(value = {Exception.class})//表示此类用于处理其它所有类型的异常 public ModelAndView exHandler3(Exception ex){ ModelAndView mv = new ModelAndView(); mv.addObject("msg",ex.getMessage()); mv.setViewName("error");//跳转到error.jsp return mv; } }
-
编写测试方法,产生各种不同的异常进行测试
/** * 异常处理控制器 */ @RequestMapping("/ex") @Controller public class ExController { @RequestMapping("test01-1/{id}/{name}") public ModelAndView test01_1(@PathVariable("id") int id, @PathVariable("name") String teamName) throws TeamIDException, TeamNameException { ModelAndView mv = new ModelAndView(); if(id<1000){ throw new TeamIDException("teamID错误,必须要大于1000"); } if("test".equals(teamName)){ throw new TeamNameException("teamName错误,值不能是test"); } //int a = 10/0; mv.setViewName("ok"); return mv; }
-
10、拦截器
SpringMVC中的拦截器( Interceptor)是非常重要的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。
拦截的时间点在处理器映射器HandlerMapping根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器HandlerAdaptor,在处理器适配器执行处理器之前。
在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链HandlerExecutionChain,并返回给了前端控制器。
自定义拦截器,需要实现HandlerInterceptor接口。而该接口中含有三个方法:
preHandle(request,response,object handler):
该方法在处理器方法执行之前执行。其返回值为boolean,若为true,则紧接着会执行处理器方法,且会将afterCompletion()方法放入到一个专门的方法栈中等待执行。
postHandle(request ,response, object handler ,modelAndView) :
该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。
afterCompletion(request,response, object handler, Exception ex):
当preHandle(方法返回true时, 会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在前端控制器渲染(数据填充)了响应页面之后执行的,此时对ModelAndView再操作也对响应无济于事。
afterCompletion最后执行的方法,清除资源,例如在Controller方法中加入数据
自定义拦截器
定义包com.zx.interceptor,然后定义两个拦截器
package com.zx.interceptor;
public class MyInterceptor implements HandlerInterceptor {
//执行时机:控制器方法执行之前,在ModelAndView返回之前
//使用场景:登陆验证
//返回值:true:继续执行控制器方法,表示放行;
// false:不会继续执行控制器方法,表示拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle----------");
return true;
}
//执行时机:控制器方法执行之后。在ModelAndView返回之前,有机会修改返回值
//使用场景:日记记录,记录登录的ip、时间
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle----------");
}
//执行时机:控制器方法执行之后。在ModelAndView返回之后(页面渲染完成后),没有机会修改返回值
//使用场景:全局资源的一些操作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion----------");
}
}
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle2----------");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle2----------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion2----------");
}
}
然后从springmvc的配置文件xml中配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<!--按顺序配置多个拦截器-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zx.interceptor.MyInterceptor" id="myInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zx.interceptor.MyInterceptor2" id="myInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
随便访问一个controller,观察控制台输出。
输出:这里输出的顺序需要注意。
preHandle----------
preHandle2----------
postHandle2----------
postHandle----------
afterCompletion2----------
afterCompletion----------
11、文件上传和下载
SpringMVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver实现
Spring中有一个MultipartResolver的实现类:CommonsMultipartResolver.
在SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件上传工作。
如果想使用Spring的文件上传功能则需要先在上下文中配置MultipartResolver.
上传基础功能
添加依赖:
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
创建文件上传的页面
<form action="/file/upload" method="post" enctype="multipart/form-data">
请选择文件:<input type="file" name="myFile"/><br/>
<button type="submit">上传文件</button>
</form>
@Controller
@RequestMapping("/file")
public class FileController {
@RequestMapping("/upload")
public String upLoad(@RequestParam("myFile") MultipartFile myFile, HttpServletRequest request) throws IOException {
System.out.println("1");
//获取文件的原始名称
String originalFileName = myFile.getOriginalFilename();
//实际开发中一般都要将文件重新命名后进行存储
//根据实际名称获取到源文件的后缀
//存储到服务器的文件名称=随机的字符串+根据实际名称获取到源文件的后缀
String fileName = UUID.randomUUID().toString().replace("-","")+originalFileName.substring(originalFileName.lastIndexOf("."));
System.out.println(fileName);
//文件的存储路径
String realPath = request.getServletContext().getRealPath("/uploadFilePath")+"/";
myFile.transferTo(new File(realPath+fileName));//真正的文件上传到服务器的指定位置
System.out.println("上传成功:"+realPath+fileName);
return "ok";
}
@RequestMapping("/hello")
public String hello(){
return "fileHandle";
}
}
限制上传大小和类型
限制大小,直接配置文件中设置CommonsMultipartResolver的参数maxUploadSize
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--文件上传的下大小限制,以字节Bit为单位,比如5MB=1024Bit*1024*5-->
<property name="maxUploadSize" value="5242880"/><!--运行时超出大小就会报错,可以使用全局异常处理器拦截并处理-->
<property name="defaultEncoding" value="utf-8"/>
</bean>
限制类型,使用拦截器来过滤文件后缀
/**
* 文件后缀处理的拦截器
*/
public class FileInterceptor implements HandlerInterceptor {
/**
* 在文件上传之前判定文件后缀是否合法
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean flag = true;
//判定是否是文件上传的请求
if(request instanceof MultipartHttpServletRequest){
MultipartHttpServletRequest multiparRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multiparRequest.getFileMap();
//遍历文件
Iterator<String> iterator = fileMap.keySet().iterator();
while(iterator.hasNext()){
String key = iterator.next();
MultipartFile file = multiparRequest.getFile(key);
String originalFileName = file.getOriginalFilename();
String hz = originalFileName.substring(originalFileName.lastIndexOf("."));//取到后缀
//判断后缀是否合法,若不合法,false。
if(!"png".equals(hz.toLowerCase()) || !"jpg".equals(hz.toLowerCase())){
//由于preHandle执行时机,若要跳转页面需要自行处理
request.getRequestDispatcher("/jsp/fileTypeError.jsp").forward(request,response);
flag=false;
}
}
}
return flag;
}
}
并在springmvc.xml中添加该拦截器
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zx.interceptor.FileInterceptor" id="fileInterceptor"></bean>
</mvc:interceptor>
创建产生文件类型错误后要跳转的页面fileTypeError.jsp。测试即可
下载
fileHandle.jsp
<form action="/file/download" method="post" enctype="multipart/form-data">
<button type="submit">下载图片3a5fb7ed1a5f43ef89c4d2ee92dee848.png</button>
</form>
FileController.java添加方法
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws IOException {
//指定文件的路径
String path = request.getServletContext().getRealPath("/uploadFilePath")+"/3a5fb7ed1a5f43ef89c4d2ee92dee848.png";
//创建响应 的头信息的对象
HttpHeaders headers = new HttpHeaders();
//标记以流的形式做出响应
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//以附件的形式响应给用户
headers.setContentDispositionFormData("attachment", URLEncoder.encode("3a5fb7ed1a5f43ef89c4d2ee92dee848.png","utf-8"));//编码格式问题
File file = new File(path);
ResponseEntity<byte[]> resp = new ResponseEntity<>(FileUtils.readFileToByteArray(file),headers, HttpStatus.CREATED);
return resp;
}