第一部分 Spring MVC 应用
1、简介
1.1 三层架构
我们的开发架构一般都是基于两种形式,一种是 C/S 架构,也就是客户端/服务器;另一种是 B/S 架构 ,也就是浏览器服务器。在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发。那么在 B/S 架构中,系 统标准的三层架构包括:表现层、业务层、持久层。三层架构在我们的实际开发中使用的非常多,所以 我们课程中的案例也都是基于三层架构设计的。
-
表现层:
也就是我们常说的web 层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http 协 议请求web 层,web 需要接收 http 请求,完成 http 响应。表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。 表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。 表现层的设计一般都使用 MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系) -
业务层
也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项目的需求息息相关。web 层依赖业
务层,但是业务层不依赖 web 层。业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说
的, 事务应该放到业务层来控制) -
持久层
也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进 行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化 到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进行增删改查的。
1.2 MVC设计模式
MVC 全名是 Model View Controller,是 模型(model)-视图(view)-控制器(controller) 的缩写, 是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职: Model(模型):模型包含业务模型和数据模型,数据模型用于封装数据,业务模型用于处理业务。
- View(视图): 通常指的就是我们的 jsp 或者 html。作用一般就是展示数据的。通常视图是依据 模型数据创建的。
- Controller(控制器): 是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。
- MVC提倡:每一层只编写自己的东⻄,不编写任何其他的代码;分层是为了解耦,解耦是为了维护方便和分工协作。
1.3 SpringMVC
全名叫 Spring Web MVC
是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级Web 框架,属于 SpringFrameWork 的后续产品。为了解决表现层问题 的web框架,基于 MVC 设计模式的。而这些表现层框架的主要职责就是处理前端HTTP请求。本质可以认为是对servlet的封装,简化了我们serlvet的开发
作用:
1)接收请求
2)返回响应,跳转⻚面
2、Spring Web MVC 工作流程
2.1 Spring MVC 请求处理流程
流程说明
第一步: 用户发送请求至前端控制器DispatcherServlet
第二步: DispatcherServlet收到请求调用HandlerMapping处理器映射器
第三步: 处理器映射器根据请求Url找到具体的Handler(后端控制器),生成处理器对象及处理器拦截 器(如果 有则生成)一并返回DispatcherServlet
第四步: DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler
第五步: 处理器适配器执行Handler
第六步: Handler执行完成给处理器适配器返回ModelAndView
第七步: 处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的一个 底层对 象,包括 Model 和 View
第八步: 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图。
第九步: 视图解析器向前端控制器返回View
第十步: 前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
第十一步:前端控制器向用户响应结果
开发过程
1)配置DispatcherServlet前端控制器
2)开发处理具体业务逻辑的Handler(@Controller、@RequestMapping)
3)xml配置文件配置controller扫描,配置springmvc三大件
4)将xml文件路径告诉springmvc(DispatcherServlet)
2.2 Spring MVC 九大组件
-
HandlerMapping(处理器映射器)
HandlerMapping 是用来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是 方法。比如,标注了@RequestMapping的每个方法都可以看成是一个Handler。Handler负责具 体实际的请求处理,在请求到达后,HandlerMapping 的作用便是找到请求相应的处理器 Handler 和 Interceptor. -
HandlerAdapter(处理器适配器)
HandlerAdapter 是一个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请 求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理 方法调用 Handler 来进行处理,便是 HandlerAdapter 的职责。 -
HandlerExceptionResolver
HandlerExceptionResolver 用于处理 Handler 产生的异常情况。它的作用是根据异常设置
ModelAndView,之后交给渲染方法进行渲染,渲染方法会将 ModelAndView 渲染成⻚面。 -
ViewResolver
ViewResolver即视图解析器,用于将String类型的视图名和Locale解析为View类型的视图,只有一 个resolveViewName()方法。从方法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这里被解析成为View。View是用来渲染⻚面的,也就是说,它会将程序返回 的参数和数据填入模板中,生成html文件。ViewResolver 在这个过程主要完成两件事情: ViewResolver 找到渲染所用的模板(第一件大事)和所用的技术(第二件大事,其实也就是找到 视图的类型,如JSP)并填入参数。默认情况下,Spring MVC会自动为我们配置一个 InternalResourceViewResolver,是针对 JSP 类型视图的。 -
RequestToViewNameTranslator
RequestToViewNameTranslator 组件的作用是从请求中获取 ViewName.因为 ViewResolver 根据 ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName, 便要通过这个组件从请求中查找 ViewName。 -
LocaleResolver
ViewResolver 组件的 resolveViewName 方法需要两个参数,一个是视图名,一个是 Locale。 LocaleResolver 用于从请求中解析出 Locale,比如中国 Locale 是 zh-CN,用来表示一个区域。这 个组件也是 i18n 的基础。 -
ThemeResolver
ThemeResolver 组件是用来解析主题的。主题是样式、图片及它们所形成的显示效果的集合。 Spring MVC 中一套主题对应一个 properties文件,里面存放着与当前主题相关的所有资源,如图 片、CSS样式等。创建主题非常简单,只需准备好资源,然后新建一个“主题名.properties”并将资 源设置进去,放在classpath下,之后便可以在⻚面中使用了。SpringMVC中与主题相关的类有 ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和 具体的资源。 -
MultipartResolver
MultipartResolver 用于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实 现。MultipartHttpServletRequest 可以通过 getFile() 方法 直接获得文件。如果上传多个文件,还 可以调用 getFileMap()方法得到Map<FileName,File>这样的结构,MultipartResolver 的作用就 是封装普通的请求,使其拥有文件上传的功能。 -
FlashMapManager
FlashMap 用于重定向时的参数传递,比如在处理用户订单时候,为了避免重复提交,可以处理完 post请求之后重定向到一个get请求,这个get请求可以用来显示订单详情之类的信息。这样做虽然 可以规避用户重新提交订单的问题,但是在这个⻚面上要显示订单的信息,这些数据从哪里来获得 呢?因为重定向时么有传递参数这一功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写入请求(可以通过ServletRequestAttributes.getRequest()方法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会自动将其设置到Model中,在显示订单信息的⻚面 上就可以直接从Model中获取数据。FlashMapManager 就是用来管理 FalshMap 的。
springmvc.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--开启controller扫描-->
<context:component-scan base-package="com.lagou.edu.controller"/>
<!--配置springmvc的视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--
自动注册最合适的处理器映射器,处理器适配器(调用handler方法)
-->
<mvc:annotation-driven conversion-service="conversionServiceBean"/>
<!--注册自定义类型转换器-->
<bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.lagou.edu.converter.DateConverter"></bean>
</set>
</property>
</bean>
<!--静态资源配置,方案一 -->
<!--
原理:添加该标签配置之后,会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler对象
这个对象如同一个检查人员,对进入DispatcherServlet的url请求进行过滤筛查,如果发现是一个静态资源请求
那么会把请求转由web应用服务器(tomcat)默认的DefaultServlet来处理,如果不是静态资源请求,那么继续由
SpringMVC框架处理
-->
<!--<mvc:default-servlet-handler/>-->
<!--静态资源配置,方案二 -->
<!--
SpringMVC框架自己处理静态资源
mapping: 约定的静态资源的url规则
location: 指定的静态资源的存放位置
-->
<mvc:resources location="classpath:/" mapping="/resources/**"/>
<mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
<mvc:interceptors>
<!--拦截所有handler-->
<!--<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>-->
<mvc:interceptor>
<!--配置当前拦截器的url拦截规则,**代表当前目录下及其子目录下的所有url-->
<mvc:mapping path="/**"/>
<!--exclude-mapping可以在mapping的基础上排除一些url拦截-->
<!--<mvc:exclude-mapping path="/demo/**"/>-->
<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.lagou.edu.interceptor.MyIntercepter02"/>
</mvc:interceptor>
</mvc:interceptors>
<!--多元素解析器
id固定为multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置上传文件大小上限,单位是字节,-1代表没有限制也是默认的-->
<property name="maxUploadSize" value="5000000"/>
</bean>
</beans>
web.xml
<!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>
<display-name>Archetype Created Web Application</display-name>
<!--springmvc提供的针对post请求的编码过滤器-->
<filter>
<filter-name>encoding</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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换-->
<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>
<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:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
方式一:带后缀,比如*.action *.do *.aaa
该种方式比较精确、方便,在以前和现在企业中都有很大的使用比例
方式二:/ 不会拦截 .jsp,但是会拦截.html等静态资源(静态资源:除了servlet和jsp之外的js、css、png等)
为什么配置为/ 会拦截静态资源???
因为tomcat容器中有一个web.xml(父),你的项目中也有一个web.xml(子),是一个继承关系
父web.xml中有一个DefaultServlet, url-pattern 是一个 /
此时我们自己的web.xml中也配置了一个 / ,覆写了父web.xml的配置
为什么不拦截.jsp呢?
因为父web.xml中有一个JspServlet,这个servlet拦截.jsp文件,而我们并没有覆写这个配置,
所以springmvc此时不拦截jsp,jsp的处理交给了tomcat
如何解决/拦截静态资源这件事?
springmvd.xml配置文件中 两种方式处理
方式三:/* 拦截所有,包括.jsp
-->
<!--拦截匹配规则的url请求,进入springmvc框架处理-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
MyIntercepter01.java DateConverter.java
/**
* 自定义springmvc拦截器
*/
public class MyIntercepter01 implements HandlerInterceptor {
/**
* 会在handler方法业务逻辑执行之前执行
* 往往在这里完成权限校验工作
* @param request
* @param response
* @param handler
* @return 返回值boolean代表是否放行,true代表放行,false代表中止
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyIntercepter01 preHandle......");
return true;
}
/**
* 会在handler方法业务逻辑执行之后尚未跳转页面时执行
* @param request
* @param response
* @param handler
* @param modelAndView 封装了视图和数据,此时尚未跳转页面呢,你可以在这里针对返回的数据和视图信息进行修改
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyIntercepter01 postHandle......");
}
/**
* 页面已经跳转渲染完毕之后执行
* @param request
* @param response
* @param handler
* @param ex 可以在这里捕获异常
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyIntercepter01 afterCompletion......");
}
}
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
// 完成字符串向日期的转换
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
Date parse = simpleDateFormat.parse(source);
return parse;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
3、请求参数绑定
3.1 默认支持 Servlet API 作为方法参数
/**
*
* SpringMVC 对原生servlet api的支持 url:/demo/handle?id=1
* 如果要在SpringMVC中使用servlet原生对象,
* 比如HttpServletRequest\HttpServletResponse\HttpSession,
* 直接在Handler方法形参中声 明使用即可
*/
@RequestMapping("/handle")
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,HttpSession session) {
String id = request.getParameter("id");
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
3.2 简单类型参数
简单数据类型: 八种基本数据类型及其包装类型
参数类型推荐使用包装数据类型,因为基础数据类型不可以为null
整型:Integer、int
字符串:String
单精度:Float、float
双精度:Double、double
布尔型:Boolean、boolean 说明:对于布尔类型的参数,请求的参数值为true或false。或者1或0
注意:绑定简单数据类型参数,只需要直接声明形参即可(形参参数名和传递的参数名要保持一 致,建议使用包装类型,当形参参数名和传递参数名不一致时可以使用@RequestParam注解进行手动映射)
/*
* SpringMVC 接收简单数据类型参数 url:/demo/handle?ids=1
* 注意: 接收简单数据类型参数,直接在handler方法的形参中声明即可,框架会取出参数值然后绑定到对应参数上
* 要求: 传递的参数名和声明的形参名称保持一致
*/
@RequestMapping("/handle")
public ModelAndView handle(@RequestParam("ids") Integer id, Boolean flag) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
3.3 Pojo类型参数
public class User {
private Integer id;
private String name;
//setter getter
}
/*
* SpringMVC接收pojo类型参数 url:/demo/handle?id=1&username=zhangsan
* 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名无所谓
* 但是要求传递的参数名必须和Pojo的属性名保持一致
*/
@RequestMapping("/handle")
public ModelAndView handle(User user) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
3.4 Pojo包装对象参数
包装类型 QueryVo
public class QueryVo {
private String mail;
private String phone;
// 嵌套了另外的Pojo对象
private User user;
//setter getter
}
/*
* SpringMVC接收pojo包装类型参数 url:/demo/handle?user.id=1&user.username=zhangsan
* 不管包装Pojo与否,它首先是一个pojo,那么就可以按照上述pojo的要求来
* 1、绑定时候直接形参声明即可
* 2、传参参数名和pojo属性保持一致,如果不能够定位数据项,那么通过属性名 + "." 的方式进一步锁定数据
*/
@RequestMapping("/handle")
public ModelAndView handle(QueryVo queryVo) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
3.5 绑定日期类型参数
需要配置自定义类型转换器
/**
* 自定义类型转换器
* S:source,源类型
* T:target:目标类型
*/
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
// 完成字符串向日期的转换
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
Date parse = simpleDateFormat.parse(source);
return parse;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
/**
* 绑定日期类型参数 url:/demo/handle?birthday=2019-10-08
* 定义一个SpringMVC的类型转换器接口,扩展实现接口接口,注册我们的实现
* @param birthday
* @return
*/
@RequestMapping("/handle")
public ModelAndView handle(Date birthday) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
<!--
自动注册最合适的处理器映射器,处理器适配器(调用handler方法)
-->
<mvc:annotation-driven conversion-service="conversionServiceBean"/>
<!--注册自定义类型转换器-->
<bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.lagou.edu.converter.DateConverter"></bean>
</set>
</property>
</bean>
4、Restful ⻛格请求支持
Restful 是一种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是一个资源定位及资源操作的⻛格。
REST(英文:Representational State Transfer,简称 REST)
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation)。比 如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进 制格式。
状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。
HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器, 必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层 之上的,所以就是 “ 表现层状态转化” 。具体说, 就是 HTTP 协议里面,四个表示操作方式的动词: GET 、POST 、PUT 、DELETE 。
1、GET 用来获取资源, 查询
2、POST 用来新建资源, 修改
3、PUT 用来更新资源, 添加
4、DELETE 用来删除资源。 删除
只是一种协议规定,不是说一个动词只能实现这一种功能,例如 POST 也可以用作查询
一般使用时还是 post 和 get。put 和 delete几乎不使用
/*
* restful /demo/handle/123
*/
@RequestMapping(value = "/handle/{id}",method = {RequestMethod.GET})
public ModelAndView handleGet(@PathVariable("id") Integer id) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
/*
* restful /demo/handle
*/
@RequestMapping(value = "/handle",method = {RequestMethod.POST})
public ModelAndView handlePost(String username) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
/*
* restful put /demo/handle/123/test
*/
@RequestMapping(value = "/handle/{id}/{name}",method = {RequestMethod.PUT})
public ModelAndView handlePut(@PathVariable("id") Integer id, @PathVariable("name") String username) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
/*
* restful delete /demo/handle/123
*/
@RequestMapping(value = "/handle/{id}",method = {RequestMethod.DELETE})
public ModelAndView handleDelete(@PathVariable("id") Integer id) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
web.xml
<!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换-->
<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>
5、Ajax Json交互
交互: 两个方向
1)前端到后台:前端ajax发送json格式字符串,后台直接接收为pojo参数,使用注解@RequstBody
2)后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串,使用注解 @ResponseBody
@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之 后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。 注意:在使用此注解之 后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定 格式的数据。
pom.xml
<!--json数据交互所需jar,start-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
<!--json数据交互所需jar,end-->
@RequestMapping("/handle")
// 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据
public @ResponseBody User handle(@RequestBody User user) {
// 业务逻辑处理,修改name为张三丰
user.setName("张三丰");
return user;
}
第二部分 Spring MVC 高级技术
1、拦截器(Interceptor)
1.1、监听器、过滤器和拦截器对比
-
Servlet: 处理Request请求和Response响应
-
过滤器(Filter): 对Request请求起到过滤的作用,作用在Servlet之前,如果配置为/*可以对所 有的资源访问(servlet、js/css静态资源等)进行过滤处理
-
监听器(Listener): 实现了javax.servlet.ServletContextListener 接口的服务器端组件,它随 Web应用的启动而启动,只初始化一次,然后会一直运行监视,随Web应用的停止而销毁
作用一: 做一些初始化工作,web应用中spring容器启动ContextLoaderListener;
作用二: 监听web中的特定事件,比如HttpSession,ServletRequest的创建和销毁;变量的创建、 销毁和修改等。可以在某些动作前后增加处理,实现监控,比如统计在线人数,利用 HttpSessionLisener等。
-
拦截器(Interceptor): 是SpringMVC、Struts等表现层框架自己的,不会拦截 jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler)。
从配置的⻆度: servlet、filter、listener是配置在web.xml中的,而interceptor是配置在表现层框架自己的配置文件中的,在Handler业务逻辑执行之前拦截一次,在Handler逻辑执行完毕但未跳转⻚面之前拦截一次,在跳转⻚面之后拦截一次。
1.2、单拦截器的执行流程
在运行程序时,拦截器的执行是有一定顺序的,该顺序与配置文件中所定义的拦截器的顺序相关。 单个
拦截器,在程序中的执行流程如下图所示:
1)程序先执行preHandle(),如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行。
2)在业务处理器(Controller)处理完请求后,会执行postHandle(),然后会通过DispatcherServlet向客户端返回响应。
3)在DispatcherServlet处理完请求后,才会执行afterCompletion()方法。
1.3、多个拦截器的执行流程
多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置文件中, Interceptor1拦截器配置在前),在程序中的执行流程如下图所示:
从图可以看出,当有多个拦截器同时工作时,它们的preHandle()方法会按照配置文件中拦截器的配置 顺序执行,而它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行。
自定义拦截器
/**
* 自定义springmvc拦截器
*/
public class MyIntercepter01 implements HandlerInterceptor {
/**
* 会在handler方法业务逻辑执行之前执行
* 往往在这里完成权限校验工作
* @param request
* @param response
* @param handler
* @return 返回值boolean代表是否放行,true代表放行,false代表中止
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyIntercepter01 preHandle......");
return true;
}
/**
* 会在handler方法业务逻辑执行之后尚未跳转页面时执行
* @param request
* @param response
* @param handler
* @param modelAndView 封装了视图和数据,此时尚未跳转页面呢,你可以在这里针对返回的数据和视图信息进行修改
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyIntercepter01 postHandle......");
}
/**
* 页面已经跳转渲染完毕之后执行
* @param request
* @param response
* @param handler
* @param ex 可以在这里捕获异常
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyIntercepter01 afterCompletion......");
}
}
// MyIntercepter02 与 MyIntercepter01一样
注册SpringMVC拦截器
<mvc:interceptors>
<!--拦截所有handler-->
<!--<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>-->
<mvc:interceptor>
<!--配置当前拦截器的url拦截规则,**代表当前目录下及其子目录下的所有url-->
<mvc:mapping path="/**"/>
<!--exclude-mapping可以在mapping的基础上排除一些url拦截-->
<!--<mvc:exclude-mapping path="/demo/**"/>-->
<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.lagou.edu.interceptor.MyIntercepter02"/>
</mvc:interceptor>
</mvc:interceptors>
运行结果
2、处理multipart形式的数据
pom.xml
<!--文件上传所需jar-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
springmvc.xml
<!--配置文件上传解析器,id是固定的multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置上传大小,单位字节-->
<property name="maxUploadSize" value="1000000000"/> </bean>
前端From表单
<form method="post" enctype="multipart/form-data" action="/demo/upload">
<input type="file" name="uploadFile"/>
<input type="submit" value="上传"/>
</form>
业务逻辑java
/**
* 文件上传
* @return
*/
@RequestMapping(value = "/upload")
public ModelAndView upload(MultipartFile uploadFile,HttpSession session) throws IOException {
// 处理上传文件
// 重命名,原名123.jpg ,获取后缀
String originalFilename = uploadFile.getOriginalFilename();// 原始名称
// 扩展名 jpg
String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1, originalFilename.length());
String newName = UUID.randomUUID().toString() + "." + ext;
// 存储,要存储到指定的文件夹,/uploads/yyyy-MM-dd,考虑文件过多的情况按照日期,生成一个子文件夹
String realPath = session.getServletContext().getRealPath("/uploads");
String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
File folder = new File(realPath + "/" + datePath);
if(!folder.exists()) {
folder.mkdirs();
}
// 存储文件到目录
uploadFile.transferTo(new File(folder,newName));
// TODO 文件磁盘路径要更新到数据库字段
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
3、控制器中处理异常
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 可以让我们优雅的捕获所有Controller对象handler方法抛出的异常
@ControllerAdvice
public class GlobalExceptionResolver {
@ExceptionHandler(ArithmeticException.class)
public ModelAndView handleException(ArithmeticException exception, HttpServletResponse response) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg",exception.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
4、基于Flash属性的跨重定向请求数据传递
重定向时请求参数会丢失,我们往往需要重新携带请求参数,我们可以进行手动参数拼接如下:
return "redirect:handle01?name=" + name;
但是上述拼接参数的方法属于get请求,携带参数⻓度有限制,参数安全性也不高,此时,我们可以使 用SpringMVC提供的flash属性机制,向上下文中添加flash属性,框架会在session中记录该属性值,当 跳转到⻚面之后框架会自动删除flash属性,不需要我们手动删除,通过这种方式进行重定向参数传递, 参数⻓度和安全性都得到了保障,如下:
/**
* SpringMVC 重定向时参数传递的问题
* 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A
* url不会变,参数也不会丢失,一个请求
* 重定向:A 找 B 借钱400,B 说我没有钱,你找别人借去,那么A 又带着400块的借钱需求找到C
* url会变,参数会丢失需要重新携带参数,两个请求
*/
@RequestMapping("/handleRedirect")
public String handleRedirect(String name, RedirectAttributes redirectAttributes) {
//return "redirect:handle01?name=" + name; // 拼接参数安全性、参数长度都有局限
// addFlashAttribute方法设置了一个flash类型属性,该属性会被暂存到session中,在跳转到页面之后该属性销毁
redirectAttributes.addFlashAttribute("name",name);
return "redirect:handle01";
}
第三部分 手写 MVC 框架
LgDispatcherServlet.java
package *;
import com.lagou.demo.service.IDemoService;
import com.lagou.edu.mvcframework.annotations.LagouAutowired;
import com.lagou.edu.mvcframework.annotations.LagouController;
import com.lagou.edu.mvcframework.annotations.LagouRequestMapping;
import com.lagou.edu.mvcframework.annotations.LagouService;
import com.lagou.edu.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LgDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<>(); // 缓存扫描到的类的全限定类名
// ioc容器
private Map<String,Object> ioc = new HashMap<String,Object>();
// handlerMapping
//private Map<String,Method> handlerMapping = now HashMap<>(); // 存储url和Method之间的映射关系
private List<Handler> handlerMapping = new ArrayList<>();
@Override
public void init(ServletConfig config) throws ServletException {
// 1 加载配置文件 springmvc.properties
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
doLoadConfig(contextConfigLocation);
// 2 扫描相关的类,扫描注解
doScan(properties.getProperty("scanPackage"));
// 3 初始化bean对象(实现ioc容器,基于注解)
doInstance();
// 4 实现依赖注入
doAutoWired();
// 5 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
initHandlerMapping();
System.out.println("lagou mvc 初始化完成....");
// 等待请求进入,处理请求
}
/*
构造一个HandlerMapping处理器映射器
最关键的环节
目的:将url和method建立关联
*/
private void initHandlerMapping() {
if(ioc.isEmpty()) {return;}
for(Map.Entry<String,Object> entry: ioc.entrySet()) {
// 获取ioc中当前遍历的对象的class类型
Class<?> aClass = entry.getValue().getClass();
if(!aClass.isAnnotationPresent(LagouController.class)) {continue;}
String baseUrl = "";
if(aClass.isAnnotationPresent(LagouRequestMapping.class)) {
LagouRequestMapping annotation = aClass.getAnnotation(LagouRequestMapping.class);
baseUrl = annotation.value(); // 等同于/demo
}
// 获取方法
Method[] methods = aClass.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
// 方法没有标识LagouRequestMapping,就不处理
if(!method.isAnnotationPresent(LagouRequestMapping.class)) {continue;}
// 如果标识,就处理
LagouRequestMapping annotation = method.getAnnotation(LagouRequestMapping.class);
String methodUrl = annotation.value(); // /query
String url = baseUrl + methodUrl; // 计算出来的url /demo/query
// 把method所有信息及url封装为一个Handler
Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));
// 计算方法的参数位置信息 // query(HttpServletRequest request, HttpServletResponse response,String name)
Parameter[] parameters = method.getParameters();
for (int j = 0; j < parameters.length; j++) {
Parameter parameter = parameters[j];
if(parameter.getType() == HttpServletRequest.class
|| parameter.getType() == HttpServletResponse.class) {
// 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse
handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j);
}else{
handler.getParamIndexMapping().put(parameter.getName(),j); // <name,2>
}
}
// 建立url和method之间的映射关系(map缓存起来)
handlerMapping.add(handler);
}
}
}
// 实现依赖注入
private void doAutoWired() {
if(ioc.isEmpty()) {return;}
// 有对象,再进行依赖注入处理
// 遍历ioc中所有对象,查看对象中的字段,是否有@LagouAutowired注解,如果有需要维护依赖注入关系
for(Map.Entry<String,Object> entry: ioc.entrySet()) {
// 获取bean对象中的字段信息
Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
// 遍历判断处理
for (int i = 0; i < declaredFields.length; i++) {
Field declaredField = declaredFields[i]; // @LagouAutowired private IDemoService demoService;
if(!declaredField.isAnnotationPresent(LagouAutowired.class)) {
continue;
}
// 有该注解
LagouAutowired annotation = declaredField.getAnnotation(LagouAutowired.class);
String beanName = annotation.value(); // 需要注入的bean的id
if("".equals(beanName.trim())) {
// 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入) IDemoService
beanName = declaredField.getType().getName();
}
// 开启赋值
declaredField.setAccessible(true);
try {
declaredField.set(entry.getValue(),ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
// ioc容器
// 基于classNames缓存的类的全限定类名,以及反射技术,完成对象创建和管理
private void doInstance() {
if(classNames.size() == 0) return;
try{
for (int i = 0; i < classNames.size(); i++) {
String className = classNames.get(i); // com.lagou.demo.controller.DemoController
// 反射
Class<?> aClass = Class.forName(className);
// 区分controller,区分service'
if(aClass.isAnnotationPresent(LagouController.class)) {
// controller的id此处不做过多处理,不取value了,就拿类的首字母小写作为id,保存到ioc中
String simpleName = aClass.getSimpleName();// DemoController
String lowerFirstSimpleName = lowerFirst(simpleName); // demoController
Object o = aClass.newInstance();
ioc.put(lowerFirstSimpleName,o);
}else if(aClass.isAnnotationPresent(LagouService.class)) {
LagouService annotation = aClass.getAnnotation(LagouService.class);
//获取注解value值
String beanName = annotation.value();
// 如果指定了id,就以指定的为准
if(!"".equals(beanName.trim())) {
ioc.put(beanName,aClass.newInstance());
}else{
// 如果没有指定,就以类名首字母小写
beanName = lowerFirst(aClass.getSimpleName());
ioc.put(beanName,aClass.newInstance());
}
// service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入
Class<?>[] interfaces = aClass.getInterfaces();
for (int j = 0; j < interfaces.length; j++) {
Class<?> anInterface = interfaces[j];
// 以接口的全限定类名作为id放入
ioc.put(anInterface.getName(),aClass.newInstance());
}
}else{
continue;
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
// 首字母小写方法
public String lowerFirst(String str) {
char[] chars = str.toCharArray();
if('A' <= chars[0] && chars[0] <= 'Z') {
chars[0] += 32;
}
return String.valueOf(chars);
}
// 扫描类
// scanPackage: com.lagou.demo package----> 磁盘上的文件夹(File) com/lagou/demo
private void doScan(String scanPackage) {
String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
File pack = new File(scanPackagePath);
File[] files = pack.listFiles();
for(File file: files) {
if(file.isDirectory()) { // 子package
// 递归
doScan(scanPackage + "." + file.getName()); // com.lagou.demo.controller
}else if(file.getName().endsWith(".class")) {
String className = scanPackage + "." + file.getName().replaceAll(".class", "");
classNames.add(className);
}
}
}
// 加载配置文件
private void doLoadConfig(String contextConfigLocation) {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求:根据url,找到对应的Method方法,进行调用
// 获取uri
// String requestURI = req.getRequestURI();
// Method method = handlerMapping.get(requestURI);
// 反射调用,需要传入对象,需要传入参数,此处无法完成调用,没有把对象缓存起来,也没有参数!!!!改造initHandlerMapping();
// method.invoke() //
// 根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list))
Handler handler = getHandler(req);
if(handler == null) {
resp.getWriter().write("404 not found");
return;
}
// 参数绑定
// 获取所有参数类型数组,这个数组的长度就是我们最后要传入的args数组的长度
Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
// 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的)
Object[] paraValues = new Object[parameterTypes.length];
// 以下就是为了向参数数组中塞值,而且还得保证参数的顺序和方法中形参顺序一致
Map<String, String[]> parameterMap = req.getParameterMap();
// 遍历request中所有参数 (填充除了request,response之外的参数)
for(Map.Entry<String,String[]> param: parameterMap.entrySet()) {
// name=1&name=2
String value = StringUtils.join(param.getValue(), ",");
// 如果参数和方法中的参数匹配上了,填充数据
if(!handler.getParamIndexMapping().containsKey(param.getKey())) {
continue;
}
// 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
Integer index = handler.getParamIndexMapping().get(param.getKey());
paraValues[index] = value; // 把前台传递过来的参数值填充到对应的位置去
}
int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
paraValues[requestIndex] = req;
int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
paraValues[responseIndex] = resp;
// 最终调用handler的method属性
try {
handler.getMethod().invoke(handler.getController(),paraValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private Handler getHandler(HttpServletRequest req) {
if(handlerMapping.isEmpty()){return null;}
String url = req.getRequestURI();
for(Handler handler: handlerMapping) {
Matcher matcher = handler.getPattern().matcher(url);
if(!matcher.matches()){continue;}
return handler;
}
return null;
}
}
Handler.java
package *;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 封装handler方法相关的信息
*/
public class Handler {
private Object controller; // method.invoke(obj,)
private Method method;
private Pattern pattern; // spring中url是支持正则的
private Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>
public Handler(Object controller, Method method, Pattern pattern) {
this.controller = controller;
this.method = method;
this.pattern = pattern;
this.paramIndexMapping = new HashMap<>();
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Pattern getPattern() {
return pattern;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public Map<String, Integer> getParamIndexMapping() {
return paramIndexMapping;
}
public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
this.paramIndexMapping = paramIndexMapping;
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lagou.edu</groupId>
<artifactId>mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件定义编译细节-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
<!--告诉编译器,编译的时候记录下形参的真实名称-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
springmvc.properties
scanPackage=com.lagou.demo
web.xml
<!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>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>lgoumvc</servlet-name>
<servlet-class>com.lagou.edu.mvcframework.servlet.LgDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springmvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>lgoumvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
注解代码及Controller,Service省略
第四部分 Spring MVC 源码深度剖析
1、前端控制器 DispatcherServlet 继承结构
2、重要时机点分析
2、1 Handler方法的执行时机
2、2 ⻚面渲染时机(打断点并观察调用栈)
SpringMVC处理请求的流程 (org.springframework.web.servlet.DispatcherServlet#doDispatch方法的执行过程)
- 调用getHandler()获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler+拦截器)
- 调用HandlerExecutionChain.getHandlerAdapter(); 获取能够执行1中Handler的适配器 HandlerAdapter
- HandlerAdapter适配器调用执行ha.handle() , 总会返回一个ModelAndView对象
- 调用processDispatchResult()方法完成视图渲染跳转
3、核心步骤getHandler方法剖析
4、核心步骤getHandlerAdapter方法剖析
5、核心步骤ha.handle方法剖析
6、核心步骤processDispatchResult方法剖析
解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名
把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求 域取出来的根本原因
将数据设置到请求域中
7、SpringMVC九大组件初始化
7、1 DispatcherServlet中定义了九个属性
每一个属性都对应一种组件, 九大组件都是定义了接口,接口其实就是定义了该组件的规范
比如ViewResolver、HandlerAdapter 等都是接口
/** MultipartResolver used by this servlet. */
// 多部件解析器
@Nullable
private MultipartResolver multipartResolver;
/** LocaleResolver used by this servlet. */
// 区域化 国际化解析器
@Nullable
private LocaleResolver localeResolver;
/** ThemeResolver used by this servlet. */
// 主题解析器
@Nullable
private ThemeResolver themeResolver;
/** List of HandlerMappings used by this servlet. */
// 处理器映射器组件
@Nullable
private List<HandlerMapping> handlerMappings;
/** List of HandlerAdapters used by this servlet. */
// 处理器适配器组件
@Nullable
private List<HandlerAdapter> handlerAdapters;
/** List of HandlerExceptionResolvers used by this servlet. */
// 异常解析器组件
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet. */
// 默认视图名转换器组件
@Nullable
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMapManager used by this servlet. */
// flash属性管理组件
@Nullable
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet. */
// 视图解析器
@Nullable
private List<ViewResolver> viewResolvers;
7、2 九大组件的初始化时机
Spring容器在启动时org.springframework.context.support.AbstractApplicationContext#onfresh()中调用了onRefresh(), 该方法由子类DispatcherServlet实现(初始化九大件)
如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进行注册初始化,默 认策略在DispatcherServlet.properties文件中配置
第五部分 SSM 整合
1、Mybatis整合Spring
1、1 整合目标
数据库连接池以及事务管理都交给Spring容器来完成 SqlSessionFactory对象应该放到Spring容器中作为单例对象管理 Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象
1、2 整合所需 Jar 分析
- Junit测试jar(4.12版本)
- Mybatis的jar(3.4.5) Spring相关jar(spring-context、spring-test、spring-jdbc、spring-tx、spring-aop、 aspectjweaver)
- Mybatis/Spring整合包jar(mybatis-spring-xx.jar)
- Mysql数据库驱动jar
- Druid数据库连接池的jar
pom.xml
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--spring相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!--mybatis与spring的整合包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
<!--数据库驱动jar-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bank
jdbc.username=root
jdbc.password=123456
applicationContext-service.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--包扫描-->
<context:component-scan base-package="com.lagou.edu.service"/>
<!--事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务管理注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
applicationContext-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--包扫描-->
<context:component-scan base-package="com.lagou.edu.mapper"/>
<!--数据库连接池以及事务管理都交给Spring容器来完成-->
<!--引入外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三方jar中的bean定义在xml中-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--
SqlSessionFactory对象应该放到Spring容器中作为单例对象管理
原来mybaits中sqlSessionFactory的构建是需要素材的:SqlMapConfig.xml中的内容
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--别名映射扫描-->
<property name="typeAliasesPackage" value="com.lagou.edu.pojo"/>
<!--数据源dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象-->
<!--扫描mapper接口,生成代理对象,生成的代理对象会存储在ioc容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--mapper接口包路径配置-->
<property name="basePackage" value="com.lagou.edu.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
AccountMapper接口
package com.lagou.edu.mapper;
import com.lagou.edu.pojo.Account;
import java.util.List;
public interface AccountMapper {
// 定义dao层接口方法--> 查询account表所有数据
List<Account> queryAccountList() throws Exception;
}
AccountMapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lagou.edu.mapper.AccountMapper">
<select id="queryAccountList" resultType="com.lagou.edu.pojo.Account">
select * from account
</select>
</mapper>
测试程序
import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:application*.xml"})
public class MybatisSpringTest {
// 希望测试ioc容器中的哪个对象你注入即可。
@Autowired
private AccountService accountService;
@Test
public void testMybatisSpring() throws Exception {
List<Account> accounts = accountService.queryAccountList();
for (int i = 0; i < accounts.size(); i++) {
Account account = accounts.get(i);
System.out.println(account);
}
}
}
2、 整合SpringMVC
pom.xml
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!--jsp-api&servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--页面使用jstl表达式-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!--json数据交互所需jar,start-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
<!--json数据交互所需jar,end-->
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--扫描controller-->
<context:component-scan base-package="com.lagou.edu.controller"/>
<!--配置springmvc注解驱动,自动注册合适的组件handlerMapping和handlerAdapter-->
<mvc:annotation-driven/>
</beans>
web.xml
<!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>
<display-name>Archetype Created Web Application</display-name>
<!-- 解决post乱码问题 -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!-- 设置编码参是UTF8 -->
<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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
Get请求乱码(Get请求乱码需要修改tomcat下server.xml的配置)
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext*.xml</param-value>
</context-param>
<!--spring框架启动-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springmvc启动-->
<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*:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Controller类
package com.lagou.edu.controller;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
@RequestMapping("/account")
public class AccountController {
/**
* Spring容器和SpringMVC容器是有层次的(父子容器)
* Spring容器:service对象+dao对象
* SpringMVC容器:controller对象,,,,可以引用到Spring容器中的对象
*/
@Autowired
private AccountService accountService;
@RequestMapping("/queryAll")
@ResponseBody
public List<Account> queryAll() throws Exception {
return accountService.queryAccountList();
}
}
附录 Spring MVC 必备设计模式
1、策略模式 Strategy
就是一个问题有多种解决方案,选择其中的一种使用,这种情况下我们 使用策略模式来实现灵活地选择,也能够方便地增加新的解决方案。比如做数学题,一个问题的 解法可能有多种;再比如商场的打折促销活动,打折方案也有很多种,有些商品是不参与折扣活 动要按照原价销售,有些商品打8.5折,有些打6折,有些是返现5元等。
-
结构
策略(Strategy) 定义所有支持算法的公共接口。 Context 使用这个接口来调用某 ConcreteStrategy 定义的算法。 策略实现(ConcreteStrategy) 实现了Strategy 接口的具体算法 上下文(Context) 维护一个 Strategy 对象的引用 用一个 ConcreteStrategy 对象来装配 可定义一个接口方法让 Strategy 访问它的数据
-
示例
假如现在有一个商场优惠活动,有的商品原价售卖,有的商品打8.5折,有的商品打6折,有的返现 5元
package designpattern.strategy.old;
import java.text.MessageFormat;
public class BuyGoods {
private String goods;
private double price;
private double finalPrice;
private String desc;
public BuyGoods(String goods, double price) {
this.goods = goods;
this.price = price;
}
public double calculate(String discountType) {
if ("discount85".equals(discountType)) {
finalPrice = price * 0.85;
desc = "该商品可享受8.5折优惠";
} else if ("discount6".equals(discountType)) {
finalPrice = price * 0.6;
desc = "该商品可享受6折优惠";
} else if ("return5".equals(discountType)) {
finalPrice = price >= 5 ? price - 5 : 0;
desc = "该商品可返现5元"; } else {
finalPrice = price;
desc = "对不起,该商品不参与优惠活动";
}
System.out.println(MessageFormat.format("您购买的商品为:{0},原价为: {1},{2},最终售卖价格为:{3}",
goods, price, desc, finalPrice));
return finalPrice;
}
}
public class Test {
public static void main(String[] args) {
BuyGoods buyGoods1 = new BuyGoods("Java编程思想", 99.00);
buyGoods1.calculate("discount85");
BuyGoods buyGoods2 = new BuyGoods("罗技鼠标", 66 );
buyGoods2.calculate("discount6");
BuyGoods buyGoods3 = new BuyGoods("苹果笔记本", 15000.00);
buyGoods3.calculate("return5");
BuyGoods buyGoods4 = new BuyGoods("佳能相机", 1900);
buyGoods4.calculate(null);
}
}
上述代码可以解决问题,但是从代码设计的⻆度还是存在一些问题
- 增加或者修改打折方案时必须修改 BuyGoods 类源代码,违反了面向对象设计的 “开闭原 则”,代码的灵活性和扩展性较差。
- 打折方案代码聚合在一起,如果其他项目需要重用某个打折方案的代码,只能复制粘贴对应代码,无法以类组件的方式进行重用,代码的复用性差。
- BuyGoods 类的 calculate() 方法随着优惠方案的增多会非常庞大,代码中会出现很多if分 支,可维护性差。
此时,我们可以使用策略模式对 BuyGoods 类进行重构,将打折方案逻辑(算法)的定义和使用分离。
抽象策略类 AbstractDiscount,它是所有具体打折方案(算法)的父类,定义了一个discount 抽象方法
public abstract class AbstractDiscount {
protected double finalPrice;
protected String desc;
public AbstractDiscount(String desc) {
this.desc = desc;
}
public abstract double discount(double price);
}
四种具体策略类,继承自抽象策略类 AbstractDiscount,并在 discount 方法中实现具体的打折方案(算法)
public class Discount85 extends AbstractDiscount {
public Discount85() {
super("该商品可享受8.5折优惠");
}
@Override
public double discount(double price) {
finalPrice = price * 0.85;
return finalPrice;
}
}
public class Discount6 extends AbstractDiscount {
public Discount6() {
super("该商品可享受6折优惠");
}
@Override
public double discount(double price) {
finalPrice = price * 0.6;
return finalPrice;
}
}
public class Return5 extends AbstractDiscount {
public Return5() {
super("该商品可返现5元");
}
@Override
public double discount(double price) {
this.finalPrice = price >= 5 ? price - 5 : 0;
return finalPrice;
}
}
public class NoDiscount extends AbstractDiscount {
public NoDiscount() {
super("对不起,该商品不参与优惠活动");
}
@Override
public double discount(double price) {
finalPrice = price;
return finalPrice;
}
}
类 BuyGoods,维护了一个 AbstractDiscount 引用
public class BuyGoods {
private String goods;
private double price;
private AbstractDiscount abstractDiscount;
public BuyGoods(String goods, double price, AbstractDiscount abstractDiscount) {
this.goods = goods;
this.price = price;
this.abstractDiscount = abstractDiscount;
}
public double calculate() {
double finalPrice = abstractDiscount.discount(this.price);
String desc = abstractDiscount.getDesc();
System.out.println(MessageFormat.format("商品:{0},原价:{1},{2},最终价格为:{3}", goods, price, desc, finalPrice));
return finalPrice;
}
}
public class Test {
public static void main(String[] args) {
BuyGoods buyGoods1 = new BuyGoods("Java编程思想", 99.00, new Discount85());
buyGoods1.calculate();
BuyGoods buyGoods2 = new BuyGoods("罗技鼠标", 66, new Discount6());
buyGoods2.calculate();
BuyGoods buyGoods3 = new BuyGoods("苹果笔记本", 15000.00, new Return5());
buyGoods3.calculate();
BuyGoods buyGoods4 = new BuyGoods("佳能相机", 1900, new NoDiscount());
buyGoods4.calculate();
}
}
重构后:
增加新的优惠方案时只需要继承抽象策略类即可,修改优惠方案时不需要修改BuyGoods类 源码;
代码复用也变得简单,直接复用某一个具体策略类即可; BuyGoods类的calculate变得简洁,没有了原本的if分支;
2、模板方法模式
-
模板方法模式是指定义一个算法的⻣架,并允许子类为一个或者多个步骤提供实现。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为型设计模式。
-
采用模板方法模式的核心思路是处理某个流程的代码已经具备,但其中某些节点的代码暂时不能确 定。此时可以使用模板方法。
-
示例
/**
* 面试大厂流程类
*/
public abstract class Interview {
private final void register() {
System.out.println("面试登记");
}
protected abstract void communicate();
private final void notifyResult() {
System.out.println("HR小姐姐通知面试结果");
}
protected final void process() {
this.register();
this.communicate();
this.notifyResult();
}
}
/**
* 面试人员1,它是来面试Java工程师的
*/
public class Interviewee1 extends Interview{
public void communicate() {
System.out.println("我是面试人员1,来面试Java工程师,我们聊的是Java相关内容");
}
}
/**
* 面试人员2,它是来面试前端工程师的
*/
public class Interviewee2 extends Interview{
public void communicate() {
System.out.println("我是面试人员2,来面试前端工程师,我们聊的是前端相关内 容");
}
}
public class InterviewTest {
public static void main(String[] args) {
// 面试Java工程师
Interview interviewee1 = new Interviewee1();
interviewee1.process();
// 面试前端工程师
Interview interviewee2 = new Interviewee2();
interviewee2.process();
}
}
3、适配器模式
使得原本由于接口不兼容而不能一起工作、不能统一管理的那些类可以一起工作、可以进行统一管理
3、1 解决接口不兼容而不能一起工作问题
看下面一个非常经典的案例:
在中国,⺠用电都是220v交流电,但是手机锂电池用的都是5v直流电。因此,我们给手机充电时就需要使用电源适配器来进行转换。使用代码还原这个生活场景 创建AC220类,表示220v交流电, 创建DC5接口,表示5V直流电
public class AC220 {
public int outputAC220V() {
int output = 220;
System.out.println("输出交流电" + output + "V");
return output;
}
}
public interface DC5 {
int outputDC5V();
}
//创建电源适配器类 PowerAdapter
public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
@Override
public int outputDC5V() {
int adapterInput = ac220.outputAC220V();
// 变压器...
int adapterOutput = adapterInput/44;
System.out.println("使用 PowerAdapter 输入AC:" + adapterInput + "V输出DC:" + adapterOutput + "V");
return adapterOutput;
}
}
public class AdapterTest {
public static void main(String[] args) {
DC5 dc5 = new PowerAdapter(new AC220());
dc5.outputDC5V();
}
}
在上面的案例中,通过增加电源适配器类PowerAdapter实现了二者的兼容
3、2 解决不能统一管理的问题
SpringMVC中处理器适配器(HandlerAdapter)机制就是解决类统一管理问题非常经典的场景
其中HandlerAdapter接口是处理器适配器的顶级接口,它有多个子类,包括 AbstractHandlerMethodAdapter、SimpleServletHandlerAdapter、 SimpleControllerHandlerAdapter、HttpRequestHandlerAdapter、 RequestMappingHandlerAdapter
其适配器调用的关键代码也在DispatcherServlet的 doDispatch() 方法中