文章目录
SpringMVC
1. SpringMVC介绍
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建Web 应用程序的全功能 MVC 模块。 使用 Spring 可插入的 MVC 架构,可以选择是使用内置的 Spring Web 框架还是Struts 这样的 Web 框架。通过策略接口,Spring 框架是高度可配置的,而且包含多种视图技术,如JavaServerPages(JSP)技术、Velocity、Tiles、iText 和 POI。Spring MVC 框架并不知道使用的视图,所以不会强迫您只使用 JSP 技术。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。
web请求过程
2. SpringMVC组件
SpringMVC中几个常用的核心组件如下:
- DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
- HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
- HandlerAdapter:通过扩展处理器适配器,支持更多类型的处理器,调用处理器传递参数等工作!
- ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
2.1 SpringMVC执行过程
SpringMVC流程:
- 用户发送请求至前端控制器 DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。HandlerMapping根据请求查找具体的Handler:HandlerExecution,HandlerExecution根据请求查找控制器,并将解析后的信息(如控制器映射)传递回DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器。HandlerAdapter经过适配调用具体的Controller处理器
- Controller执行完成后将具体的执行信息(如ModelAndView)返回给HandlerAdapter。
- HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
2.2 DispatcherServlet
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:
- 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
- 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
- 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
- 通过ViewResolver解析逻辑视图名到具体视图实现;
- 本地化解析;
- 渲染具体的视图等;
- 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。
DispatcherServlet辅助类
spring中的DispatcherServlet使用一些特殊的bean来处理request请求和渲染合适的视图。
bean类型 说明 Controller 处理器/页面控制器,做的是MVC中的C的事情,控制逻辑转移到前端控制器,用于对请求进行处理 HandlerMapping 请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器 HandlerAdapter HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且调用处理器的handleRequest方法进行功能处理 HandlerExceptionResolver
处理器异常解析器处理器异常解析,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息) ViewResolver 视图解析器 ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图 LocaleResolver &LocaleContextResolver
地区解析器和地区Context解析器解析客户端中使用的地区和时区,用来提供不同的国际化的view视图。 ThemeResolver 主题解析器,解析web应用中能够使用的主题,比如提供个性化的网页布局。 MultipartResolver 多部件解析器,主要处理multi-part(多部件)request请求,例如:在HTML表格中处理文件上传。 FlashMapManager FlashMap管理器储存并检索在「input」和「output」的FlashMap中可以在request请求间(通常是通过重定向)传递属性的FlashMap
3. SpingMVC框架搭建
1.新建一个maven的web项目,并建立好java和resources文件夹
2.在pom.xml文件中添加jar依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
3.resources文件夹下新建一个spring.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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注解扫描-->
<context:component-scan base-package="top.peng"/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- jsp所在的位置,前缀--> <!--/ 表示在根目录webapp下 -->
<property name="prefix" value="/" />
<!-- jsp文件的后缀名-->
<property name="suffix" value=".jsp" />
</bean>
</beans>
4.在web.xml文件中添加以下内容
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
/
和/*
的区别
< url-pattern > / </ url-pattern >
不会匹配到*.jsp
,即:*.jsp
不会进入spring的DispatcherServlet类 。
< url-pattern > /* </ url-pattern >
会匹配*.jsp
,会出现返回jsp视图时再次进入spring的DispatcherServlet 类,导致找不到对应的controller报404错。可以配置
/
,此工程所有请求全部由springmvc解析,此种方式可以实现 RESTful方式,需要特殊处理对静态文件的解析不能由springmvc解析。可以配置.do或.action,所有请求的url扩展名为.do或.action由springmvc解析,此种方法常用
不可以
/*
,如果配置/*
,返回jsp也由springmvc解析,这是不对的。url-pattern有5种配置模式
(1)
/xxx
:完全匹配/xxx
的路径(2)
/xxx/*
:匹配以/xxx
开头的路径,请求中必须包含xxx。(3)
/*
:匹配/
下的所有路径,请求可以进入到action或controller,但是转发jsp时再次被拦截,不能访问jsp界面。(4)
.xx
:匹配以xx
结尾的路径,所有请求必须以.xx
结尾,但不会影响访问静态文件。(5)
/
:默认模式,未被匹配的路径都将映射到servlet。jpg,js,css等静态文件也被拦截,不能访问。
5.创建控制器类
注解
@Controller
创建控制器对象
@RequestMapping("请求地址")
加在类上: 给模块添加请求根路径
加载方法上: 方法具体的请求路径
设置method属性,即设置请求方式,取值如下。
@RequestMapping(method=RequestMethod.GET,value="请求名")
各请求方式的含义可以看我之前这篇。
6.配置Tomcat
7.运行测试
前端代码如下:
4. SpringMVC数据传递
4.1 后台从前台接收参数
springMVC中接收参数的方式有以下几种
-
通过
HttpServletRequest
的getParameter()
方法 -
页面传值时的key=处理请求的方法的参数名
如请求为
/login?username=abc&password=123
那么处理该请求的方法可以定义为:@RequestMapping(value = "/login") public String login(String username,String password){ //TODO:业务逻辑 }
方法的参数名与传参的name值不同
如请求为
/login?name=abc&password=123
,页面传的参数的key分别为name
和password
,方法的参数我们用的是username
和password
,这时我们可以通过@RequestParam(value = "name")
来绑定参数name。public String login(@RequestParam(value="name") String username,String password){}
@RequestParam()
还可以设置默认值:@RequestParam(defaultValue = "1")
注意参数的类型问题,避免给定的参数不能由框架转换成目标格式而引起的错误。
-
使用控件名和对象的属性名一致的方式进行接收(常用)
如请求为
/login?username=abc&password=123
,username和password都是user对象里的属性,可以通过下面的方式接收到参数@RequestMapping(value = "/login") public String login(User user){ //TODO:业务逻辑 String username = user.getUsername(); //得到参数username String password = user.getPassword(); //得到参数password }
springMVC中接收参数中有日期应该如何处理?
springmvc框架默认支持转换得日期格式为:yyyy/MM/dd
@RequestMapping(value = "/test") public String test(Date birthday){ System.out.println("Date:"+birthday); //返回结果页面的名称 return "success"; }
如果前端传了yyyy-MM-dd格式的日期,就会报错。
解决日期问题方式:
使用string接收日期,接收后,再转换: SimpleDataFormate
使用工具类处理日期
- 添加日期处理工具包
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.9</version> </dependency>
- 在spring.xml文件中添加下面代码,注意配置mvc命名空间
<mvc:annotation-driven/>
- 在对应的处理方法中关于日期的参数前用
@DateTimeFormat(pattern = "前端传的日期的格式")
@RequestMapping(value = "/test") public String test(@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday){ System.out.println("Date:"+birthday); //返回结果页面的名称 return "success"; }
tips:参数类型使用引用数据类型,基本数据类型使用包装类
4.2 后台返回参数给前台
获得返回参数需要保证项目可以使用Jsp的EL表达式,修改web.xml 文件,删除2.3的版本,改为2.5,即在<web-app>
标签内添加以下代码。
<!-- 删除这里 -->
<!-- <!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 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
springMVC中返回数据给前端的方式有以下几种
-
通过
HttpServletRequest
的setAttribute("key",value)
方法,将后台数据存储到request域,前台用EL表达式展示数据。注意:由于数据存储在request域,页面重定向时会丢失数据。 -
使用
ModelMap
的addAttribute("key",value)
方法存储后台数据 ,默认存储在request域。 -
使用
Model
的addAttribute("key",value)
方法存储后台数据 -
通过
ModelAndView
对象存数据,同时作为返回值类型,如下代码所示。@RequestMapping(value = "/test") public ModelAndView test(@DateTimeFormat(pattern = "yyyy-MM-dd")Date birthday){ System.out.println("Date:"+birthday); ModelAndView modelAndView = new ModelAndView(); //设置跳转页面 modelAndView.setViewName("success"); //存值 modelAndView.addObject("mvKey",birthday); //返回页面和数据模型 return modelAndView; }
总结:
Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;
ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;
ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。
4.3 Session存值
SpringMVC中使用session的方式有以下几种:
-
使用
HttpSession
(request.getSession()
)的setAttribute("key",value)
方法存值 -
在类上使用注解
@sessionAttributes("key值")
key是ModelMap中定义的key值注:该注解和ModelMap结合使用,当使用ModelMap存值时,会在session中同时存储一份数据
@SessionAttributes()
的小括号中如果是多个值,加{},清除注解(session)使用SessionStatus
的setComplete()
方法。示例:@SessionAttributes("sessionMap") public class MyController { @RequestMapping(value = "/login") public String login(String username,String password,ModelMap map){ map.addAttribute("sessionMap",username); //返回结果页面的名称 return "success"; } @RequestMapping(value = "/out") public String out(SessionStatus status){ status.setComplete(); //清除注解session return "login"; } } //有多个值存入session的写法 @SessionAttributes({"key1","key2"})
4.4 Cookie、请求头的获取
@CookieValue
注解可以获取请求中的cookie
@RequestMapping(value = "/test")
public String testCookie(@CookieValue("JSESSIONID")String cookie){
System.out.println("cookie:"+cookie);
return "result";
}
@RequestHeader
注解可以获取请求头中的数据
@RequestMapping(value = "/test")
public String testHeader(@RequestHeader("User-Agent")String header){
System.out.println("cookie:"+cookie);
return "result";
}
请求头中包含如下信息,都可以通过
@RequestHeader
注解获取
4.5 弹窗响应(后台给前台返回一个弹窗)
假设一个场景:用户点击退出时,弹出一个退出成功的提示框,然后跳转页面。那这个弹窗如何实现呢?
@RequestMapping(value = "/out")
public void out(SessionStatus status, HttpServletResponse resp) throws IOException {
status.setComplete(); //清除session
resp.setContentType("text/html;charset=UTF-8"); //指定编码
//响应流
PrintWriter writer = resp.getWriter();
writer.print("<script>alert('退出成功');location.href='login.jsp'</script>");//js代码
}
springMVC中处理post请求发送数据的乱码问题
在web.xml文件中加入springMVC定义的CharacterEncodingFilter过滤器。
<filter> <filter-name>charset</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>charset</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
4.6 结果跳转方式
4.6.1 ModelAndView方式
设置ModelAndView对象 ,根据view的名称 ,和视图解析器跳到指定的页面 。
页面 : {视图解析器前缀} + viewName +{视图解析器后缀}
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
@Controller
public class myController{
@RequestMapping(value = "/test")
public ModelAndView test() throws Exception {
//返回一个模型视图对象
ModelAndView mv = new ModelAndView();
mv.addObject("msg","ControllerTest");//存值
mv.setViewName("success"); //跳转到success.jsp
return mv;
}
}
4.6.2 ServletAPI方式
通过设置ServletAPI ,不需要视图解析器 。
-
通过HttpServletResponse进行输出
-
通过HttpServletResponse实现重定向
-
通过HttpServletResponse实现转发
@Controller
public class myController {
@RequestMapping("/test1")
public void test1(HttpServletRequest req, HttpServletResponse resp) throws IOException{
rsp.getWriter().println("Hello,Spring BY servlet API");
}
@RequestMapping("/test2")
public void test2(HttpServletRequest req, HttpServletResponse resp) throws IOException{
rsp.sendRedirect("/success.jsp"); //重定向
}
@RequestMapping("/test3")
public void test3(HttpServletRequest req, HttpServletResponse resp) throws Exception{
req.getRequestDispatcher("/success.jsp").forward(req,resp);//转发
}
}
4.6.3 SpringMVC转发和重定向
通过SpringMVC来实现转发和重定向 - 无需视图解析器;return的页面这里将路径写全,注意测试时将视图解析器注释掉。
@Controller
public class MyController {
@RequestMapping("/test1")
public String test1(){
return "/index.jsp"; //转发
}
@RequestMapping("/test2")
public String test2(){
return "forward:/index.jsp"; //转发
}
@RequestMapping("/test3")
public String test3(){
return "redirect:/success.jsp"; //重定向
}
}
通过SpringMVC来实现转发和重定向 - 有视图解析器;
重定向会忽略视图解析器 ,本质就是重新请求一个新地方,所以注意路径问题。
也可以重定向到另外一个请求实现 。
@Controller
public class MyController {
@RequestMapping("/test1")
public String test1(){
return "success"; //默认转发,走视图解析器
}
@RequestMapping("/test2")
public String test2(){
return "redirect:/success.jsp"; //重定向,不走视图解析器,注意路径问题。
//return "redirect:hello.do"; //hello.do为另一个请求/
}
}
4.7 异常处理
springMVC中我们可以很方便的将错误都跳转到一个error页面,比如404页面
方法1:在web.xml响应状态码配置一个对应页面
<error-page>
<error>404</error>
<location>/404.html</location>
</error-page>
方法2:在Controller类中定义一个发生就跳转到异常结果界面的方法,该方法加@ExceptionHandler
注解
@ExceptionHandler(Exception.class)
public String error(){
return "error"; //跳转异常页面
}
@ControllerAdvice
注解在一个Contoller类上添加
@ControllerAdvice
注解可以使这个类成为全局异常处理类,类中用@ExceptionHandler
方法注解的方法可以处理所有Controller发生的异常
4.8 SpringMVC访问静态资源
需要注意:DispatcherServlet拦截资源设置成了/
避免了死循环, /
不拦截jsp资源,但是它会拦截其他静态资源,例如 html,js,css,image等等, 那么我们在使用jsp内部添加静态资源就无法成功,所以我们需要单独处理下静态资源!
处理方案:
方式1: 在springmvc的配置文件中添加mvc命名空间下的<mvc:resources>
标签!指定静态资源路径
-
修改Spring MVC对应配置文件,添加mvc命名空间和约束
xmlns:mvc="http://www.springframework.org/schema/mvc" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
-
添加处理标签
<mvc:resources>
<mvc:annotation-driven /> <!--注解驱动--> <!--/**表示当前目录及其子目录 --> <mvc:resources mapping="/img/**" location="/images/" ></mvc:resources> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:resources mapping="/html/**" location="/html"/>
方式2: 在springmvc的配置文件中添加mvc命名空间下的<mvc:default-servlet-handler>
标签!统一配置
这种方式简单实用,比较常用
<mvc:default-servlet-handler></mvc:default-servlet-handler>
5. RestFul风格
5.1 概述
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
资源:互联网所有的事物都可以被抽象为资源
资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
它们分别代表着四种基本操作:
- GET用来获取资源
- POST用来创建新资源
- PUT用来更新资源
- DELETE用来删除资源
传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get
http://127.0.0.1/item/queryItem.action?id=1 查询,GET
http://127.0.0.1/item/addItem.action 新增,POST
http://127.0.0.1/item/updateItem.action 更新,POST
http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST
使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!
http://127.0.0.1/item/1 查询,HTTP GET :得到id = 1 的 item
http://127.0.0.1/item/ 新增,HTTP POST : 新增 item
http://127.0.0.1/item 更新,HTTP PUT : 更新item
http://127.0.0.1/item/1 删除,HTTP DELETE:删除 id=1 的item
5.2 Spring中实现RESTful风格
1.web.xml添加HiddenHttpMethodFilter过滤器。
浏览器form表单只支持GET和POST,不支持DELETE和PUT请求,Spring添加了一个过滤器,可以将这些请求转换为标准的http方法,支持GET、POST、DELETE、PUT等请求!
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>
org.springframework.web.filter.HiddenHttpMethodFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.编写controller
@Controller
public class RestFulController {
@RequestMapping(value = "/testRest",method = RequestMethod.GET)
public String get(){
System.out.println("get请求");
return "get_success";
}
@RequestMapping(value = "/testRest",method = RequestMethod.POST)
public String post(){
System.out.println("post请求");
return "post_success";
}
@RequestMapping(value = "/testRest",method = RequestMethod.PUT)
public String put(){
System.out.println("put请求");
return "put_success";
}
@RequestMapping(value = "/testRest",method = RequestMethod.DELETE)
public String delete(){
System.out.println("delete请求");
return "delete_success";
}
}
jsp页面代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/testRest" method="get">
<input type="submit" value="get"/>
</form>
<form action="/testRest" method="post">
<input type="submit" value="get"/>
</form>
<!--由于doFilterInternal方法只对method为post的表单进行过滤,所以在页面中必须如下设置:代表post请求,但是HiddenHttpMethodFilter将把本次请求转化成标准的put请求方式! name="_method"固定写法!-->
<form action="/testRest" method="post">
<!--注意:这里name必须是"_method",不可修改,HiddenHttpMethodFilter中会根据这个name来转换请求-->
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="put"/>
</form>
<form action="/testRest" method="post">
<input type="hidden" name="_method" value="DELETE">
<input type="submit" value="delete"/>
</form>
</body>
</html>
这样我们就可以通过/testRest
这一个请求完成不同的操作。
5.3 RESTful传值
可以通过 @PathVariable
注解,让方法参数的值对应绑定到一个URI模板变量上。
@Controller
public class RestFulController {
//映射访问路径
@RequestMapping("/commit/{p1}/{p2}")
public String index(@PathVariable int p1, @PathVariable("p2") int par2, Model model){
int result = p1+p2;
//Spring MVC会自动实例化一个Model对象用于向视图中传值
model.addAttribute("msg", "结果:"+result);
//返回视图位置
return "test";
}
}
我们来测试请求查看下
思考:使用路径变量的好处?
-
使路径变得更加简洁;
-
获得参数更加方便,框架会自动进行类型转换。
-
通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径是/commit/1/a,则路径与方法不匹配,而不会是参数转换失败。
我们来修改下对应的参数类型,再次测试
//映射访问路
@RequestMapping("/commit/{p1}/{p2}")
public String index(@PathVariable int p1, @PathVariable String p2, Model model)
String result = p1+p2;
//Spring MVC会自动实例化一个Model对象用于向视图中传值
model.addAttribute("msg", "结果:"+result);
//返回视图位置
return "test"
}
5.4 小结
Spring MVC的 @RequestMapping
注解能够处理HTTP请求的方法,比如 GET,PUT,POST,DELETE 以及 PATCH等。
**所有的地址栏请求默认都会是 HTTP GET 类型的。**看下面例子
//映射访问路径,必须是POST请求
@RequestMapping(value = "/hello",method = {RequestMethod.POST})
public String index2(Model model){
model.addAttribute("msg", "hello!");
return "test";
}
我们使用浏览器地址栏进行访问默认是Get请求,会报错405:
如果将POST修改为GET则正常了;
//映射访问路径,必须是Get请求
@RequestMapping(value = "/hello",method = {RequestMethod.GET})
public String index2(Model model){
model.addAttribute("msg", "hello!");
return "test";
}
方法级别的注解变体有如下几个:组合注解
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@GetMapping 是一个组合注解,平时使用的会比较多。
它所扮演的是
@RequestMapping(method=RequestMethod.GET)
的一个快捷方式。
6. SpringMVC中使用Json和Ajax
1.首先我们需要导json依赖包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
2.创建一个User类
public class User {
private String username;
private int age;
//constructor getter setter toString等
}
3.创建Controller
在需要转换JSON数据的方法上添加@ResponseBody
注解,该注解会自动将我们返回的数据解析为Json格式
public class UserController {
@RequestMapping(value = "/getUser")
@ResponseBody
public User getUser() {
User user = new User("张三",20);
return user;
}
@RequestMapping(value = "/getUsers")
@ResponseBody
public List<User> getUsers() {
List<User> list = new ArrayList<>();
list.add(new User("张三",20));
list.add(new User("李四",20));
list.add(new User("王五",20));
return list;
}
}
4.启动Tomcat,访问对应请求
- http://localhost:8080/getUser
- http://localhost:8080/getUsers
返回Json数据的另一种方式,使用ObjectMapper对象,具体用法如下:
注意:这种方法会出现乱码,可以在
@RequestMapping
中添加produces
指定编码和返回类型。每次都指定比较麻烦,统一处理乱码问题见下面链接。
@Controller public class UserController { @RequestMapping(value = "/getUser2",produces = "application/json;charset=utf-8") @ResponseBody public String getUser() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user = new User("张三", 20); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(user); //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便 return str; } }
在类上直接使用
@RestController
注解 ,这样,类里面所有的方法都只会返回 json字符串,不用再每一个方法都添加@ResponseBody
。我们在前后端分离开发中,一般都使用@RestController
,十分便捷!@RestController public class UserController { @RequestMapping(value = "/getUser") public User getUser() { //code } @RequestMapping(value = "/getUsers") public List<User> getUsers() { //code } }
5.写一个jsp页面getUsers.jsp,通过Ajax来展示后台得到的Json数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<input type="button" id="btn" value="获取数据"/>
<table id="table" width="100px" border="1" style="border-collapse: collapse; ">
<tr>
<td>姓名</td>
<td>年龄</td>
</tr>
<tbody id="content">
</tbody>
</table>
<!--引入jquery文件,注意配置静态资源的访问-->
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.min.js"></script>
<script>
$(function () {
$("#table").hide();
$("#btn").click(function () {
$("#table").show();
$.post("/getUsers",function (data) {
console.log(data)
var html="";
for (var i = 0; i <data.length ; i++) {
html+= "<tr>" +
"<td>" + data[i].username + "</td>" +
"<td>" + data[i].age + "</td>" +
"</tr>"
}
$("#content").html(html);
},"JSON");
})
})
</script>
</body>
</html>
启动Tomcat,访问http://localhost:8080/getUsers.jsp
SpringMVC使用Ajax更多操作看下面链接
7. SpirngMVC拦截器
7.1 概述
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。
过滤器与拦截器的区别:拦截器是AOP思想的具体应用。
过滤器
- servlet容器规范中的一部分,任何java web工程都可以使用
- 在实现上基于函数回调,可以对几乎所有请求进行过滤
- 一个过滤器实例只能在容器初始化时调用一次
- 在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截
拦截器
- 拦截器是依赖于web框架,是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
- 在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。
- 一个拦截器实例在一个controller生命周期之内可以多次调用。
- 拦截器只会拦截访问的控制器方法, 如果访问的是jsp/html/css/image/js是不会进行拦截的
7.2 自定义拦截器的使用
想要自定义拦截器,必须实现 HandlerInterceptor
接口,重写以下三个方法。
preHandle()
拦截器开始postHandle()
拦截器结束afterCompletion()
最后执行
public class MyInterceptor implements HandlerInterceptor {
//在请求处理的方法之前执行
//如果返回true执行下一个拦截器
//如果返回false就不执行下一个拦截器
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
System.out.println("------------处理前,拦截开始------------");
return true;
}
//在请求处理方法执行之后执行
public void postHandle(HttpServletRequest req,HttpServletResponse resp,Object handler, ModelAndView mv) throws Exception {
System.out.println("------------处理后,拦截结束------------");
}
//在DispatcherServlet处理后执行,做清理工作。
public void afterCompletion(HttpServletRequest req,HttpServletResponse resp,Object handler, Exception e) throws Exception {
System.out.println("------------清理,无论如何都会执行------------");
}
}
然后在springmvc的配置文件中配置拦截器
<!--关于拦截器的配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--指定需要拦截的请求 /** 包括当前路径及其子路径 /*不包含子路径-->
<mvc:mapping path="/请求名"/>
<mvc:mapping path="/请求根路径名/**"/>
<mvc:mapping path="/请求名根路径名/*"/>
<!--bean配置的就是要执行拦截操作的拦截器类的全路径-->
<bean class="top.peng.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
配置完成后就可以对请求进行拦截了
注:如果使用MyInterceptor类拦截所有请求,可以简单配置为:
<mvc:interceptors>
<!--bean配置的就是要执行拦截操作的拦截器类的全路径-->
<bean class="top.peng.interceptor.MyInterceptor"/>
</mvc:interceptors>
比如我们去访问上面的getUsers请求。
我们在UserController类的getUsers()方法中加一个输出语句
public List<User> getUsers() {
System.out.println("执行/getUsers请求...");
List<User> list = new ArrayList<>();
list.add(new User("张三",20));
list.add(new User("李四",20));
list.add(new User("王五",20));
return list;
}
看一下执行结果:
多个拦截器的执行顺序效果
配置如下,拦截器1在上
<mvc:interceptors>
<!--bean配置的就是要执行拦截操作的拦截器类的全路径-->
<bean class="top.peng.interceptor.MyInterceptor1"/>
<bean class="top.peng.interceptor.MyInterceptor2"/> <!--拦截器栈-->
</mvc:interceptors>
符合栈的特性,先进后出,先开始的后结束,所以也叫拦截器栈。
多个过滤器与拦截器的代码执行顺序
- 过滤器的运行是依赖于servlet容器的,跟springmvc等框架并没有关系。并且,多个过滤器的执行顺序跟web.xml文件中定义的先后关系有关
- 对于多个拦截器它们之间的执行顺序跟在SpringMVC的配置文件中定义的先后顺序有关
拦截器的使用场景
- 日志记录:记录请求信息的日志
- 权限检查:如登录检查
- 性能检测:检测方法的执行时间
8. SpringMVC文件上传下载
在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而SpringMVC则提供了更简单的封装。
SpringMVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver实现。
SpringMVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。
在SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要先在上下文中配置MultipartResolver。
使用文件上传对前端表单也有要求:为了能上传文件,必须将表单的method设置为POST(因为get请求是有长度限制的),并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
对表单中的 enctype 属性做个详细的说明:
- application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
- multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
- text/plain:除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
<form action="" enctype="multipart/form-data" method="post"> <input type="file" name="file"/> <input type="submit"> </form>
8.1文件上传和下载步骤
8.1.1. 添加JAR包
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!--文件下载-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
8.2.2. 在SpringMVC的配置文件中配置MultipartResolver对象
注意:这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--请求编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1-->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(5242880=5M) -->
<property name="maxUploadSize" value="5242880"/>
</bean>
8.2.3. 编写代码
8.2.3.1 文件上传
前端代码
<form action="/upload" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="file">
<input type="submit" value="上传">
</form>
编写Controller
@Controller
public class FileController {
@RequestMapping(value = "/upload")
public String upload(@RequestParam("file")MultipartFile file, HttpServletRequest req) {
//上传路径设置,文件上传到项目根目录的upload文件夹内
String path=req.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
//上传并保存到指定路径
if (!file.isEmpty())
{
try {
//file.getOriginalFilename()获取上传的文件的原名
file.transferTo(new File(realPath+"/"+file.getOriginalFilename()));
} catch (IOException e) {
e.printStackTrace();
}
}
//测试:将文件名存入request域,在成功页面展示。
req.setAttribute("filename",file.getOriginalFilename());
return "success";
}
}
success界面
<h1>文件${filename}上传成功</h1>
<img src="upload/${filename}" width="288px" height="180px">
看一下效果:
8.2.3.2文件下载
在fileController中添加一个download方法
@RequestMapping(value = "/download")
public ResponseEntity<byte[]> download(String filename, HttpServletRequest req) throws IOException {
//获取文件在服务器中的路径
String realPath = req.getServletContext().getRealPath("/upload");
//要下载的文件地址
String filePath = realPath+"/"+filename;
//设置响应头信息
HttpHeaders headers = new HttpHeaders();
//以流的形式传输数据
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//attachment表示将文件以附件的形式响应给客户端
headers.setContentDispositionFormData("attachment",URLEncoder.encode(filename,"UTF-8"));
File file = new File(filePath);
//将文件进行返回
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
return responseEntity;
}
我们在上面的文件上传的成功的界面(success.jsp)加一个下载链接
<h1>文件${filename}上传成功</h1>
<img src="upload/${filename}" width="288px" height="180px">
<!--添加下面代码-->
<a href="/download?filename=${filename}">下载</a>
看下效果:
CommonsMultipartFile 的 常用方法:
方法名称 解释 byte [] getBytes()
获取文件数据 String getContentType()
获取文件MIMETYPE类型,如image/jpeg,text/plain等 InputStream getInputStream()
获取文件输入流 String getName()
获取表单中文件组件的名称 name值 String getOriginalFilename()
获取上传的文件的原名 long getSize()
获取文件的字节大小,单位为byte boolean isEmpty()
是否有上传的文件 void transferTo(File dest)
将上传的文件保存到指定的文件路径中