SpringMVC总结
什么是SpringMVC?
springMVC全称是spring web mvc,是Spring框架的一部分,是一个MVC模式的WEB开发框架,使用了MVC架构模式的思想,将web层进行职责解耦。
SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring,springmvc仅给spring的表现层提供支持。
MVC是什么?
MVC(Model-View-Controller,模型-视图-控制器)指把页面、后台的交付分成3层来组成,是一种解决页面代码和后台代码分离的设计思想!
MVC里面的M指的是Model(通常包含bean、dao(mapper)、service);V指的是View,视图层,视图层主要的技术(JSP、HTML);C指的是Controller,控制层。控制层不负责具体数据、逻辑的处理和运算,它只负责将Model层的结果返回给对应的视图层去展示。
在JavaWeb阶段, Controller层指的就是Servlet; View层指的就是JSP或者HTML; Model层指的就是bean、dao、service。
在J2EE阶段,Controller层指的就是SpringMVC、Structs1\2; View层不变还是主流的页面展示技术; Model层包括bean、mybatis、service。
为什么要用SpringMVC?
基本上,框架的作用就是用来简化编程的,相对于servlet/Jsp来说,获取表单参数,响应请求等变得更简单了。
如何使用SpringMVC?
1.添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!--可选 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
2.在web.xml中配置DispatcherServlet
(web项目,无论使用什么框架,入口总是web.xml。
由原生的web框架,交给SpringMVC的DispatcherServlet去处理请求
DispatcherServlet是SpringMVC的核心控制器,就像是SpringMVC的心脏,几乎所有的请求都会经过这个控制器,请求处理完响应也要经过它
DispatcherServlet 怎么找到可以处理请求的方法呢,HandlerMapping处理器映射器,记录有什么方法可以处理什么请求)
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定SpringMVC 配置文件位置,DispatcherServlet初始化时会初始化Spring上下文(WebApplicationContext) -->
<!-- 默认配置文件寻找位置:/WEB-INF/{servlet-name}-servlet.xml,如果名字符合默认寻找规则,可以不指定配置文件路径 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<!-- 配置容器启动时初始化DispatcherServlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!-- 映射路径配置成/,代表当前Servlet是一个默认Servlet,就是当其他Servlet都无法处理请求时,由默认Servlet处理 -->
<!-- 它将拦截所有其他Servlet都无法拦截的请求,例如:/user/add、/user/test/aa.js,弊端:js,弊端:对jpg,js,css静态文件的访问也被拦截不能正常显示(因为它覆盖了Tomcat给我们提供的专门用于处理静态资源的Servlet -->
<url-pattern>/</url-pattern>
</servlet-mapping>
注意: 上述 DispatcherServlet 的 servlet 对象 初始化时将在应用程序的 WEB-INF 目录下查找一个配置文件,该配置文件的命名规则是“{servlet-name}-servlet.xml”,例如 dispatcher-servlet.xml。
另外,也可以将 Spring MVC 配置文件存放在应用程序目录中的任何地方,但需要使用 servlet 的 init-param 元素加载配置文件
3.配置SpringMVC dispatcher-servlet.xml
SpringMVC大部分组件都有默认配置,我们一般简单应用只需要指定视图解析器就行了
<!--配置视图解析器,将请求处理方法中返回的值解析成一个真正可以渲染的页面-->
<mvc:view-resolvers>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>
</mvc:view-resolvers>
4.定义Controller
新建一个普通类,然后添加@Controller
注解
@Controller //通过@Controller标注的类会被SpringIOC容器扫描和管理
public class UserController {
@RequestMapping("/helloPage")
public String ToHelloPage(@RequestParam String message,Model model){
model.addAttribute("msg",message);
return "hello"; //请求对应的页面
}
}
5.定义请求处理方法(Handler)
在Controller类中定义一个普通的方法,添加@RequestMapping
注解.
SpringMVC主要组件
Root WebApplicationContext和Servlet WebApplicationContext
参考官网的一张图:
两个ApplicationContext的联系和区别
RootApplicationContext如applicationContext.xml文件
ServletApplicationContext如dispatcher-servlet.xml文件
二者形成了父子关系的上下文。
applicationContext.xml 是spring 全局配置文件,用来控制spring 特性的
dispatcher-servlet.xml 是spring mvc里面的,可配置视图解析器。
常用知识点
定义controller
新建一个普通类,然后添加@Controller
注解
@Controller
@RequestMapping("/param")
public class ParamController{
}
处理请求
在Controller的方法上添加@RequestMapping注解
@RequestMapping("/hello")
public String hello() {
return "hello";
}
获取请求参数
@RequestMapping("/request_param")
public String testRequestParam(@RequestParam String name, Model model){
model.addAttribute("type","请求参数");
model.addAttribute("name",name);
return "param-show";
}
获取请求头参数
@RequestMapping("/request_header")
public String testRequestHeader(@RequestHeader String name,Model model ){
model.addAttribute("type","请求头参数");
model.addAttribute("name",name);
return "param-show";
}
获取Cookie数据
@RequestMapping("/cookie")
public String testCookie(@CookieValue String name,Model model){
model.addAttribute("type","cookie参数");
model.addAttribute("name",name);
return "param-show";
}
获取Session作用域数据
@RequestMapping("/session_scope")
public String testSessionAttribute(@SessionAttribute String name, Model model){
model.addAttribute("type","会话作用域参数");
model.addAttribute("name",name);
return "param-show";
}
获取request作用域数据
@RequestMapping("/request_scope")
public String testRequestAttribute(@RequestAttribute String name,Model model){
model.addAttribute("type","请求作用域参数");
model.addAttribute("name",name);
return "param-show";
}
param_req.jsp
请求参数测试<br>
<%--GET请求中的查询参数--%>
<a href="/param/request_param?name=芒种">传递请求参数</a><br>
<%-- POST请求中的参数 --%>
<form action="/param/request_param" method="post">
<%--<input type="hidden" name="name" value="谁">--%>
<input type="text" name="name">
<input type="submit" value="提交">
</form>
<hr>
请求作用域参数传递:<br />
<%
request.setAttribute("name","zhangsan");
request.getRequestDispatcher("/param/request_scope").forward(request, response);
%>
<hr />
会话作用域参数传递:<br />
<%
session.setAttribute("sname", "lisi");
%>
<a href="/param/session_scope">发起请求</a>
<hr />
cookie参数传递:<br />
<%
response.addCookie(new Cookie("sname", "wangwu"));
%>
<a href="/param/cookie">发起请求</a>
param-show.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>参数显示页面</title>
</head>
<body>
展示${type}: <br>
name:${name}
</body>
</html>
后端参数传递到前端
Model
handler方法定义:
@RequestMapping("/show_msg")
public String showMessage(Model model) {
model.addAttribute("msg", "我是通过model传过来的参数");
return "message_page";
}
message_page.jsp
<h1>
获取后台参数: ${msg}
</h1>
ModelAndView
/**
* ModelAndView其实是将数据Model和视图View做了一个封装,底层实现原理一样
*/
@RequestMapping("/show_msg")
public ModelAndView showMessage(ModelAndView mv) {
mv.addObject("msg", "我是通过model传过来的参数");
mv.setViewName("message_page");
return mv;
}
Model和ModelAndView传递的参数本质上也是用请求作用域来实现的,所以仅对请求转发有效。
视图控制器
如果我们有些请求只是想跳转页面,不需要来后台处理什么逻辑,用于通过配置的方式简化我们项目中不含业务逻辑的页面跳转,省去了我们写一个空方法的步骤。
视图控制器的作用和用法
试想一下,如果我们的工程中有这样的一个需求,比如当用户点了某个链接和按钮的时候,我们需要做一个页面跳转,这个跳转的目标可能是一个公开目录的页面也可能是私有目录(/WEB-INF/下)的页面、还可能是跳转到后台Controller的某个方法中,我们如何实现?
示例:比如我们想将登陆页设置为一个项目默认欢迎页面
方案一:在Controller中添加一个拦截/的handler方法
@RequestMapping("/")
public String toWelcomePage() {
return "login";
}
方案二:使用视图控制器
在dispatcher-servlet.xml(SpringMVC配置文件)配置中添加
<mvc:view-controller path="/" view-name="login" />
重定向
public class UserController {
@Autowired
private UserService userService;
/**
* 定义一个Handler, 可以处理http://localhost:8080/userList这个请求
* Controller 调用业务逻辑处理用户请求后返回 ModelAndView(返回值)。
*/
@RequestMapping("/userList")
public String ToUserList(Model model){
List<User> userList = userService.queryUser();
model.addAttribute("userList",userList);
// return "redirect:userlist.jsp"; //添加redirect前缀,变成重定向,地址栏会变,但仍然不经过视图解析器
}
/* @RequestMapping("/userList")
public View ToUserList(Model model){
List<User> userList = userService.queryUser();
model.addAttribute("userList",userList);
return new RedirectView("userlist.jsp"); //使用重定向的方式跳转页面,Model中的数据就无法带到页面了
}
*/
}
请求转发
public String ToUserList(Model model){
List<User> userList = userService.queryUser();
model.addAttribute("userList",userList);
//return "userlist"; 直接返回逻辑视图名,此类情况相当于forward,点击首页对应链接,进入的是handler方法,并没有进入某个页面,地址栏没换成展示列表另一页面
//return "forward:userlist"; //跳过视图解析器这一环,直接到@RequestMapping("/userlist")注解标注的方法内(如果没有此方法,报404错误),带着数据展示页面
return "forward:userlist.jsp"; //直接到展示页面
}
/* @RequestMapping("/userList")
public InternalResourceView ToUserList(Model model){
List<User> userList = userService.queryUser();
model.addAttribute("userList",userList);
return new InternalResourceView("userlist.jsp"); //显示数据列表
}*/
放行静态资源
为什么静态资源无法访问?
默认Tomcat是可以处理静态资源的(通过DefaultServlet处理,在%tomcat_home%/conf/web.xml中),但是我们配置了SpringMVC后,往往会将DispatcherServlet的拦截路径设置成"/",会将Tomcat中默认的DefaultServlet覆盖掉,所以静态资源也会进入DispatcherServlet中处理,而默认DispatcherServlet中是没有处理静态资源相关的逻辑,所以访问静态资源就会报404
针对静态资源的处理
<!-- http://localhost:8080/static/css/index.css -->
<mvc:resources mapping="/static/**" location="/static/" />
文件上传
SpringMVC提供了一个MultipartResolver接口用于支持文件上传。 MultipartResolver有两个实现类分别基于apache file-upload包和Serlvet3.0 API方式的文件上传。他们是:
-
CommonsMultipartResolver (2.5)
-
StandardServletMultipartResolver(3.0)
第一种:
<!-- 配置基于apache fileupload的通用文件上传器(注意:id属性不可省略) -->
<!--<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
</bean>-->
CommonsUploadController.java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
@RequestMapping("/commons")
public class CommonsUploadController {
@RequestMapping("/upload")
public String upload(@RequestParam("myFile") MultipartFile file, HttpServletRequest request, Model model){
try {
file.transferTo(new File(WebUtils.getRealPath(request.getServletContext(),"/upload/")+file.getOriginalFilename()));
System.out.println(WebUtils.getRealPath(request.getServletContext(),"/upload/"));
} catch (IOException e) {
e.printStackTrace();
}
StringBuilder sb = new StringBuilder();
sb.append("http://localhost:8080/upload/");
sb.append(file.getOriginalFilename());
model.addAttribute("filePath", sb.toString());
model.addAttribute("fileName", file.getOriginalFilename());
return "filelist";
}
}
第二种:
<!-- 配置基于Servlet3.0方式的文件上传器(注意:id属性不可省略) -->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
<property name="resolveLazily" value="true" />
</bean>
StandardUploadController.java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
@RequestMapping("/standard")
public class StandardUploadController {
//使用Servlet3.0 SpringMVCAPI上传
@RequestMapping("/upload")
public String upload(@RequestParam("myFile") MultipartFile file, HttpServletRequest request, Model model){
try {
file.transferTo(new File(WebUtils.getRealPath(request.getServletContext(),"/upload/")+file.getOriginalFilename()));
System.out.println(WebUtils.getRealPath(request.getServletContext(),"/upload/"));
} catch (IOException e) {
e.printStackTrace();
}
StringBuilder sb = new StringBuilder();
sb.append("http://localhost:8080/upload/");
sb.append(file.getOriginalFilename());
model.addAttribute("filePath", sb.toString());
model.addAttribute("fileName", file.getOriginalFilename());
return "filelist";
}
消息转换器
什么是消息转换器?
之前我们在写普通Controller时,handler方法的返回值要是就是一个String(逻辑视图名),要么就是一个ModelAndView、View对象。
而我们使用了RestController之后,方法的返回值已经不会被作为视图去渲染了,这时候我们的方法其实可以返回任意类型的数据。这些数据会直接通过响应体以流的方式返回给客户端。
我们知道Java中的对象是不能直接用流的方式读取的,需要序列化。比如我们的handler方法返回了一个User类型,SpringMVC就不知道如果将这个类型返回给客户端了。这时就需要我们通过配置消息转换器来完成这种类型对象的返回处理。 而在RESTFull服务中,对象绝大部分传递方式就是通过JSON格式。
如何配置一个转换JSON格式的消息转换器
-
使用SpringMVC默认的Jackson库
-
引入依赖
<!-- jackson依赖,用于将handler方法返回的对象直接转换成JSON数据 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.7</version> </dependency>
-
开启mvc注解支持
<mvc:annotation-driven />
-
定义Controller类
@RestController @RequestMapping("/user") public class UserRestController { @GetMapping("/{id}") public User getUserById(Integer id) { return userService.findById(id); } }
Jackson是SpringMVC默认的JSON格式消息转换器, 所以在不配置额外转json参数时,我们可以直接只引入jackson依赖,再开启mvc直接支持就可以了。而第三方的转换库如FastJSON就必须显示配置MessageConverter
-
-
使用阿里巴巴的FastJSON库
1.引入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.57</version> </dependency>
2.配置MessageConverter
<!-- mvc的注解驱动 --> <!--message-converters 用于当Controller 使用ResponseBody 方式返回数据时, 将数据做格式化(设置编码,避免中文乱码),可以配置多个 --> <mvc:annotation-driven> <mvc:message-converters> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="defaultCharset" value="UTF-8" /> <property name="fastJsonConfig"> <!-- 设置转换JSON的参数 --> <bean class="com.alibaba.fastjson.support.config.FastJsonConfig"> <property name="dateFormat" value="yyyy-MM-dd HH:mm:ss" /> </bean> </property> <property name="supportedMediaTypes"> <!-- 指定转换完JSON后返回的响应头和编码,添加text/html是为了处理在IE下application/json会弹出下载框问题 --> <list> <!--<value>text/html;charset=UTF-8</value>--> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
3.定义Controller类
@RestController @RequestMapping("/user") public class UserRestController { @GetMapping("/{id}") public User getUserById(Integer id) { return userService.findById(id); } }
RESTfull服务
常用RESTFull注解
-
@RestController
该注解本身是一个组合注解,由
@Controller
和@ResponseBody
两个注解组成,所以拥有这两个注解的作用。 在类上声明后,该类会变成一个Controller类,同时,方法的返回值会作为响应体经过消息转换器直接响应给客户端,而不会将其作为视图渲染。@RestController //@Controller //@ResponseBody public class UserRestController { }
-
@GetMapping
:作用:声明当前handler方法只匹配GET请求
使用示例
@GetMapping("/{id}") public User getUserById(@PathVariable Integer id) { return userService.findById(id); }
-
@PostMapping
: 声明当前handler方法只匹配POST请求 -
@DeleteMapping
: 声明当前handler方法只匹配DELETE请求 -
@PutMapping
: 声明当前handler方法只匹配PUT请求
上面四个注解分别对应HTTP的一种请求方法,使用方法类似。
上面四个注解其实是一种快捷注解, 等效于使用下面代码:
@RequestMapping(value = "/user", method = RequestMethod.GET)
@RequestMapping(value = "/user", method = RequestMethod.POST)
@RequestMapping(value = "/user", method = RequestMethod.PUT)
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
解决POST请求乱码
SpringMVC给我们提供了一个专门用来解决post请求乱码的过滤器,我们只需将其配置到web.xml中,就可以避免post请求乱码,免去了我们自己写过滤器的麻烦
<filter>
<filter-name>characterFilter</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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意: 如果项目中配置了多个过滤器,建议将此过滤器放到所有过滤器的最上面。
拦截器
配置方法
<mvc:interceptors>
<!-- 通过<bean>的形式添加interceptor,这种方式配置的拦截器会拦截所有请求 -->
<bean class="com.lanou3g.springmvc.interceptor.TestInterceptor" />
<!-- 通过<mvc:interceptor>配置,可以进一步拦截哪些路径和不拦截哪些路径 -->
<!--<mvc:interceptor>
<mvc:mapping path="/admin/**"/ />
<mvc:exclude-mapping path="/interceptor/**"/>
<bean class="com.lanou.springmvc.web.TestInterceptor" />
</mvc:interceptor>-->
</mvc:interceptors>
拦截时机
/**
* 定义拦截器步骤:
* 1. 定义一个普通类,实现HandlerInterceptor接口
* 2. 按需实现接口中的方法
* 3. 在SpringMVC配置文件中通过<mvc:interceptors></mvc:interceptors>配置拦截器
*
*/
public class TestInterceptor implements HandlerInterceptor {
//此方法在执行Handler之前被调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("凡是我能拦截到的请求,一律不准通过!");
return false;
}
//此方法在执行Handler之后被调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
//此方法在DispatcherServlet最终响应之前被调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}