第一部分 SpringMVC应用
1. SpringMVC简介
1.1 MVC结构体系
三层架构
当今互联网开发架构一般是基于两种形式,一种是C/S(客户端/服务端);另一种是B/S架构(浏览器/服务端)。在JavaEE开发中,几乎都是基于B/S架构开发。在B/S架构中,系统又分为三层架构,包括:表现层、业务层、持久层。
- 表现层
也就是常说的web层,它负责接收客户端请求,向客户端响应结构,通常使用http协议,web层接收http请求,完成http响应。
表现层包括:展示层和控制层,控制层负责接收请求,展示层负责结果展示。
- 业务层
也就是常说的service层,它负责处理业务逻辑,和我们开发项目的需求息息相关。
表现层和业务层直接的关系:表现层依赖业务层,业务层不依赖表现层。
- 持久层
也就是常说的dao层,负责数据持久化,包括数据层,即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,通俗来讲就是持久层和数据库进行交互,对数据库表进行增删改查。
MVC设计模型
MVC,即模型(model) - 视图(view) - 控制器(controller)的缩写。
- Model:包含业务模型和数据模型,数据模型用于封装数据,业务模型用于处理业务。
- View:指Jsp或html,用于展示数据。
- Controller:web应用程序中处理用户交互,处理程序逻辑。
1.2 SpringMVC是什么?
SpringMVC全称Spring Web MVC,是基于MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork后续产品。
SpringMVC已成为目前最主流的MVC框架之一,已全面超越Struts2,成为最有效的MVC框架。
它本质上是对Servlet的封装,简化了servlet的开发。
作用:1)接收请求;2)返回响应,跳转页面。
主要职责:处理前端http请求。
2. SpringMVC工作流程
流程说明
- 用户发起请求至前端控制器DispatcherServlet
- DispatcherServlet接收请求调用HandlerMapping处理器映射器
- HandlerMapping处理器映射器根据请求url找到具体的Handler(后端控制器),生成处理器对象及处理器拦截器一并返回DispatcherServlet
- DispatcherServlet调用HandlerAdapter处理器适配器去调用Handler
- 处理器适配器执行Handler,执行完成后返回ModelAndView至DispatcherServlet
- 前端控制器请求视图解析器对视图进行解析
- 视图解析器向前端控制器返回View
- 前端控制器对视图进行渲染,将数据模型填充到request域
- 前端控制器向用户响应结果
2.1 SpringMVC九大组件
- HandlerMapping(处理器映射器):查找Handler,标注@RequestMapping的方法都可以看成一个Handler。负责具体请求处理,请求到达后,找到请求对应的处理器Handler和拦截器Interceptor
- HandlerAdapter(处理器适配器):执行Handler。
- HandlerExceptionResolver(异常解析器):用于处理Handler产生的异常情况。
- ViewResolver(视图解析器):用于将String类型的视图名和Locale解析为View类型的视图。
- RequestToViewNameTranslator(默认视图名转换器):从请求中获取ViewName。
- LocaleResolver(区域化解析器):用于从请求中解析出Locale,比如:中国的Locale 是zh-CN。
- ThemeResolver(主题解析器):用于解析主题。
- MultipartResolver(多部件解析器):用于上传请求,作用就是封装普通请求,使其拥有文件上传的功能。
- FlashMapManager:用于重定向时的参数传递。
3. 请求参数绑定
-
默认支持Servlet API作为方法参数,当需要使⽤HttpServletRequest、HttpServletResponse、HttpSession等原⽣servlet对象时,直接在handler⽅法中形参声明使⽤即可。
-
绑定简单类型参数
简单数据类型:八种基本数据类型及其包装类型(推荐),绑定简单数据类型参数,只需直接声明形参即可。
-
绑定pojo类型参数
直接声明形参即可,要求参数名必须和pojo的属性名保持一致
-
绑定pojo包装对象参数
传参参数名和pojo属性保持一致,如果不能定位数据项,通过属性名.属性的方式进一步锁定数据
-
绑定日期类型参数(需定义类型转换器)
- 自定义类型转换器
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; } }
- 注册自定义类型转换器
<mvc:annotation-driven conversion-service="conversionServiceBean"/> <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceF actoryBean"> <property name="converters"> <set> <bean class="com.lagou.edu.converter.DateConverter"></bean> </set> </property> </bean>
4. 对Restful风格请求支持
4.1 什么是Restful
Restful 是⼀种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是⼀个资源定位及资源操作的⻛
格。
优点: 结构清晰、符合标准、易于理解、扩展方便。
特性:
- 资源:网络上的一个实体,或者说是网络上的一个具体信息。
- 表现层:把资源具体呈现处理的形式,叫做它的表现层。
- 状态转化:发起一个请求,就代表客户端和服务器的一次交互过程。
示例:
/account/1 HTTP GET 查询id=1的account
/account/1 HTTP DELETE 删除id=1的account
/account/1 HTTP PUT 更新id=1的account
5.Ajax Json交互
- 前端到后台:前端ajax发生json格式字符串,后台直接接收为pojo参数。(注解@RequestBody)
- 后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串。(注解@ResponseBody)
5.1 什么是Json
Json是一种与语言无关的数据交互格式,就是一种字符串,用特殊符号{}内表示对象、[]表示数组、""表示属性或值、:表示后者是前者的值。
{“name”: “Michael”}可以理解为是⼀个包含name为Michael的对象
[{“name”: “Michael”},{“name”: “Jerry”}]就表示包含两个对象的数组
5.2 @ResponseBody注解
@ResponseBody注解作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入response对象的body区,通常用来返回JSON数据或是XML数据。
5.3 分析SpringMVC使用Json交互
- 所需jar包
<!--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>
-
示例代码
- 前端jsp及js代码
<div> <h2>Ajax json交互</h2> <fieldset> <input type="button" id="ajaxBtn" value="ajax提交"/> </fieldset> </div>
$(function () { $("#ajaxBtn").bind("click",function () { // 发送ajax请求 $.ajax({ url: '/demo/handle07', type: 'POST', data: '{"id":"1","name":"李四"}', contentType: 'application/json;charset=utf-8', dataType: 'json', success: function (data) { alert(data.name); } }) }) })
- 后台代码
@RequestMapping("/handle07") // 添加@ResponseBody之后,不再⾛视图解析器那个流程,⽽是等同于response直接输出 数据 public @ResponseBody User handle07(@RequestBody User user) { // 业务逻辑处理,修改name为张三丰 user.setName("张三丰"); return user; }
第二部分 SpringMVC高级技术
1. 拦截器(Intecepter)使用
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的访问等,只会拦截访问的控制器方法。
从配置角度来看:servlet、filter、listener是配置在web.xml中,而interceptor配置在框架自己的配置文件中。
- 在Handler业务逻辑执行之前拦截一次
- 在Handler逻辑执行完毕但未跳转页面之前拦截一次
- 跳转页面之后拦截一次
1.2 拦截器的执行流程
拦截器的执行是有一定的顺序的,该顺序与配置文件中定义的拦截器的顺序一致,单个拦截器,在程序中的执行流程如下图所示:
1)程序先执行preHandle()方法,如果该方法的返回值为true,则程序继续向下执行处理器中的方法,否则将不再向下执行。
2)在业务处理器(控制器Controller类)处理完请求后,会执行postHandle()方法,然后通过DispatcherServlet向客户端返回响应。
3)DispatcherServlet处理完请求后,才会执行afterCompletion()方法。
1.3 多个拦截器的执行流程
多个拦截器,在程序中的执行流程如下图所示:
2. 控制器中处理异常
@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;
}
}
3. 基于Flash属性的跨重定向请求数据传递
重定向时请求参数会丢失,往往通过重新携带请求参数,参数拼接如下:
return "redirect:handle01?name=" + name;
但是此方法有局限性,因为携带参数长度有限制,参数安全性也不高。此时,可以使用SpringMVC提供的flash属性机制,向上下文中添加flash属性,框架会在session中记录该属性值,跳转页面之后会自动删除flash属性,通过此方法进行重定向参数传递,参数长度和安全性得到保障。
@RequestMapping("/handleRedirect")
public String handleRedirect(String name,RedirectAttributes
redirectAttributes) {
//return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有局限
// addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在
跳转到⻚⾯之后该属性销毁
redirectAttributes.addFlashAttribute("name",name);
return "redirect:handle01";
}
第三部分 SpringMVC源码深度剖析
1. 前端控制器 DispatcherServlet继承结构
2. SpringMVC处理请求流程
时序图如下:
1)调用getHandler()获取能够处理当前请求的执行联HandlerExecutionChain(Handler+拦截器)
遍历两个HandlerMapping,获取能够处理当前请求的执行链
2)调用getHandlerAdapter()方法;获取能够执行1)中Handler的适配器
遍历各个HandlerAdapter,找到支持处理当前Handler的适配器
3)适配器调用Handler执行ha.handle(返回一个ModelAndView对象)
入口:ha.handle()
4)调用processDispatchResult()方法完成视图渲染跳转
入口:rander方法
- 视图解析器解析出View视图对象
- 解析出View视图对象的过程中会判断是否重定向、是否转发等,不同情况封装的是不同的View实现
- 解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名
- 封装View视图对象之后,调用view对象的render方法
- 渲染数据
- 把modelMap中的数据暴露到request域中,后台通过el表达式直接取值
3. SpringMVC九大组件初始化
1)在DispatcherServlet中定义了九个属性,每⼀个属性都对应⼀种组件
// 多部件解析器
private MultipartResolver multipartResolver;
// 国际化解析器
private LocaleResolver localeResolver;
// 主题解析器
private ThemeResolver themeResolver;
// 处理器映射器
private List<HandlerMapping> handlerMappings;
// 处理器适配器
private List<HandlerAdapter> handlerAdapters;
// 异常解析器
private List<HandlerExceptionResolver> handlerExceptionResolvers;
// 默认视图名转换器
private RequestToViewNameTranslator viewNameTranslator;
// flash属性管理器
private FlashMapManager flashMapManager;
// 视图解析器
private List<ViewResolver> viewResolvers;
2)九大组件初始化时机
- DispatcherServlet中的onRefresh(),此方法初始化了九大组件
- initStrategies方法
- 观看其中一个组件
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 找到所有的HandlerMapping
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 否则在ioc中按照固定名称去找
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
// 最后还为空则按照默认策略生成
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
如果按照类型和固定id去ioc容器中找不到对应组件,则按照默认策略进行初始化,默认策略在DispatcherServlet.properties中配置
- DispatcherServlet.properties
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
- 注意:有些组件解析器的初始化必须按照id注册对象(比如multipartResolver、localeResolver、themeResolver等)