概述
SpringMVC是基于Java的实现MVC设计模型的请求驱动类型的轻量级web框架。通过一套注解让一个简单的Java类成为处理请求的控制器而无须实现任何接口,同时它还支持RESTful编程风格的请求
入门
maven项目创建后需要下载一些插件,该过程会花费一定时间,为解决这个问题,可以在创建项目时后的属性框(properties)新增一对键值对:
archetype-internal
环境搭建
1. 导入依赖
<properties>
<!--版本锁定-->
<spring-version>5.1.2.RELEASE</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
2. 配置前端控制器的servlet
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3. 创建springmvc.xml
4. 部署tomcat
代码编写
在web.xml
中配置前端控制器
(处理用户请求的整个流程的控制中心,由它调用其它组件,降低了其它组件之间的耦合性)
在springmvc.xml
中开启注解扫描
在控制器的类上加上@Controller
注解(或者@RestController ,没有该注解的话请求可能会报404错误)
在控制器内请求后要执行的对应方法上加上@RequestMapping
注解(建立请求URL和处理请求的方法之间的关系,可以放在方法上也可以放在类上,用于一个类作为一个模块,其中有多个方法的情况,一级URL注解在类上(/user),二级URL注解在方法上(/findUser)),用path属性指定请求路径
在前端控制器中加入标签,用于读取springmvc.xml文件
在spring.xml中配置视图解析器
(springMVC规范以方法返回的字符串为要跳转的视图文件的名称(前缀))
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.xxx.www"></context:component-scan>
<!--配置视图解析器-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置视图文件所在目录,在webapp的WEB-INF的其他目录下-->
<property name="prefix" value="/WEB-INF/xx"></property>
<!--配置视图文件的后缀-->
<property name="suffix" value=".html"></property>
</bean>
<!--开启springMVC框架的注解的支持。在各个组件中,处理器映射器,处理器适配器,视图解析器称为springMVC的三大组件,使用这一句标签会自动加载处理器映射器和处理器适配器,相当于用这一标签替代处理器和适配器的配置-->
<mvc:annotation-driven/>
</beans>
<!--前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--读取springMVC.xml文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--所有请求都会在这被拦截-->
<url-pattern>/</url-pattern>
</servlet-mapping>
@Controller
public class HelloController {
@RequestMapping(path = "/hello")
public String sayHello(){
System.out.println("hellospringMVC");
return null;
}
}
RequestMapping的其他属性
value
属性与path的作用是一样的method
属性用于指定该方法能接收什么请求方式,其类型为RequestMethod[]
,RequestMethod是一个枚举类,包括GET,POST等其它元素(枚举类引用直接用类名.元素名即可),类型为数组,所以可以声明多种请求方式(用{}来引用),如method={RequestMethod.POST,RequestMethod.GET,....}
params
属性用于限制请求参数的条件,如params = {"accountName"}
表示请求参数中必须有accountName,params = {"money!100"}
表示请求参数中money不能是100headers
属性用于指定限制请求消息头的条件
ps:当以上四个属性只要出现2个或以上时,他们的关系是与的关系
请求参数的绑定
绑定机制
前端表单提交的数据都是k-v
形式的,springMVC的参数绑定过程就是把表单提交的请求参数作为控制器中的方法的参数进行绑定的,要求提交表单的name和参数的名称是相同的。通过反射实现绑定
绑定实体类
- 不含其它实体类的引用
前端表单提交的数据的name应与实体类的属性名相同,然后直接在控制器的方法的参数声明一个实体类对象即可实现绑定封装 - 含其它实体类的引用
对于实体类内引用的实体类,前端表单的name应写为引用的实体类在外部实体类中的名称.引用的实体类中的属性名,如Account中含有User的引用(private User user
),User含属性name,则表单中的name应写为user.name
配置解决中文乱码的过滤器
在web.xml文件中:
<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>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
绑定集合类型
- 对于List集合,假设有一个List集合在实体类中,名称为list,元素为User,则表单的name应为
list[下标].User类中属性名
- 对于Map集合假设有一个Map集合在实体类中,名称为map,k值类型为String,v值类型为User,则表单的name应为
map['k值'].User类中属性名
,k值可自定义
自定义类型转换器
这里以字符串转换为日期为例
1.新建一个类实现Converter接口
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
if(s == null){
throw new RuntimeException("..");
}
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
return dateFormat.parse(s);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("..");
}
}
}
2.在sprinmvc.xml中配置自定义类型转换器
<!--自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.xxx.www.utils.StringToDateConverter"></bean>
</set>
</property>
</bean>
<!--开启springMVC框架的注解的支持,开启这个类型转换器-->
<mvc:annotation-driven conversion-service="conversionService"/>
获取servlet原生API
要在控制器方法中获取request或response等对象,可以直接在方法参数中声明即可
public String test(HttpServletRequest request,HttpServletResponse response){
//System.out.println(request);
return null;
}
常用注解
RequestParam
如果要实现请求参数绑定,但是前端请求中的名称与控制器中方法的参数名称又不一致,例如前端请求的参数名称为name,而方法中要绑定的参数名称为username,此时可以把RequestParam注解用在方法参数上,用value
属性或者name
属性指定前端请求参数的名称即可实现绑定:
public String test(@RequestRaram(name="name") String username){
//System.out.println(username);
return null;
}
不过这个注解只能绑定URL中的参数(get请求)或者以表单形式formdata提交的参数,如果是post请求,参数放在请求体内,该注解是获取不到的,此时只能使用 @RequestBody
注解将参数绑定到一个实体类中;或者前端使用 formdata 的形式提交相关参数
RequestBody
用于获取请求体内容,直接使用得到的是k=v&k=v&…结构的数据,get请求方式不适用。required属性 表示是否必须有请求体,默认值是true,当取值为true时,get请求方式会报错,如果取值为false,get请求得到的是null
public String test(@RequestBody String body){ //将请求体的参数绑定到body中
//System.out.println(body);
return null;
}
PathVariable
首先了解一下REST
风格的URL。
对于控制器以及控制器中的方法,按原来的方式,我们是把控制器作为URL的一级路径(/user
),一个功能模块,然后把控制器中各个方法作为二级路径(/saveUser
,/findUser
),即,每个方法的请求路径都不同
而在RESR风格中,一个控制器中所有方法的请求路径都相同,根据每个请求的请求方式不同再执行不同的方法。此时如果有两个方法使用相同的请求方式,如findAll()
和findOneById()
,都使用get
请求方式,请求路径都为/user,但findOneById()
需要提供一个参数id,此时可以把路径改为/user/{id},这里这个{id}
就是占位符,这样就可以和findAll()
方法区分开。此时前端的请求URL应按照这个格式直接写/user/id,即/user/1
等等。而 PathVariable注解就是用于获取这个占位符的值:
//name属性的值与请求路径中占位符的名称一致
@RequestMapping("/test/{sid}")
public String test(@PathVariable(name="sid") String id){
//System.out.println(id);
return null;
}
RequestHeader
用于获取请求头信息
//获取请求头中Accept的信息
public String test(@RequestHeader(value="Accept") String header){
//System.out.println(header);
return null;
}
CookieValue
用于把指定cookie名称的值传入控制器方法参数
ModelAttribute
作用在参数上,获取指定的数据给参数赋值
作用在方法上,该方法会在控制器的其它方法执行前先执行,可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
应用场景: 当表单提交不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据而不是空
1.修饰带返回值
@RequestMapping("/testModelAttribute")
public String test(User user){
//此时的user中,表单提交的字段的数据与表单一致,
//表单未提交的字段的数据则与showUser()方法中查询得到的user对象一致
System.out.println(user);
return "success";
}
@ModelAttribute
public User showUser(String username){
//先通过username获取数据库中对应的实体类对象再返回,具体步骤省略
User user = new User();
return user;
}
2.修饰不含返回值的方法,需要提供一个Map集合
@RequestMapping("/testModelAttribute")
//根据要取出的元素的自定义key值获取元素
public String test(@ModelAttribute("test") User user){
System.out.println(user);
return "success";
}
@ModelAttribute
public void showUser(String username, Map<String,User> map){
//先通过username获取数据库中对应的实体类对象再返回,具体步骤省略
User user = new User();
//把查询结果封装到Map中,自定义key值
map.put("test",user);
}
SessionAttributes
用于多次执行控制器方法间的参数共享,value属性用于指定存入的属性名称,type属性用于指定存入的数据类型
不使用原生的servletAPI去存值到域对象中,使用Model对象
响应
页面的跳转
- 当控制器方法返回值是字符串,框架会通过视图解析器找到要跳转的页面(底层是用ModelAndView类的方式实现)
- 当返回值是void,默认根据请求路径去找要跳转的页面。此时可以利用原生的servletAPI完成请求转发,重定向或直接响应(
response.getWriter()
)的操作来完成响应 - 当返回值是ModelAndView
- 使用
forward
和redirect
关键字来自定义跳转:
public String test(){
//请求转发是一次请求,可以使用相对路径写法
return "forward:/WEB-INF/pages/success.jsp";
//重定向是重新发送请求,不能使用相对路径的写法,必须从项目名称
//开始写目录。这里框架会帮我们加上项目名称,所以直接写根目录下的
//视图文件即可
//return "redirect:/index.jsp"
}
响应json数据
过滤静态资源
利用jQuery使用ajax发送异步请求时,由于我们配置过前端控制器会拦截所有请求,我们在发送请求时,也会请求到js的文件(jquery.min.js),这些静态资源文件都会被拦截到,导致无法使用,所以我们要配置前端控制器对于哪些静态资源不拦截
<!--样式-->
<mvc:resources mapping="/css/**" location="/css/"/>
<!--图片-->
<mvc:resources mapping="/images/**" location="/images/"/>
<!--JavaScript-->
<mvc:resources mapping="/js/**" location="/js/"/>
对json数据进行封装
5. 相关依赖
//第一种方式,使用jackson自己序列化
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.1</version>
</dependency>
//第二种方式,使用ResponseBody注解把返回的user对象转成json字符串给前端接收
public @ResponseBody User testAjax(@RequestBody User user){
//前端传的是json字符串(每一个key值与user类属性名要一致),将其封装到user对象
System.out.println(user);
User user1 = new User();
return user1;
}
文件上传
不采用springMVC的方式
1.将form表单的enctype取值改为multipart/form-data,请求正文就改成每一部分都是mime类型描述的(默认是application/x-www-form-urlencoded,请求正文是k=v&k=v&…的形式),enctype是表单请求正文的类型
2.使用post方式
3.提供一个文件选择域<input type=“file” />
4.使用组件
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
代码:
public void test(HttpServletRequest request) throws Exception {
//设置上传位置(下面这个语句会找/upload/这个路径在项目目录中的绝对路径)
String path = request.getSession().getServletContext().getRealPath("/upload/");
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
//获取磁盘文件项工厂对象
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
//解析request
List<FileItem> fileItems = servletFileUpload.parseRequest(request);
for (FileItem fileItem : fileItems) {
//判断是上传的文件项还是普通的表单项
if(fileItem.isFormField()){
//是普通表单项
}else{
//String uuid = UUID.randomUUID().toString().replace("-", "");
//获取文件名
String fileItemName = fileItem.getName();
//==可以在文件名前加上自己生成的uuid,防止重复文件名
//==fileItemName = uuid + fileItemName;
//上传到指定位置,如果使用war方式部署就要到tomcat的webapp/upload下;如果是war exploded方式就会在项目路径的target下
fileItem.write(new File(path,fileItemName));
//删除临时文件
fileItem.delete();
}
}
}
使用springMVC的方式
请求发送后,springMVC的前端控制器会把请求交给文件解析器处理,文件解析器解析request以及上传的文件,再把处理完的内容返回到前端控制器,这其中就包括解析得到的文件项,springMVC提供了一个类MultipartFile
用于接收文件项,所以我们只需要在控制器的方法参数添加一个MultipartFile类的对象就可以了,注意对象的名字应该跟前端文件选择域input标签的name属性一致,或者使用@RequestParam注解进行指定,才能实现文件跟方法参数的绑定
异常处理
处理思路
Controller
调用Service
,Service
调用Dao
,异常都是向上抛出,最终有个DispatcherServlet
找异常处理器进行异常的处理
SpringMVC处理异常
1.编写自定义异常类
public class MyException extends Exception{
//提示信息
private String msg;
public MyException(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
2.编写异常处理器
public class MyExceptionResolver implements HandlerExceptionResolver {
/**
* @param httpServletRequest
* @param httpServletResponse
* @param o 当前在对进行处理异常的处理器对象
* @param e 捕获到的异常
* @return org.springframework.web.servlet.ModelAndView
*/
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
MyException myException = null;
if(e instanceof MyException){
myException = (MyException) e;
}else{
myException = new MyException("..");
}
ModelAndView modelAndView = new ModelAndView();
//取出异常里的提示信息放入request域中,再由前端页面取出
//并提示给用户
modelAndView.addObject("errorMsg",myException.getMsg());
//指定要跳转的页面
modelAndView.setViewName("error");
return modelAndView;
}
}
3.在springmvc.xml中配置异常处理器
<bean id="myExceptionResolver" class="com.xxx.www.exception.MyExceptionResolver"></bean>
拦截器
与servlet中的过滤器的区别
在功能上,拦截器与过滤器基本一样。过滤器是servlet规范中的一部分,任何java web工程都可以使用;拦截器是springMVC框架自己的,只有把使用了框架的工程才能用。过滤器在url-pattern中配置了/*后可以实现对所有要访问的资源拦截;拦截器只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js是不会进行拦截的。拦截器也是AOP思想的具体应用
入门使用
1.编写一个拦截器
public class MyInterceptor implements HandlerInterceptor{
/**
*预处理,控制器方法执行前执行
* @param request
* @param response
* @param handler
* @return boolean 返回true,放行,执行下一个拦截器,如果没有下一个拦截器则执行controller方法;
* 返回false,不放行,则借助request或response进行页面跳转,
* 当然也可以在return之前就随时进行页面跳转
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
/**
*后处理,在控制器方法执行后,页面跳转前(指控制器中指定的页面),执行
*如果控制器指定跳转了某个页面,而后处理方法中也指定跳转了另一个页面
*则最终会跳转到后处理方法指定的页面
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
*控制器方法执行完,后处理方法也执行完(如果有),页面也跳转完,才执行
*多个拦截器的情况下,会逆向顺序执行所有拦截器的该方法(除非该拦截器preHandle()方法返回false)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2.在springmvc.xml中配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<!--要拦截的具体的方法,path写"/**"表示所有方法;"/user/*"表示一级路径为user的所有方法-->
<mvc:mapping path="/user/*"/>
<!--不要拦截的方法
<mvc:exclude-mapping path=""/>-->
<!--配置拦截器对象-->
<bean class="com.xxx.www.interceptor.MyInterceptor"/>
</mvc:interceptor>
<!--多个拦截器,拦截器的顺序与配置文件中的顺序一致-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.xxx.www.interceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>