1、SpringMVC介绍
2、SpringMVC架构
3、SpringMVC简单入门程序
4、SSM简单整合
5、Controller层方法中的参数绑定
6、@RequestMapping用法介绍
7、Controller层方法返回值
8、异常处理器 (跳过)
9、SpringMVC上传图片/文件
10、JSON数据交互
11、SpringMVC拦截器
结尾
1、SpringMVC介绍
什么是SpringMVC?
Spring web MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。
模型封装了应用程序数据,并且通常它们由 POJO 组成。
视图主要用于呈现模型数据,并且通常它生成客户端的浏览器可以解释的 HTML 输出。
- 控制器主要用于处理用户请求,并且构建合适的模型并将其传递到视图呈现。
SpringMVC处理流程
2、SpringMVC架构
1. 用户发送请求至前端控制器DispatcherServlet
2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4. DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5. 执行处理器(Controller,也叫后端控制器)。
6. Controller执行完成返回ModelAndView
7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9. ViewReslover解析后返回具体View
10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11. DispatcherServlet响应用户。
在springmvc的各个组件中,处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件。
需要用户开发的组件有handler、view
3、SpringMVC简单入门程序(不涉及数据库操作)
3.1 入门程序步骤
1) 创建springmvc.xml配置文件并在web.xml中配置前端控制器
SpringMVC本身是Spring的子项目,对Spring兼容性很好,不需要进行过多的配置。只配置一个Controller扫描(也就是组件扫描器)就可以了,让SpringMVC对页面控制层Controller进行管理。
<?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: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-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置controller扫描包 --> <context:component-scan base-package="com.panlei.controller" /> </beans>
在web.xml中配置前端控制器
<!-- 配置SpringMVC前端控制器 --> <servlet> <servlet-name>springmvc-first</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定SpringMVC配置文件 --> <!-- SpringMVC的配置文件的默认路径是/WEB-INF/${servlet-name}-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc-first</servlet-name> <!-- 设置所有以action结尾的请求进入SpringMVC --> <url-pattern>*.action</url-pattern> </servlet-mapping>
2) 添加相关jsp页面,此处是展示商品信息的jsp页面并添加商品对应的POJO,注意属性名的一致性
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>查询商品列表</title> </head> <body> <form action="${pageContext.request.contextPath }/queryItem.action" method="post"> 查询条件: <table width="100%" border=1> <tr> <td>商品id<input type="text" name="item.id"></td> <td>商品名称<input type="text" name="item.name"></td> <td><input type="submit" value="查询"/></td> </tr> </table> 商品列表: <table width="100%" border=1> <tr> <td>商品名称</td> <td>商品价格</td> <td>生产日期</td> <td>商品描述</td> <td>操作</td> </tr> <c:forEach items="${itemList }" var="item"> <tr> <td>${item.name }</td> <td>${item.price }</td> <td><fmt:formatDate value="${item.createtime }" pattern="yyyy-MM-dd HH:mm:ss"/></td> <td>${item.detail }</td> <td><a href="${pageContext.request.contextPath }/itemEdit.action?id=${item.id}">修改</a></td> </tr> </c:forEach> </table> </form> </body> </html>
3) 创建ItemController
ItemController是一个普通的java类,不需要实现任何接口。需要在类上添加@Controller注解,把Controller交由Spring管理。在方法上面添加@RequestMapping注解,里面指定请求的url。其中“.action”可以加也可以不加。
@Controller public class ItemController { @Autowired private ItemService itemService; // 静态展示,也就是不连接数据库 @RequestMapping("/itemliststatic.action") public ModelAndView queryItemList() { // 创建页面需要显示的商品数据 List<Item> list = new ArrayList<>(); list.add(new Item(1, "1华为 荣耀8", 2399, new Date(), "质量好!1")); list.add(new Item(2, "2华为 荣耀8", 2399, new Date(), "质量好!2")); list.add(new Item(3, "3华为 荣耀8", 2399, new Date(), "质量好!3")); list.add(new Item(4, "4华为 荣耀8", 2399, new Date(), "质量好!4")); list.add(new Item(5, "5华为 荣耀8", 2399, new Date(), "质量好!5")); list.add(new Item(6, "6华为 荣耀8", 2399, new Date(), "质量好!6")); // 创建ModelAndView,用来存放数据和视图 ModelAndView modelAndView = new ModelAndView(); // 设置数据到模型中 modelAndView.addObject("itemList", list); // 设置视图jsp,需要设置视图的物理地址 modelAndView.setViewName("itemList"); return modelAndView; } }然后运行测试即可。
3.2 相关组件配置讲解(组件映射器,组件适配器,视图解析器)
上一小节的例子中,我们并没有对第二节所讲架构中的三大组件进行配置等操作,但我们仍然可以运行程序,这是因为框架已经默认为我们加载这些组件了,我们可以在源码中发现其具体的配置:
我们使用的注解式的处理器映射以及适配,也就是图中标注的两个。在开发中,我们可能需要在处理器适配器中加入自己的转换器等配置,这就需要我们自己进行组建的配置。
1)配置处理器映射器
注解式处理器映射器,对类中标记了@ResquestMapping的方法进行映射。根据@ResquestMapping定义的url匹配@ResquestMapping标记的方法,匹配成功返回HandlerMethod对象给前端控制器。
HandlerMethod对象中封装url对应的方法Method。推荐使用RequestMappingHandlerMapping完成注解式处理器映射。
<!-- 配置处理器映射器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />注解描述:
@RequestMapping:定义请求url到处理器功能方法的映射
2)配置处理器适配器
注解式处理器适配器,对标记@ResquestMapping的方法进行适配。推荐使用RequestMappingHandlerAdapter完成注解式处理器适配。
<!-- 配置处理器适配器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />3)视图解析器
视图解析器使用SpringMVC框架默认的InternalResourceViewResolver,这个视图解析器支持JSP视图解析
<!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 配置逻辑视图的前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 配置逻辑视图的后缀 --> <property name="suffix" value=".jsp" /> </bean>逻辑视图名需要在controller中返回ModelAndView指定,比如逻辑视图名为ItemList,则最终返回的jsp视图地址:“WEB-INF/jsp/itemList.jsp”
最终jsp物理地址:前缀+逻辑视图名+后缀
4、与MyBatis+Spring进行简单整合 具体项目不再展示,多写代码就会了
上面的例子中的数据是静态的,本节我们将从数据库中获取数据,然后在前端展示。
整合思路:分层配置
Dao层(Mapper):与数据库进行交互
sqlMapConfig.xml
applicationContext.xml中的数据库相关配置:数据库连接池、SqlSessionFactory对象、mapper文件扫描
Service层:使用Dao完成相应操作
applicationContext-service.xml包扫描器,扫描@service注解的类。
applicationContext.xml配置事务
Controller层:相应前端请求,调用Service层服务
springmvc.xml:包扫描器,扫描@Controller注解的类、配置注解驱动、配置视图解析器
web.xml:项目配置
配置Spring
配置前端控制器
5、参数绑定(也就是Controller中方法的参数)
个人理解:SpringMVC中的参数绑定,简化了原始Servlet开发中,由HttpServletRequest来根据key来获取value的方式,可以直接根据前端页面值的name属性,后端方法可以由属性名称一致进行获取,也就是前端页面的值的属性要与后端的属性名称一致,不管是简单的数据类型,还是自定义的POJO,还是包装类型,还是数组、列表等类型,都遵循这一原则。其底层代码还是servlet的根据request的key-value的形式。明白这个原则之后,下面所讲的所有类型参数绑定,便一目了然了。
1)默认支持的参数类型
HttpServletRequest:通过request对象获取请求信息
HttpServletResponse:通过response处理响应信息
HttpSession:通过session对象得到session中存放的对象
2)绑定简单类型
当请求的参数名称和处理器形参名称一致时会将请求参数与形参进行绑定。这样,从Request取参数的方法就可以进一步简化。
@RequestParam:使用该注解来处理简单类型的绑定。它有三个参数:
value:参数名字,即入参的请求参数名字,如value=“itemId”表示请求的参数 区中的名字为itemId的参数的值将传入
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报错
TTP Status 400 - Required Integer parameter 'XXXX' is not present
defaultValue:默认值,表示如果请求中没有同名参数时的默认值
举例:
@RequestMapping("/itemEdit") public String queryItemById(@RequestParam(value = "itemId", required = true, defaultValue = "1") Integer id, ModelMap modelMap) { ... }
3)绑定pojo类型
如果提交的参数很多,或者提交的表单中的内容很多的时候,可以使用简单类型接受数据,也可以使用pojo接收数据。
要求:pojo对象中的属性名和表单中input的name属性一致。
4)绑定包装POJO
包装POJO中的成员变量名称与前端页面的名称一致
5)自定义参数绑定
我们的商品中有一个生产日期的属性,现在要求我们可以根据业务需求自定义日期格式。很明显日期格式的转换是需要我们自定义进行处理的。
前端控制器接收到请求后,找到注解形式的处理器适配器,对RequestMapping标记的方法进行适配,并对方法中的形参进行参数绑定。可以在springmvc处理器适配器上自定义转换器Converter进行参数绑定。
前端页面数据:
自定义Convert:
配置Convert:方法一,处理器适配器与处理器映射器不单独配置的方式,也就是annotation-driven
<!-- 配置注解驱动 --> <!-- 如果配置此标签,可以不用配置... --> <mvc:annotation-driven conversion-service="conversionService" /> <!-- 转换器配置 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="cn.itcast.springmvc.converter.DateConverter" /> </set> </property> </bean>配置Convert:方法二,此方法需要独立配置处理器映射器、适配器,不再使用<mvc:annotation-driven/>
<!-- 手动配置Handleradapter --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer" ref="customBinder"></property> </bean> <bean id="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService"/> </bean> <!-- 转换器工厂,也就是将自定义的转换器配置在此,可以配置多个 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.panlei.util.DataConvert"/> </set> </property> </bean>6)数组参数的绑定:应用场景比如选定多个
案例演示:我们在列表展示页面添加一列 checkbox,选中多个商品将id传给后端
jsp页面代码<form action="${pageContext.request.contextPath }/deleteItem.action" method="post"> 商品列表: <table width="100%" border=1> <tr> <td><input type="checkbox" name="ids" value=""></td> <td>商品名称</td> <td>商品价格</td> <td>生产日期</td> <td>商品描述</td> <td>操作</td> </tr> <c:forEach items="${itemList }" var="item"> <tr> <td><input type="checkbox" name="ids" value="${item.id }"></td> <td>${item.name }</td> <td>${item.price }</td> <td><fmt:formatDate value="${item.createtime }" pattern="yyyy-MM-dd HH:mm:ss"/></td> <td>${item.detail }</td> <td><a href="${pageContext.request.contextPath }/itemEdit.action?id=${item.id}">修改</a></td> </tr> </c:forEach> </table> <input type="submit" value="查询"/> </form>我们可以看到jsp页面代码中checkbox的name属性值为ids,相应的我们接收可以根据此名字接收。
7)将表单的数据绑定到List
案例演示:批量修改,这次我们利用包装类型进行数据的接收,还是注意属性名的一致性。
先是jsp页面
<form action="${pageContext.request.contextPath }/updateItem.action" method="post"> 商品列表: <table width="100%" border=1> <tr> <td><input type="checkbox" name="ids" value=""></td> <td>商品名称</td> <td>商品价格</td> <td>生产日期</td> <td>商品描述</td> <td>操作</td>
</tr> <c:forEach items="${itemList }" var="item" varStatus="s"> <tr> <td><input type="checkbox" name="ids" value="${item.id }"></td> <td><input type="text" name="itemsList[${s.index }].name" value="${item.name }" /></td> <td><input type="text" name="itemsList[${s.index }].price" value="${item.price }" /></td> <td><fmt:formatDate value="${item.createtime }" pattern="yyyy-MM-dd HH:mm:ss"/></td> <td>${item.detail }</td> <td><a href="${pageContext.request.contextPath }/itemEdit.action?id=${item.id}">修改</a></td> </tr> </c:forEach> </table> <input type="submit" value="提交"/>
</form>这里因为要传递一个list,我们在for循环中动态的加载每一个信息,这里属性名是 itemsList,然后我们在QueryVO中添加这一属性
最后测试获取这一属性
6、@RequestMapping用法介绍
1)URL路径映射
@RequestMapping(value="item")或@RequestMapping("/item")
value的值是数组,可以将多个url映射到同一个方法。
@RequestMapping(value = { "itemList", "itemListAll" })
2)添加在类上面
在class上添加@RequestMapping(url)指定通用请求前缀, 限制此类下的所有方法请求url必须以请求前缀开头。可以使用此方法对url进行分类管理。
3)请求方法限定
限定请求进来的方法
比如,限定GET方法
@RequestMapping(method = RequestMethod.GET)
如果通过POST访问则报错:
HTTP Status 405 - Request method 'POST' not supported
7、Controller层方法返回值
1)返回ModelAndView
controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view。
2)返回 void
3)返回String
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
//指定逻辑视图名,经过视图解析器解析为jsp物理路径:/WEB-INF/jsp/itemList.jsp
return "itemList";
重定向:Contrller方法返回字符串可以重定向到一个url地址
如下商品修改提交后重定向到商品编辑页面。
/** * 更新商品 * @param item * @return */ @RequestMapping("updateItem") public String updateItemById(Item item) { // 更新商品 this.itemService.updateItemById(item); // 修改商品成功后,重定向到商品编辑页面 // 重定向后浏览器地址栏变更为重定向的地址, // 重定向相当于执行了新的request和response,所以之前的请求参数都会丢失 // 如果要指定请求参数,需要在重定向的url后面添加 ?itemId=1 这样的请求参数 return "redirect:/itemEdit.action?itemId=" + item.getId(); }forward转发
Controller方法执行后继续执行另一个Controller方法
如下商品修改提交后转向到商品修改页面,修改商品的id参数可以带到商品修改方法中。
/** * 更新商品 * * @param item * @return */ @RequestMapping("updateItem") public String updateItemById(Item item) { // 更新商品 this.itemService.updateItemById(item); // 修改商品成功后,重定向到商品编辑页面 // 重定向后浏览器地址栏变更为重定向的地址, // 重定向相当于执行了新的request和response,所以之前的请求参数都会丢失 // 如果要指定请求参数,需要在重定向的url后面添加 ?itemId=1 这样的请求参数 // return "redirect:/itemEdit.action?itemId=" + item.getId(); // 修改商品成功后,继续执行另一个方法 // 使用转发的方式实现。转发后浏览器地址栏还是原来的请求地址, // 转发并没有执行新的request和response,所以之前的请求参数都存在 return "forward:/itemEdit.action"; }
8、异常处理器(跳过)
springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。
7.1 异常处理的思路
系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理,如下图:
9、SpringMVC上传图片/文件
首先在IDEA配置一下文件上传的虚拟目录,也就是说,不再像以前一样,只是把文件上传到WEB-INF目录下,只能本项目使用。
参考博客:https://blog.csdn.net/qq_36481052/article/details/78813213
需要引入jar包
配置MultipartFile
MultipartFile是一个接口,用以文件上传处理。因为是一个接口,所以我们需要配置实现类
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"/> <!-- 最大上传文件大小 --> <property name="maxUploadSize" value="20971520"/> <!-- 最大内存 --> <property name="maxInMemorySize" value="20971520"/> </bean>
这里bean的id是固定的
配置好后就在Controller层获取文件流进行上传操作,保存:具体操作根据参数来进行
CommonsMultipartFile shopImg = null; //文件解析器 CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); //是否是文件流 if (commonsMultipartResolver.isMultipart(request)) { MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request; shopImg = (CommonsMultipartFile) multipartHttpServletRequest.getFile("shopImg");
10、JSON数据交互
引入jar包
1)request请求是json,转为pojo
@RequestBody
后端接收
2)参数返回转为json
@ResponseBody加在方法之前,可以自动将返回值转为JSON格式
11、SpringMVC拦截器
拦截器配置
拦截器类:分为方法前,方法后,页面渲染后。在具体应用中,我们一般不是implement这个接口,而是继承某个实现了这个接口的类。
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("方法前"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { System.out.println("方法后"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { System.out.println("渲染后"); } }配置拦截器
<mvc:interceptors> <mvc:interceptor> <!-- 拦截所有 --> <mvc:mapping path="/**" /> <bean id="LoginInterceptor" class="com.panlei.interceptor.LoginInterceptor" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> <!-- 路径排除 --> <mvc:exclude-mapping path="/shopadmin/shoplist" /> <bean id="ShopPermissionInterceptor" class="com.panlei.interceptor.Interceptor2" /> </mvc:interceptor> </mvc:interceptors>
拦截器执行规则
preHandle按拦截器定义顺序调用
postHandler按拦截器定义逆序调用
afterCompletion按拦截器定义逆序调用
postHandler在拦截器链内所有拦截器都成功的时候才调用
afterCompletion只有对应preHandle返回true才调用
具体例子:判断用户是否登录,可以作为模板代码参考
说明:拦截器需要继承HandlerInterceptorAdapter,因为需要验证是否已经登录,所以只需要重写preHandle方法即可。
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** * @author pan_l * @ClassName LoginInteceptor * @Description * @date 2019-07-20 18:03 */ public class LoginInteceptor extends HandlerInterceptorAdapter { /** * 主要做事前拦截,即用户操作发生前,改写preHandle里的逻辑,进行拦截 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从session中取出用户信息来 Object adminObj = request.getSession().getAttribute("admin"); if (adminObj != null) { return true; } // 若不满足登录验证,则直接跳转到帐号登录页面 PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<script>"); out.println("window.open ('" + request.getContextPath() + "/login','_self')"); out.println("</script>"); out.println("</html>"); return false; } }写好拦截器后,需要进行配置,这里使用的是Java类的配置,是重写addInterceptors方法,添加过滤器,并指定过滤规则即可。
@Configuration public class MvcConfiguration implements WebMvcConfigurer { @Override //需要告知系统,这是要被当成静态文件的! public void addResourceHandlers(ResourceHandlerRegistry registry) { // 设置文件上传的文件不拦截 // registry.addResourceHandler("/upload/**").addResourceLocations("file:"+ TaleUtils.getUplodFilePath()+"upload/"); //将所有/static/** 访问都映射到classpath:/static/ 目录下 registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); } @Override public void addInterceptors(InterceptorRegistry registry) { InterceptorRegistration registration = registry.addInterceptor(new LoginInteceptor()); registration.addPathPatterns("/admin/**"); } }