文章目录
SpringMVC
1. 什么是SpringMVC
SpringMVC 也叫 Spring Web MVC。是 Spring内置的一个MVC框架,在 Spring3.0 后发布。SpringMVC框架解决了WEB开发中常见的问题(参数接收、文件上传、表单验证等等),而且使用简单,与Spring无缝集成。支持 RESTful风格的URL请求。采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。
2.SpringMVC的优势
-
功能强大
支持RESTful、数据验证、格式化、本地化、主题等。
-
功能分工明确
基于 MVC 架构,解决页面代码和后台代码的分离。
-
轻量级,简单易用
SpringMVC 也是轻量级的,jar 很小。不依赖的特定的接口和类就可以开发一个注解的SpringMVC 项目。
-
Spring内置的MVC框架
作为 Spring 框架一部分 ,与Spring兼容性好、无缝结合,能够使用Spring的IoC和AOP。方便整合MyBatis、Hiberate、JPA等其他框架。
-
SpringMVC的注解强大易用
-
约定大于配置
3. MVC模式回顾
MVC(Model View Controller),即模型、视图和控制器:
-
Model
模型层,javaBean 负责数据访问和业务处理 dao service pojo。
-
View
视图,JSP技术负责收集和展示数据。
-
Controller
控制器,servlet技术中间调度:
- 接受客户端的请求(包括请求中携带的数据);
- 处理请求,调用后台的模型层中的业务逻辑;
- 页面导航,处理完毕给出响应(JSP页面 )。
注:
- 模型1:jsp+javabean模型—在jsp页面中嵌入大量的java代码
- 模型2:jsp+servlet+javabean模型—jsp页面将请求发送给servlet,由servlet调用javabean,再由servlet将制定jsp页面响应给用户(一般就是现在的MVC模式,也是我们一直使用的)。
4. MVC入门
4.1 创建MAVEN项目
4.2 添加依赖
<!--web项目-->
<packaging>war</packaging>
<!--以下可以设置在项目中设置成共享依赖,项目中的多个模块可以共享-->
<dependencies>
<!--spring-webmvc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<!--springmvc底层还是servlet,所以必须添加serlvet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope><!--插件运行的时候没有范围插件启动会失败-->
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编码和编译和JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!--tomcat插件-->
<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>
4.3 添加配置文件
-
Spring配置文件
<?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 http://www.springframework.org/schema/context/spring-context.xsd"> <!--Spring的配置文件:除了控制器之外的bean对象都在这里扫描创建--> <!--组件扫描--> <context:component-scan base-package="com.lijinghua.dao com.lijinghua.service com.lijinghua.pojo"/> </beans>
-
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 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.lijinghua.controller"/> <!--视图解析器--> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/"></property> <!--后缀--> <property name="suffix" value=".jsp"></property> </bean> <mvc:annotation-driven/> <!-- annotation-driven是一种简写形式,也可以手动配置替代这种简写形式,简写形式可以让初学者快速应用默认配置方案。 该注解会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter两个bean, 是springMVC为@Controller分发用户请求所必须的,解决了@Controller注解使用的前提配置。 同时它还提供了:数据绑定支持,@NumberFormatannotation支持,@DateTimeFormat支持, @Valid支持,读写XML的支持(JAXB,读写JSON的支持(Jackson)。 我们处理响应ajax请求时,就使用到了对json的支持(配置之后,在加入了jackson的core和mapper包之后,不写配置文件也能自动转换成json)。 --> </beans>
-
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"> <!--首先进行Spring配置--> <context-param> <!--contextConfigLocation:表示用于加载 Bean的配置文件--> <param-name>contextConfigLocation</param-name> <!-- 指定spring配置文件的位置 注意: 这个配置文件也有一些默认规则,它的配置文件名默认就叫 applicationContext.xml , 如果将这个配置文件放在 WEB-INF 目录下,那么这里就可以不用指定配置文件位置,只需要指定监听器就可以。 这段配置是 Spring 集成 Web 环境的通用配置;一般用于加载除了控制器层之外的 Bean(如 dao、service 等),以便于与其他任何Web框架集成。 --> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--SpringMVC的配置--> <!-- 前端控制器:所有的请求都会经过此控制器,然后通过此控制器分发到各个分控制器. 前端控制器本质上还是一个Servlet,因为SpringMVC底层就是使用Servlet编写的 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 创建前端控制器的时候读取springmvc配置文件启动IoC容器 如果没有配置,启用默认的规则:这里是 dispatcherServlet-servlet.xml 即如果配置文件放在 webapp/WEB-INF/ 目录下,并且配置文件的名字等于 DispatcherServlet 的名字+ -servlet --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- Tomcat启动就创建此对象 load-on-startup:标记是否在Web服务器(这里是Tomcat)启动时会创建这个 Servlet 实例, 即是否在 Web 服务器启动时调用执行该 Servlet 的 init()方法,而不是在真正访问时才创建。 要求取值是整数。 值>0:表示容器在启动时就加载并初始化这个servlet。数值越小,该 Servlet的优先级就越高,其被创建的也就越早。 值<0或者省略:表示该 Servlet 在真正被使用时才会去创建。 值相同:容器会自己选择创建顺序 --> <load-on-startup>1</load-on-startup> </servlet> <!-- 配置拦截路径url,所有以.do结尾的请求都会被前端控制器拦截处理 --> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
-
运行结果
4.4 加载Spring容器和SpringMVC容器
- ContextLoaderListener 初始化的上下文加载的 Bean 是对于整个应用程序共享的,不管是使用什么表现层技术(一般如 dao层、service层 的bean);
- DispatcherServlet 初始化的上下文加载的 bean 是只对 Spring Web MVC 有效的 bean,如Controller、HandlerMapping、HandlerAdapter 等等,该初始化上下文应该只加载 Web相关组件。
注:
Spring容器中不能扫描所有Bean
当用户发送的请求达到服务端后,会寻找前端控制器DispatcherServlet去处理,只在SpringMVC容器中找,所以Controller必须在SpringMVC容器中扫描。SpringMVC容器中可以扫描所有Bean
可以在SpringMVC容器中扫描所有Bean,但是实际开发中一般不会这么做,原因如下:
为了方便配置文件的管理
未来在 Spring+SpringMVC+Mybatis组合中,要写的配置内容很多,一般都会根据功能分开编写。
5. SpringMVC原理
5.1 SpringMVC再次理解
Servlet是java进行web开发的标准,在没有使用SpringMVC之前我们都是使用Servlet在做Web开发。但是使用Servlet开发在接收请求参数,数据共享,页面跳转等操作相对比较复杂。既然springMVC是对servlet的封装,那么很显然SpringMVC底层就是Servlet,SpringMVC就是对Servlet进行深层次的封装。
Spring MVC框架像许多其他MVC框架一样, 以请求为驱动 , Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计,即围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)。
DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁 。
5.2 SpringMVC组件
-
DispatcherServlet
前端控制器,也称为中央控制器或者核心控制器。用户请求的入口控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,相当于是 SpringMVC 的大脑,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。SpringMVC框架提供的该核心控制器需要我们在web.xml文件中配置(如4.3小节)。
-
HandlerMapping
处理器映射器,HandlerMapping也是控制器,派发请求的控制器。我们不需要自己控制该类,但是他是SpringMVC运转历程中的重要的一个控制器。 HandlerMapping负责根据用户请求找到 Handler 即处理器(也就是我们所说的 Controller),SpringMVC 提供了不同的映射器实现不同的映射方式(例如:配置文件方式、实现接口方式、注解方式等),在实际开发中,我们常用的方式是注解方式。
-
Handler
处理器,Handler 是继 DispatcherServlet 前端控制器的后端控制器,在DispatcherServlet 的控制下 Handler 对具体的用户请求进行处理。由于 Handler 涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发 Handler。(这里所说的 Handler 就是指我们的 Controller)。
-
HandlAdapter
处理器适配器,通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展处理器适配器,支持更多类型的处理器,调用处理器传递参数等工作。
-
ViewResolver
视图解析器,ViewResolver 负责将处理结果生成 View 视图,ViewResolver 首先根据逻辑视图名解析成物理视图名称,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。 SpringMVC 框架提供了很多的 View 视图类型(包括jstlView、freemarkerView、pdfView 等)。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
5.3 SpringMVC运行流程
如下图所示,这里借用Java狂神的绘图,详细可登录B站找Java狂神说的相关视频,这里就不贴链接了。实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现 。
-
用户通过浏览器发送请求到前端控制器DispatcherServlet。
-
前端控制器直接将请求转给处理器映射器HandleMapping。
-
处理器映射器HandleMapping会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链HandlerExecution(HandlerExecution表示具体的Handler );
-
HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射。
-
前端控制器DispatcherServlet找到能够执行该处理器的处理器适配器HandlerAdapter。
-
处理器适配器HandlerAdapter按照特定的规则去执行Handler,Handler调用执行处理器Controller。
-
处理器Controller进行处理后,将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给处理器适配器HandlerAdapter,如ModelAndView。
-
处理器适配器直接将结果返回给前端控制器DispatcherServlet。
-
前端控制器调用视图解析器,解析处理器适配器传递的逻辑视图名。
在视图解析器中我们把所有的视图都存放在/WEB-INF/目录下,这样可以保证视图安全,因为这个目录下的文件,客户端不能直接访问。
-
视图解析器ViewResolver将解析得到的物理视图名返回给前端控制器DispatcherServlet。
-
前端控制器DispatcherServlet根据视图解析器解析的视图结果,通过View解析成具体的物理视图,并调用render()方法对其进行渲染。
-
前端控制器响应浏览器。
6. @RequestMapping 注解
6.1 @RequestMapping的映射规则
@RequestMapping 注解定义了处理器对于请求的映射规则。
/**
该注解可以定义在类上,也可以定义在方法上,但是含义不同。
(1)一个@Controller 所注解的类中,可以定义多个处理器方法。当然,不同的处理器方法所匹配的 URI 是不同的。这些不同的 URI 被指定在注解于方法之上的@RequestMapping 的value 属性中。
(2)但若这些请求具有相同的 URI 部分,则这些相同的 URI,可以被抽取到注解在类之上的RequestMapping的value属性中。此时的这个 URI 表示模块的名称。URI 的请求是相对于 Web 的根目录。
(3)在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上。之后你还可以另外添加方法级别的注解来进一步指定到处理方法的映射关系。
*/
@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 {};
/**
(1)@RequestMapping的method属性,用来对被注解方法所处理请求的提交方式进行限制,即只有满足method 属性指定的提交方式的请求,才会执行该被注解方法。
(2)Method 属性的取值为 RequestMethod 枚举常量。
常用的为RequestMethod.GET 与RequestMethod.POST,分别表示提交方式的匹配规则为 GET 与 POST 提交。
(3)如果不配置,则以任何请求方式都可以访问到
*/
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
6.2 @RequestMapping的请求方式
6.3 静态资源访问问题(额外)
在web.xml配置SpringMVC的前端控制器时有这个节点。这个节点中的值一般有两种写法 :
(1) *.do
在没有特殊要求的情况下,SpringMVC 的前端控制器 DispatcherServlet 的常使用后辍匹配方式,可以写为*.do 或者 *.action, *.mvc 等。
(2) /
可以写为/,但是 DispatcherServlet 会将向静态内容(例如.css、.js、图片等资源)的获取请求时,也会当作是一个普通的 Controller 请求。前端控制器会调用处理器映射器为其查找相应的处理器。肯定找不到啊,所以所有的静态资源获取请求也均会报 404 错误。
(3) /*
第二种不会匹配到.jsp,而/*会匹配.jsp。会出现返回jsp视图时再次进入spring的DispatcherServlet 类,导致找不到对应的controller所以报404错。
那么,如果是按第二种方式配置的url-pattern,并且还想访问静态资源怎么办?
6.3.1 使用< mvc:default-servlet-handler/ >
声明了 <mvc:default-servlet-handler /> 后 ,springmvc框架会在容器中创建DefaultServletHttpRequestHandler处理器对象。该对象会对所有进入 DispatcherServlet的URL进行检查。如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet 处理。
<mvc:default-servlet-handler/>
一般的服务器都有默认的 Servlet。例如咱们使用的Tomcat服务器中,有一个专门用于处理静态资源访问的 Servlet 名叫 DefaultServlet。
其为default。可以处理各种静态资源访问请求。该Servlet注册在 Tomcat 服务器的 web.xml 中。在 Tomcat安装目录/conf/web.xml。
6.3.2 使用< mvc:resources/ >
在 Spring3.0 版本后,Spring 定义了专门用于处理静态资源访问请求的处理器ResourceHttpRequestHandler。并且添加了< mvc:resources/ >标签,专门用于解决静态资源无法访问问题。
<!--
location: 表示静态资源所在目录。当然,目录不要使用/WEB-INF/及其子目录。
mapping: 表示对该资源的请求。注意,后面是两个星号**。
-->
<mvc:resources location="/images/" mapping="/images/**" />
7. 处理器方法参数
处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值.所以我们可以在方法内直接使用。以下是这四类参数:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- 请求中所携带的请求参数
7.1 处理器接收参数的方式
-
直接使用方法的参数逐个接收
处理器方法中的参数逐个接收,且方法的参数名必须和用户请求中携带的参数名保持一致。不然,虽然不会报错,但是会导致后台获取到的参数为空。可以简化对请求参数的处理。
-
使用对象接收多个参数
以实体类对象作为处理器方法的参数,来接收前端的请求参数,且实体类中的属性名必须和用户请求中携带的参数名保持一致。不然,虽然不会报错,但是会导致后台获取到的参数为空。
-
请求参数和方法名称的参数不一致
//使用@RequestParam注解作用到方法中的参数上,映射到前端的请求参数. //前端请求的参数名必须写对,不然会报错400。 //如果不想报错可以将required参数设置成false(表示不要求一定获取到这个参数,默认是true)。这样的话,不会报错,但是会获取不到参数(为空) @RequestParam(value = "前端请求的参数名", required = false)
-
使用HttpServletRequest 对象获取参数
相当于原来JavaWeb中使用的Servlet去处理,只不过SpringMVC做了更进一步的封装。
@RequestMapping("test") public ModelAndView test(HttpServletRequest req){ String teamId = req.getParameter("teamId"); int id = teamId == null ? 0 : Integer.valueOf(teamId); String teamName = req.getParameter("teamName"); return new ModelAndView("success"); }
-
直接使用URL地址传参
借助 @PathVariable注解,完成URL传参。
假设请求的url为:http://localhost:8080/test/1007/Net/Brooklyn
//@RequestMapping中的{id}和@PathVariable("id")对应,名称必须一致。 @RequestMapping("test/{id}/{name}/{location}") public ModelAndView test(@PathVariable("id") Integer teamId, @PathVariable("name") String teamName, @PathVariable("location") String teamLocation){ System.out.println(teamId);//1007 System.out.println(teamName);//Net System.out.println(teamLocation);//Brooklyn return new ModelAndView("success"); }
7.2 处理器接收特殊类型参数
-
获取日期类型的参数
借助@DateTimeFormat注解,通过绑定日期的格式(例如使用对象去接收参数时)去识别、获取前端传递过来的日期参数。不然的话,也可以用字符串进行接收,然后像JavaWeb中的那样,自己日期格式化也行。
-
获取数组类型的参数
- 跟直接使用方法参数去逐个接收前端请求参数一样,只不过这里我们用数组去代替,参数名也是必须一致的。
- 和使用HttpServletRequest去获取参数一样。
-
获取集合类型的参数
SpringMVC不支持直接从参数中获取对象集合类型,需要将对象集合封装到实体类中。
-
和使用 @RequestParam注解有点类似,只要注解上的映射参数名一致即可
@RequestMapping("test") public ModelAndView test(@RequestParam("teamName") List<String> list){ for(String teamName : list){ System.out.println(teamName); } return new ModelAndView("success"); }
-
将对象集合封装到实体类
SpringMVC不支持直接从参数中获取对象集合类型,需要将对象集合封装到实体类中。
public class Team { private List<Team> teamList;//注意要和前端对应上! public List<Team> getTeamList() { return teamList; } public void setTeamList(List<Team> teamList) { this.teamList = teamList; } } @RequestMapping("test09") public ModelAndView test09(Team t){ for (Team team : t.getTeamList()) { System.out.println(team); } return new ModelAndView("ok"); }
-
7.3 中文传参与乱码问题
对于前面所接收的请求参数,若含有中文,则会出现中文乱码问题。Spring 对于请求参数中的中文乱码问题,给出了专门的字符集过滤器CharacterEncodingFilter 类。
在 web.xml 中注册字符集过滤器,推荐将该过滤器注册在其它过滤器之前。因为过滤器的执行是按照其注册顺序进行的。
<!--注册字符集过滤器:post请求中文乱码问题的解决方案-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filterclass>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--指定字符集-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!--强制request使用字符集encoding,你处理器方法中再去配置也无效,按这里的来-->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!--强制response使用字符集encoding-->
<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.4 处理器方法返回值
7.4.1 ModelAndView
如果是前后端不分的开发,大部分情况下,我们返回 ModelAndView,即数据模型+视图:
//1、返回值是ModelAndView: 这种方式既有数据的携带还有资源的跳转,可以选择该种方式
@RequestMapping("test01")
public ModelAndView test01(){
ModelAndView mv=new ModelAndView();//模型与视图
//携带数据
mv.addObject("teamName","湖人队");//相当于request.setAttribute("teamName","湖人队“);
mv.setViewName("result");// 经过视图解析器InternalResourceViewResolver的处理,将逻辑视图名称加上前后缀变为物理资源路径
return mv;
}
Model 中,放我们的数据,然后在 ModelAndView 中指定视图名称。
当处理器方法处理完后,需要跳转到其它资源的同时传递数据,选择返回 ModelAndView 比较好,但是如果只是需要传递数据或者跳转之一,这个时候ModelAndView 就不是最优选择。
7.4.2 String
//2、返回字符串
@RequestMapping("test02")
public String test02(HttpServletRequest request){
Team team=new Team();
team.setLocation("迈阿密");
team.setTeamId(1002);
team.setTeamName("热火");
//携带数据
request.setAttribute("team",team);
request.getSession().setAttribute("team",team);
//资源的跳转
return "result";// 经过视图解析器InternalResourceViewResolver的处理,将逻辑视图名称加上前后缀变为物理资源路径 前缀+result+后缀
}
上一种方式中的 ModelAndView 可以拆分为两部分,Model 和 View,在 SpringMVC 中,Model 我们可以直接在参数中指定,然后返回值是逻辑视图名,视图解析器解析可以将逻辑视图名称转换为物理视图地址。
视图解析器通过内部资源视图解析器InternalResourceViewResolver将字符串与解析器中的prefix和suffix结合形成要跳转的URI。
7.4.3 ModelMap
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){
//封装要显示到视图中的数据
//相当于req.setAttribute("name",name);
model.addAttribute("name",name);
System.out.println(name);
return "hello";
}
7.4.4 Model
@RequestMapping("/ct2/hello")
public String hello(@RequestParam("username") String name, Model model){
//封装要显示到视图中的数据
//相当于req.setAttribute("name",name);
model.addAttribute("msg",name);
System.out.println(name);
return "test";
}
注意:Model、ModelAndView和ModelMap的使用区别
- Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;
- ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;
- ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。
7.4.5 返回自定义类型对象
当处理器方法返回Object对象类型的时候,可以是Integer、String、Map、List,也可以是自定义的对象类型。但是无论是什么类型,都不是作为逻辑视图出现,而是直接作为数据返回然后展示的。
常见于前后端分离开发,例如前端发起Ajax请求的时候都会使用直接返回对象的形式(JSON)。
返回对象的时候,需要使用@ResponseBody 注解,将转换后的数据(JSON)放入到响应体中。
使用步骤:
-
pom.xml文件中添加JSON依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
-
处理器方法代码实现
//返回对象类型:Integer Double String 自定义类型 List Map //返回的不是逻辑视图的名称,而直接就是数据返回 ,一般是ajax请求搭配使用 ,将json格式的数据直接返回给响应体 //Integer @ResponseBody//一定要有@ResponseBody @RequestMapping("test01") public Integer test01(){ return 666; } //String @ResponseBody @RequestMapping("test02") public String test02(){ return "test"; } //自定义类型对象 @ResponseBody @RequestMapping("test03") public Team test03(){ Team team=new Team(); team.setLocation("迈阿密"); team.setTeamId(1002); team.setTeamName("热火"); return team; } //List @ResponseBody @RequestMapping("test04") public List<Team> test04(){ List<Team> list=new ArrayList<>(5); for(int i=1;i<=5;i++) { Team team = new Team(); team.setLocation("迈阿密"+i); team.setTeamId(1002+i); team.setTeamName("热火"+i); list.add(team); } return list; } //Map @ResponseBody @RequestMapping("test05") public Map<String,Team> test05(){ Map<String,Team> map=new HashMap(); for(int i=1;i<=5;i++) { Team team = new Team(); team.setLocation("金州"+i);team.setTeamId(1000+i); team.setTeamName("勇士"+i); //日期类型,在返回的时候是个数字,如果想要按日期格式展示需要在实体类对应属性添加注解@JsonFormat(pattern = "yyyy-MM-dd") team.setCreateTime(new Date()); map.put(team.getTeamId()+"",team); } return map; }
7.4.6 无返回值 void
方法的返回值为 void,并不一定真的没有返回值,我们可以通过其他方式给前端返回。实际上,这种方式也可以理解为 Servlet 中的的处理方案。
//通过 HttpServletRequest 做服务端跳转
@RequestMapping("test06")
public void test06(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("直接使用HttpServletRequest进行服务器端的转发");
request.getRequestDispatcher("/jsp/ok.jsp").forward(request,response);
}
//通过 HttpServletResponse 做重定向
@RequestMapping("test07")
public void test07(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("直接使用HttpServletResponse重定向跳转");
response.sendRedirect("/jsp/ok.jsp");
}
//通过 HttpServletResponse 给出响应
@RequestMapping("test08")
public void test08(HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("返回void类型测试---直接返回字符串");
writer.flush();
writer.close();
}
//也可以自己手动指定响应头去实现重定向
@RequestMapping("test04-4")
public void test044(HttpServletResponse response) throws ServletException,IOException {
response.setStatus(302);//设置响应码,302表示重定向
response.setHeader("Location","/jsp/ok.jsp");
}
8.页面导航/跳转的方式
页面导航分为两种:转发和重定向。在SpringMVC中两种导航进行页面导航的时候使用不同的前缀指定转发还是重定向:
-
转发
forward:url (默认)
-
重定向
redirect:url
SpringMVC有以下两种方式实现页面的转发或重定向:
-
返回字符串(Model、ModelMap还是借助了String返回方式的)
@RequestMapping("test10") public String test10(HttpServletRequest request){ request.setAttribute("teamName","湖人"); //(1)默认:由视图解析器处理之后将逻辑视图转为物理资源路径 //return "ok"; //(2)转发(和默认直接return一样):当添加了forward前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径 return "forward:/jsp/ok.jsp"; //(3)重定向:当添加了redirect前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径 //和原来一样,重定向会丢失request域中的数据 //return "redirect:/jsp/ok.jsp"; }
-
使用ModelAndView
@RequestMapping("test11") public ModelAndView test11(){ ModelAndView mv=new ModelAndView(); mv.addObject("teamName","huangfeng"); //1.跳转页面 //(1)默认转发:由视图解析器处理之后将逻辑视图转为物理资源路径 //mv.setViewName("ok"); //(2)转发(和默认直接return一样):当添加了forward前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径 mv.setViewName( "forward:/jsp/ok.jsp"); //(3)重定向:当添加了redirect前缀之后,视图解析器中的前后缀就失效了,必须自己编写绝对路径 //不同于字符串转发方式而言,使用ModelAndView进行重定向的时候,存储在request作用域中的值以参数的形式追加在URL后面,我们可以在返回页面中取到数据 //http://localhost:8080/jsp/ok.jsp?teamName=huangfeng //mv.setViewName( "redirect:/jsp/ok.jsp"); //2.跳转控制器,注意这里跳转的时候要写绝对路径 //(1)转发 mv.setViewName("forward:/test10"); //(2)重定向 mv.setViewName("redirect:/test10");//参数值直接追加到URL后面 return mv; }
9. 异常处理
SpringMVC框架常用@ExceptionHandler 注解处理异常。
9.1 @ExceptionHandler 注解
@ExceptionHandler 可以将一个方法指定为异常处理方法。被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法参数可以是Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。
对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中.
@Target({ElementType.METHOD})//用在方法上面的注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};//用于标注该注解的方法所要处理的异常类
}
9.2 实现步骤
//自定义异常类
public class TeamException extends Exception{
public TeamException() {}
public TeamException(String message) {
super(message);
}
}
public class TeamIdException extends TeamException{
public TeamIdException() {}
public TeamIdException(String message) {
super(message);
}
}
//Controller层
@Controller
@RequestMapping("ex")
public class ExController {
@RequestMapping("test12/{id}")
public ModelAndView test12(@PathVariable("id") Integer teamId) throws TeamIdException{
ModelAndView mv=new ModelAndView();
if(teamId<=1000){
throw new TeamIdException("teamId不合法!必须在1000之上!");
}
System.out.println(10/0);
mv.setViewName("ok");
return mv;
}
@ExceptionHandler(value ={TeamIdException.class, Exception.class})
public ModelAndView exHandler(Exception ex){
ModelAndView mv=new ModelAndView();
mv.addObject("msg",ex.getMessage());
if(ex instanceof TeamIdException){
mv.setViewName("idError");
}else{
mv.setViewName("error");
}
return mv;
}
}
注意:一个Controller下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常.
9.3 封装优化
我们可以对上述实现步骤进行封装,一般将异常处理方法专门定义在一个类中,作为全局的异常处理类。
使用注解@ControllerAdvice,就是“控制器增强”,是给控制器对象增强功能的。使用@ControllerAdvice 修饰的类中可以使用@ExceptionHandler。当使用@RequestMapping 注解修饰的方法抛出异常时,会执行@ControllerAdvice 修饰的类中的异常处理方法。
注意:@ControllerAdvice 注解所在的类需要进行包扫描,否则无法创建对象。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = TeamIdException.class)
public ModelAndView exHandler1(Exception ex){
ModelAndView mv=new ModelAndView();
mv.addObject("msg",ex.getMessage());
mv.setViewName("idError");
return mv;
}
@ExceptionHandler(value = TeamException.class)
public ModelAndView exHandler2(Exception ex){
ModelAndView mv=new ModelAndView();
mv.addObject("msg",ex.getMessage());
mv.setViewName("teamError");
return mv;
}
@ExceptionHandler(value = Exception.class)
public ModelAndView exHandler3(Exception ex){
ModelAndView mv=new ModelAndView();
mv.addObject("msg",ex.getMessage());
mv.setViewName("error");
return mv;
}
}
10. 拦截器
SpringMVC 中的拦截器( Interceptor)是非常重要的,类似于Servlet开发中的过滤器Filter,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。
拦截的时间点在“处理器映射器HandlerMapping根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器HandlerAdapter执行处理器之前”。
在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链HandlerExecutionChain,并返回给了前端控制器。
10.1 HandlerInterceptor接口
public interface HandlerInterceptor {
//该方法在处理器方法执行之前执行。其返回值为boolean,若为true,则紧接着会执行处理器方法,且会将afterCompletion()方法放入到一个专门的方法栈中等待执行。
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
//该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//当 preHandle()方法返回true时,会将该方法放到专门的方法栈中,等到对请求进行响应的所工作完成之后才执行该方法。即该方法是在前端控制器渲染(数据填充)了响应页面之后执行的,此时对ModelAndView再操作也对响应无济于事。afterCompletion最后执行的方法,清除资源,例如在Controller方法中加入数据
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
10.2 自定义拦截器
public class MyInterceptor implements HandlerInterceptor {
//执行时间:控制器方法执行之前
//使用场景:例如登录验证
// 返回值: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, HttpServletResponseresponse, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion-------------------");
}
}
10.3 配置拦截器
在springmvc.xml中添加如下配置(记得加上mvc的约束头文件):
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 这里可以同时配置多个拦截器,配置的顺序就是拦截器的拦截顺序 -->
<mvc:interceptor>
<!-- 拦截器要拦截的请求路径 拦截所有用/** -->
<mvc:mapping path="/**"/>
<!-- 指定干活的拦截器 -->
<bean class="com.lijinghua.interceptor.MyInterceptor" id="myInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<!-- 拦截器要拦截的请求路径 拦截所有用/** -->
<mvc:mapping path="/**"/>
<!-- 指定干活的拦截器 -->
<bean class="com.lijinghua.interceptor.MyInterceptor2" id="myInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
注意:
如果有多个拦截器的时候,preHandle按照配置前后顺序执行,postHandle、afterCompletion按照配置前后逆序执行。
10.4 拦截器和过滤器的区别
- 过滤器
- servlet规范中的一部分,任何java web工程都可以使用;
- 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截。
- 拦截器
- 拦截器是SpringMVC框架自己的,是AOP思想的具体应用 ,只有使用了SpringMVC框架的工程才能使用;
- 拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的。
11.文件的上传与下载
11.1 文件上传
文件上传是项目开发中最常见的功能之一,springMVC 可以很好的支持文件上传,这种支持是通过即插即用的MultipartResolver实现。
11.1.1 POM文件中添加依赖
Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。
在2003年,Apache Software Foundation 发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。
Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而Spring MVC则提供了更简单的封装。
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!--servlet-api导入高版本的-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
11.1.2 配置MultipartResolver
但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中(SpringMVC的配置文件)配置MultipartResolver。
<!--文件上传-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--这里可以对上传的文件做一些大小上的限制-->
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
注意!!!这个bena的id必须为 multipartResolver , 否则上传文件会报400的错误!!!
11.1.3 编写前端页面
为了能上传文件,必须将表单的method设置为POST,并将enctype设置为 multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件操作</title>
</head>
<body>
<form action="/upload.do" method="post" enctype="multipart/form-data">
请选择文件:<input type="file" name="myFile" /><br/>
<button type="submit">上传文件</button>
</form>
</body>
</html>
对表单中的 enctype 属性做个详细的说明:
application/x-www=form-urlencoded
默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
multipart/form-data
这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
text/plain
除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
11.1.4 编写后端代码
@Controller
public class FileController {
/**
* 文件上传
* @param myFile
* @param request
* @return
*/
@RequestMapping("upload.do")
public String upload(@RequestParam("myFile") MultipartFile myFile, HttpServletRequest request){
//获取文件的原始名称
String originalFilename = myFile.getOriginalFilename();
// 实际开发中,一般都要将文件重新名称进行存储
// 存储到服务器的文件名称=随机的字符串+根据实际名称获取到源文件的后缀
String fileName= UUID.randomUUID().toString().replace("-","")
+originalFilename.substring(originalFilename.lastIndexOf("."));
System.out.println(fileName);
//自定义文件存储路径
String realPath = request.getServletContext().getRealPath("/uploadFile")+"/";
try {
myFile.transferTo(new File(realPath+fileName));//真正的文件上传到服务器指定的位置
System.out.println("上传成功!"+realPath+fileName);
} catch (IOException e) {
e.printStackTrace();
}
return "ok";
}
}
11.1.5 对上传文件类型进行限制
可以配置一个拦截器,例如在文件上传之前判断文件后缀是否合法。
限制上传文件大小参见11.1.2小节,当然也可以用拦截器去限制。
public class FileInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean flag=true;
//判断是否是文件上传的请求
if(request instanceof MultipartHttpServletRequest){
MultipartHttpServletRequest multipartRequest= (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
//遍历文件
Iterator<String> iterator = fileMap.keySet().iterator();
while(iterator.hasNext()){
String key = iterator.next();
MultipartFile file = multipartRequest.getFile(key);
String originalFilename = file.getOriginalFilename();
//判断后缀是否合法
String hz = originalFilename.substring(originalFilename.lastIndexOf("."));
if(!hz.toLowerCase().equals(".png") && !hz.toLowerCase().equals(".jpg")){
request.getRequestDispatcher("/jsp/fileTypError.jsp").forward(request,response);
flag=false;
}
}
}
return flag;
}
}
11.2 文件下载
文件下载相对于文件上传前面的步骤来说,是公用的,这里就不多赘述了,直接贴上处理代码的示例。
@RequestMapping("download.do")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws IOException {
//指定文件的路径-- 要保证指定的路径下有该文件才可以
String path=request.getServletContext().getRealPath("/uploadFile")+"/4e27abf2c3724985a0877599773143c6.jpg";
//创建响应的头信息的对象
HttpHeaders headers=new HttpHeaders();
//标记以流的方式作出响应
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//以附件的形式响应给用户
headers.setContentDispositionFormData("attachment",URLEncoder.encode("4e27abf2c3724985a0877599773143c6.jpg","utf-8"));
File file=new File(path);
ResponseEntity<byte[]> resp=new ResponseEntity<>(FileUtils.readFileToByteArray(file),headers,HttpStatus.CREATED);
return resp;
}