文章目录
1.SpringMVC简介
- SpringMVC是一种基于Java的实现 MVC 设计模型的请求驱动类型的轻量级 Web框架,属于SpringErameWark 的后续产品,已经融合在Spring Web Flow中。
- SpringMVC已经成为目前最主流的MVC框架之一,它通过一套注解,让一个简单的Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。
SpringMVC执行流程
2.第一个SpringMVC程序
注:持久层和业务层的编写在我的另一篇文章 Spring集成web环境中,这里指编写控制层代码
1.在pom.xml中导入SpringMVC相关坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
2.在web.xml中配置SpringMVC核心(前端)控制器DispathcerServlet
<!-- 配置SpringMVC的前端控制器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--第四步进行完毕后,新增指定配置文件的地址-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3.创建Cotroller类和视图界面
@Controller
public class UserController {
//使用注解配置Cotroller类中业务方法的映射地址
@RequestMapping("/save")
public String save(){
System.out.println("controller save running");
return "success.jsp";
}
}
4.配置SpringMVC核心文件(spring-mvc.xml)
<!-- controller组件扫描,在springmvc配置文件中只配置控制层的包扫描-->
<context:component-scan base-package="com.kk.controller"/>
5.测试结果
控制台
执行流程分析
3.SpringMVC组件解析
SpringMVC执行的详细流程
- 前端控制器:DispatcherServlet
用户请求到达前端控制器,它就相当于MVC模式中的C,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherSerxlet的存在降低了组件之间的耦合性。 - 处理器映射器:HandleMapping
HandlerMapping 负责根据用户请求找到Handler即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 - 处理器适配器:HandlerAdapter
通过 HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 - 处理器:Handler
它就是我们开发中要编写的具体业务控制器,及特有部分。由DispatcherServlet把用户请求转发到Handler。由Handler对具体的用户请求进行处理。 - 视图解析器:View Resolver
View Resolver负责将处理结果生成 View 视图,View Resolver首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 - 视图: View
SpringMVC框架提供了很多的View视图类型的支持,最常用的视图就是jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面
4.SpringMVC注解和配置文件简介
@RequestMapping
- 作用:建立请求URL和处理请求方法之间的对应关系
- 位置:
- 类上,请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录
- 方法上,请求URL的第二级访问目录,与类上的使用@ReqquestMapping标注的一级目录一起组成访问虚拟路径
- 属性:
- value:用于指定请求的URL。它和path属性的作用是一样的
- method:用于指定请求的方式
- params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。例如:
- params = f"accountName"},表示请求参数必须有accountName
- params = {“moeny!100”},表示请求参数中money不能是100
@Controller
//此时要访问save方法,则url为localhost:8080/user/save?username=...
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/save",method = RequestMethod.GET,params = {"username"})
public String save(){
System.out.println("controller save running");
//return "success.jsp";
//"/"代表当前web应用
//这里默认为转发,即相当于
// return "forward:/success.jsp";
return "/success.jsp";
//重定向
//return "redirect:/success.jsp";
}
}
执行结果
配置文件解析:
<!-- 配置内部资源视图解析器,指定前缀和后缀-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
这样在controller中返回视图页面时可以这样写
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/save",method = RequestMethod.GET,params = {"username"})
public String save(){
System.out.println("controller save running");
return "success";
}
}
5.数据响应
5.1 页面跳转
页面跳转(转发及重定向)可以按照上面介绍的返回字符串并与视图解析器的前后缀拼接的形式来实现,下面再总结一下通过返回ModelAndView对象实现页面跳转。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/s1")
public ModelAndView save1(){
//Model封装数据,View展示数据
ModelAndView modelAndView = new ModelAndView();
//向model中存数据
modelAndView.addObject("username","kk");
//设置视图名称
modelAndView.setViewName("success");
return modelAndView;
}
//让框架自动注入ModelAndView 对象,不必手动创建
@RequestMapping(value = "/s2")
public ModelAndView save2(ModelAndView modelAndView){
modelAndView.addObject("username","kk");
modelAndView.setViewName("success");
return modelAndView;
}
//向Request域中添加数据,这种方式不常用
@RequestMapping(value = "/s3")
public String save3(HttpServletRequest req){
req.setAttribute("username","kk");
return "success";
}
}
在视图界面中可通过EL表达式获得model中的值(不仅仅是字符串)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>success</title>
</head>
<body>
<h1>success! ${username}</h1>
</body>
</html>
5.2 回写数据
@ResponseBody 代表不进行页面跳转
1.返回字符串
@RequestMapping(value = "/s5")
@ResponseBody
//将需要回写的字符串直接返回,通过@ResponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回。
public String save5(){
return "hello";
}
@RequestMapping(value = "/s4")
public void save4(HttpServletResponse resp) throws IOException {
resp.getWriter().print("hello");
}
2.返回Json字符串
- 手动拼接Json字符串
@RequestMapping(value = "/s6")
@ResponseBody
public String save6(){
return "{\"name\":\"kk\",\"age\":18}";
}
- 通过jackson转换json格式字符串
(1)导入依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.12.3</version>
</dependency>
(2)测试
//使用jackson回写json字符串
@RequestMapping(value = "/s7")
@ResponseBody
public String save7() throws JsonProcessingException {
User user = new User();
user.setUsername("kk");
user.setAge(20);
ObjectMapper objectMapper = new ObjectMapper();
//使用jackson转换工具将对象转换为json字符串
return objectMapper.writeValueAsString(user);
}
3.返回对象或集合,让框架自动将对象转换为json字符串
(1).在spring-mvc.xml中配置
<!-- 配置处理器适配器HandlerAdapter,指定使用jackson-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
<!-- 上述配置可以替换为-->
<mvc:annotation-driven/>
- 补充:在SprinaMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。
- 使用
< mvc:annotation-driven>
,会自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),因此可以替代注解处理器和适配器的配置,且默认底层就会集成jackson进行对象或集合的ison格式字符串的转换。
(2).测试,这里也可以将类改为集合
@RequestMapping(value = "/s8")
@ResponseBody
//框架自动将对象转换为json字符串
public User save8() {
User user = new User();
user.setUsername("kk");
user.setAge(20);
return user;
}
6.请求数据
注意需要在配置文件中添加< mvc:annotation-driven/>
,会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean。
6.1 获得常用类型参数
1 获得基本类型参数
Controller中的业务方法的参数名称(形参)与请求参数的name一致,参数值会自动映射匹配。
而当两个参数名不一致时,需要使用**@RequestParam**来绑定。
@RequestParam(value="与请求参数名称一致 ", required=true/false(即指定请求参数是否必须包含,默认为true,提交时没有此参数会报错), defaultValue="若没有为参数赋值,则使用此默认值 ")
@RequestMapping(value = "/s9")
@ResponseBody
//请求路径为http://localhost:8080/user/s9?username=kk&age=20时
public void save9(String username,int age) {
System.out.println(username);
System.out.println(age);
}
//请求路径为http://localhost:8080/user/s9?name=kk时
@RequestMapping(value = "/s9")
@ResponseBody
public void save9(@RequestParam(value = "name", required = true, defaultValue = "张三")String username) {
System.out.println(username);
}
使用@RequestParam注解测试结果
2 获得POJO类型参数
//User实体类
public class User {
private String username;
private int age;
//get/set/toString
}
//注意实体类中的属性名应与请求路径中的参数名一致
@RequestMapping(value = "/s10")
@ResponseBody
//请求路径仍为http://localhost:8080/user/s10?username=kk&age=20,但需要将参数封装到User实体类中
public void save10(User user) {
System.out.println(user);
}
3 获得数组类型参数
@RequestMapping(value = "/s11")
@ResponseBody
//请求路径仍为http://localhost:8080/user/s11?strs=1&strs=2...,但需要将参数封装到User实体类中
public void save11(String[] strs) {
System.out.println(Arrays.asList(strs));
}
4 获得集合类型参数
①为了提交多个用户的不同信息,采用表单且为post的提交方式,此时要获得集合参数,要将集合参数包装到—个POJO中才可以
新建一个实体类VO,属性userList用来存储多个用户信息
public class VO {
private List<User> userList;
//不要忘了get/set方法
}
form.jsp用于提交信息
<body>
<form action="${pageContext.request.contextPath}/user/s12" method="post">
<%-- 表名是第几个user对象的属性--%>
user1用户名:<input type="text" name="userList[0].username"><br/>
user1年 龄:<input type="text" name="userList[0].age"><br/>
user2用户名:<input type="text" name="userList[1].username"><br/>
user2年 龄:<input type="text" name="userList[1].age"><br/>
<input type="submit" name="提交"><br/>
</form>
</body>
为了防止中文乱码问题,需要在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>
获得数据
@RequestMapping(value = "/s12")
@ResponseBody
//请求路径仍为http://localhost:8080/user/s12
public void save12(VO vo) {
System.out.println(vo);
}
点击提交后会显示
②使用ajax提交请求数据
创建新文件夹放入jsp页面和下好的jquery文件 jquery下载
<head>
<title>Title</title>
<script src="${pageContext.request.contextPath}/js/jquery-3.6.0.js"></script>
<script>
let userList = [];
userList.push({username:"kk",age:18});
userList.push({username:"kk1",age:20});
$.ajax({
type:"post",
url:"${pageContext.request.contextPath}/user/s13",
data:JSON.stringify(userList),
contentType:"application/json;charset=utf-8"
})
</script>
</head>
当使用ajax提交时,可以指定contentType为json形式,那么在**方法参数位置使用@RequestBody(将请求体的内容直接封装到参数中)**可以直接接收集合数据。
注意:要在spring-mvc.xml文件中添加下面的(二选一)代码(不加会发现加载不到jquery文件,原因是SprinaMVC的前端控制器DisnatcherServlet的urd-pattern配置的是/,代表对所有的资源都进行过滤操作),因此需要指定放行的静态资源。
<!-- 开放静态资源的访问-->
<mvc:resources mapping="/js/**" location="/js/"/>
<!-- 让tomcat来找静态资源-->
<mvc:default-servlet-handler/>
测试
@RequestMapping(value = "/s13")
@ResponseBody
public void save13(@RequestBody List<User> userList) {
System.out.println(userList);
}
6.2 获得请求头和Cookie
@RequestHeader(value=“请求头参数名称(可通过抓包获得)”,required=true/false(是否必须携带此请求头)):获得请求头信息,相当于request.getHeader(“name”)。@CookieValue与之类似
@RequestMapping(value = "/s14")
@ResponseBody
public void save14(@RequestHeader(value = "User-Agent", required = false)String userAgent,
@CookieValue(value = "JSESSIONID", required = false)String jsSessionId) {
System.out.println(userAgent);
System.out.println(jsSessionId);
}
6.3 文件上传
文件上传要注意的点:
- 表单项type= “file”
- 表单的提交方式是post
- 表单的enctype属性是多部分表单形式,enctype= “multipart/form-data”
1.编写file.jsp
<body>
<form action="${pageContext.request.contextPath}/file.jsp" method="post" enctype="multipart/form-data">
名称:<input type="text" name="name"><br/>
文件:<input type="file" name="file"><br/>
<input type="submit" name="提交"><br/>
</form>
</body>
2.导入fileupload和io坐标
fileUpload是apache的commons组件提供的上传组件,它最主要的工作就是帮我们解析request.getInpustream()。
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
3.在spring-mvc.xml中配置文件上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--上传文件总大小5*1024*1024-->
<property name="maxUploadSize" value="5242800"/>
<!--上传单个文件的大小-->
<property name="maxUploadSizePerFile" value="5242800"/>
<!--上传文件的编码类型-->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
4.编写文件上传代码
@RequestMapping(value = "/s15")
@ResponseBody
public void save15(String name, MultipartFile file) throws IOException {
System.out.println(name);
System.out.println(file);
//获得文件名称
String originalFilename = file.getOriginalFilename();
//保存文件
file.transferTo(new File("E:\\upload\\"+originalFilename));
}
若要接收多个文件
public void save15(String name, MultipartFile[] file) throws IOException {
for (MultipartFile multipartFile : file) {
String originalFilename = multipartFile.getOriginalFilename();
multipartFile.transferTo(new File("E:\\upload\\"+originalFilename));
}
}
7.拦截器(Interceptor)
- Spring MVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
- 将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。
7.1 第一个拦截器程序
下面看看如何自定义一个拦截器
1.实现HandlerInteceptor接口并复写三个方法
public class MyInterceptor implements HandlerInterceptor {
@Override
//在目标方法show()执行之前(即请求处理之前)执行,通常会在该方法中进行一些操作
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle。。。");
//localhost:8080/user/tag?param=xxx
String param = request.getParameter("param");
//"yes"最好放在前面,否则请求路径localhost:8080/user/tag不带param参数会报空指针异常
if("yes".equals(param)){
return true;
}else{
request.getRequestDispatcher("/index.jsp").forward(request,response);
return false;
}
//return false;
}
@Override
//在目标方法执行之后(即请求处理之后),视图返回之前执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//这个方法可以用来对视图对象进行修改
modelAndView.addObject("name","张三");
System.out.println("postHandle。。。");
}
@Override
//视图返回之后(即视图渲染完毕)执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion。。。");
}
}
2.在spring-mvc配置文件中配置拦截器
<!-- 配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!-- path=”对那些资源进行拦截操作“-->
<mvc:mapping path="/**"/>
<bean class="com.kk.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
3.测试
@Controller
public class TestInterceptor {
@RequestMapping("/tag")
public ModelAndView show(){
System.out.println("目标资源执行");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("name","kk");
modelAndView.setViewName("success");
return modelAndView;
}
}
1.未配置拦截器时
2.配置拦截器后,会出现下面的情况,因为在上面的preHandle方法中返回的是false,因此下面的其他方法以及**show()**方法均不会执行。
3.添加对请求参数进行判断后,并在postHandle方法中对视图进行改变
7.2 多个拦截器
下面再看看如果同时存在多个拦截器时,各个方法的执行顺序。
先新建一个拦截器,与上面类似
public class MyInterceptor1 implements HandlerInterceptor{
@Override
//在目标方法show()执行之前执行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle111。。。");
return true;
}
@Override
//在目标方法执行之后,视图返回之前执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle111。。。");
}
@Override
//视图返回之后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion111。。。");
}
}
这里执行顺序与配置文件中拦截器的配置顺序有关
<mvc:interceptors>
<mvc:interceptor>
<!-- path=”对那些资源进行拦截操作“-->
<mvc:mapping path="/**"/>
<bean class="com.kk.interceptor.MyInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.kk.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
8.异常处理机制
处理异常的两种方式:
- 使用Spring MVC提供的简单异常处理器SimpleMappinaExceptionResolver,直接在spring-mvc.xml文件中配置即可
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!--默认处理错误的界面,即exceptionMappings中的异常映射均不匹配会默认跳转到此页面--> <property name="defaultErrorView" value="error"/> <!--如果出现空指针异常,会跳转到/error1.jsp页面--> <property name="exceptionMappings"> <map> <entry key="java.lang.NullPointerException" value="error1"/> </map> </property> </bean>
- 实现Spring的异常处理接口HandlerExceptionResolver 自定义异常处理器
public class ExceptionResolver implements HandlerExceptionResolver { public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { ModelAndView modelAndView = new ModelAndView(); if(e instanceof ClassCastException){ modelAndView.addObject("name","自定义异常处理器,类型转换异常"); } modelAndView.setViewName("myerror"); return modelAndView; } } //此时只需要在配置bean即可 <!-- 自定义异常处理器--> <bean id="exceptionResolver" class="com.itheima.exception.ExceptionResolver"/>