文章目录
前言
沉迷学习不可自拔,女人只会影响我的学习效率。
一、前端控制器 DispatcherServlet 继承结构
二、重要时机点分析
1. Handler方法的执行时机(打断点并观察调用栈)
doDispathch方法中的1064行代码完成handler方法的调用
2. 页面渲染时机(打断点并观察调用栈)
3. SpringMVC处理请求的流程
SpringMVC处理请求的流程即为org.springframework.web.servlet.DispatcherServlet#doDispatch方法的执行过程,其中步骤
2、3、4、5是核心步骤
- 检查是否是文件上传的请求
- 调用getHandler()获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler+拦截器)
- 调用getHandlerAdapter();获取能够执行1中Handler的适配器
- 适配器调用Handler执行ha.handle(总会返回一个ModelAndView对象)
- 调用processDispatchResult()方法完成视图渲染跳转
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 1 检查是否是⽂件上传的请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/*
2 取得处理当前请求的Controller,这⾥也称为Handler,即处理器
这⾥并不是直接返回 Controller,⽽是返回 HandlerExecutionChain 请求处理链对象
该对象封装了Handler和Inteceptor
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 如果 handler 为空,则返回404
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 3 获取处理请求的处理器适配器 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 处理 last-modified 请求头
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request,
mappedHandler.getHandler());
if (new ServletWebRequest(request,
response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 4 实际处理器处理请求,返回结果视图对象
mv = ha.handle(processedRequest, response,
mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 结果视图对象的处理
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 5 跳转⻚⾯,渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
} catch (Exception ex) {
//最终会调⽤HandlerInterceptor的afterCompletion ⽅法
triggerAfterCompletion(processedRequest, response, mappedHandler,
ex);
} catch (Throwable err) {
//最终会调⽤HandlerInterceptor的afterCompletion ⽅法
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
三、核心步骤getHandler方法剖析
遍历两个HandlerMapping,试图获取能够处理当前请求的执行链
四、核心步骤getHandlerAdapter方法剖析
遍历各个HandlerAdapter,看哪个Adapter支持处理当前Handler
五、核心步骤ha.handle方法剖析
- 入口
- 断点从入口进入
六、核心步骤processDispatchResult方法剖析
- render方法完成渲染
- 视图解析器解析出View视图对象
- 在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的View实现
- 解析出View视图对象的过程中,要将逻辑视图名解析为物理视图名
- 封装View视图对象之后,调用了view对象的render方法
- 渲染数据
- 把modelMap中的数据暴露到request域中,这也是为什么后台model.add之后在jsp中可以从请求域取出来的根本原因
- 将数据设置到请求域中
七、SpringMVC九大组件初始化
1). 在DispatcherServlet中定义了九个属性,每一个属性都对应一种组件
/** 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;
九大组件都是定义了接口,接口其实就是定义了该组件的规范,比如ViewResolver、HandlerAdapter等都是接口
2). 九大组件的初始化时机
- DispatcherServlet中的onRefresh(),该⽅法中初始化了九大组件
- initStrategies方法
- 观察其中的一个组件initHandlerMappings(context)
- 如果按照类型和按照固定id从ioc容器中找不到对应组件,则会按照默认策略进行注册初始化,默认策略在DispatcherServlet.properties文件中配置
- DispatcherServlet.properties
- 注意:多部件解析器的初始化必须按照id注册对象(multipartResolver)
八、策略模式
策略模式(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;
}
}
测试
package designpattern.strategy.old;
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抽象方法
package designpattern.strategy.now.discount;
public abstract class AbstractDiscount {
public double getFinalPrice() {
return finalPrice;
}
public void setFinalPrice(double finalPrice) {
this.finalPrice = finalPrice;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
protected double finalPrice;
protected String desc;
public IDiscount(String desc) {
this.desc = desc;
}
public abstract double discount(double price);
}
四种具体策略类,继承自抽象策略类 AbstractDiscount,并在 discount 方法中实现具体的打折方案(算法)
package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class Discount85 extends AbstractDiscount {
public Discount85() {
super("该商品可享受8.5折优惠");
}
@Override
public double discount(double price) {
finalPrice = price * 0.85;
return finalPrice;
}
}
//-------------------------------------------------------------------
package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class Discount6 extends AbstractDiscount {
public Discount6() {
super("该商品可享受6折优惠");
}
@Override
public double discount(double price) {
finalPrice = price * 0.6;
return finalPrice;
}
}
//-------------------------------------------------------------------
package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class Return5 extends AbstractDiscount {
public Return5() {
super("该商品可返现5元");
}
@Override
public double discount(double price) {
this.finalPrice = price >= 5 ? price - 5 : 0;
return finalPrice;
}
}
//-------------------------------------------------------------------
package designpattern.strategy.now.discount.impl;
import designpattern.strategy.now.discount.AbstractDiscount;
public class NoDiscount extends AbstractDiscount {
public NoDiscount() {
super("对不起,该商品不参与优惠活动");
}
@Override
public double discount(double price) {
finalPrice = price;
return finalPrice;
}
}
类 BuyGoods,维护了一个 AbstractDiscount 引用
package designpattern.strategy.now;
import designpattern.strategy.now.discount.AbstractDiscount;
import java.text.MessageFormat;
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;
}
}
测试
package designpattern.strategy.now;
import designpattern.strategy.now.discount.impl.*;
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 分支;
九、模板方法模式
- 模板方法模式是指定义⼀个算法的骨架,并允许子类为⼀个或者多个步骤提供实现。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤,属于行为型设计模式。
采用模板方法模式的核心思路是处理某个流程的代码已经具备,但其中某些节点的代码暂时不能确定,此时可以使用模板方法。 - 示例
package com.lagou.edu;
// ⾯试⼤⼚流程类
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();
}
}
Java岗位面试者
package com.lagou.edu;
// ⾯试⼈员1,它是来面试Java⼯程师的
public class Interviewee1 extends Interview {
public void communicate() {
System.out.println("我是⾯试⼈员1,来⾯试Java⼯程师,我们聊的是Java相关内容");
}
}
前端岗位面试者
package com.lagou.edu;
//⾯试⼈员2,它是来⾯试前端⼯程师的
public class Interviewee2 extends Interview {
public void communicate() {
System.out.println("我是⾯试⼈员2,来⾯试前端⼯程师,我们聊的是前端相关内容");
}
}
客户端测试类
package com.lagou.edu;
public class InterviewTest {
public static void main(String[] args) {
// ⾯试Java⼯程师
Interview interviewee1 = new Interviewee1();
interviewee1.process();
// ⾯试前端⼯程师
Interview interviewee2 = new Interviewee2();
interviewee2.process();
}
}
打印结果
十、适配器模式
使得原本由于接口不兼容而不能一起工作、不能统一管理的那些类可以一起工作、可以进行统一管理。
1. 解决接口不兼容而不能一起工作问题
看下面一个非常经典的案例:
在中国,民用电都是220v交流电,但是手机锂电池用的都是5v直流电。因此给手机充电时就需要使用电源适配器来进行转换。使用代码还原这个生活场景
创建AC220类,表示220v交流电
package com.lagou.edu;
import com.sun.org.apache.bcel.internal.generic.RETURN;
public class AC220 {
public int outputAC220V() {
int output = 220;
System.out.println("输出交流电" + output + "V");
return output;
}
}
创建DC5接口,表示5V直流电:
package com.lagou.edu;
public interface DC5 {
int outputDC5V();
}
创建电源适配器类 PowerAdapter
package com.lagou.edu;
public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
public int outputDC5V() {
int adapterInput = ac220.outputAC220V();
// 变压器...
int adapterOutput = adapterInput / 44;
System.out.println("使⽤ PowerAdapter 输⼊AC:" + adapterInput + "V输出DC:" + adapterOutput + "V");
return adapterOutput;
}
}
客户端测试代码
package com.lagou.edu;
public class AdapterTest {
public static void main(String[] args) {
DC5 dc5 = new PowerAdapter(new AC220());
dc5.outputDC5V();
}
}
在上面的案例中,通过增加电源适配器类PowerAdapter实现了⼆者的兼容
2. 解决不能统一管理的问题
SpringMVC中处理器适配器(HandlerAdapter)机制就是解决类统⼀管理问题非常经典的场景。
其中 HandlerAdapter接口是处理器适配器的顶级接口,它有多个子类,包括AbstractHandlerMethodAdapter、SimpleServletHandlerAdapter、SimpleControllerHandlerAdapter、HttpRequestHandlerAdapter、RequestMappingHandlerAdapter
其适配器调用的关键代码也在DispatcherServlet的doDispatch()方法中
在 doDispatch() 方法中调用了 getHandlerAdapter() 方法
在 getHandlerAdapter() 方法中循环调用了 supports() 方法判断是否兼容,循环迭代集合中的“Adapter” 在初始化时已经赋值。
十一、 SpringMVC介绍和手写MVC框架
笔记地址:SpringMVC介绍和手写MVC框架