SpringMVC笔记:源码剖析和设计模式


前言

沉迷学习不可自拔,女人只会影响我的学习效率。


一、前端控制器 DispatcherServlet 继承结构

在这里插入图片描述


二、重要时机点分析

1. Handler方法的执行时机(打断点并观察调用栈)

doDispathch方法中的1064行代码完成handler方法的调用
在这里插入图片描述

2. 页面渲染时机(打断点并观察调用栈)

在这里插入图片描述

3. SpringMVC处理请求的流程

SpringMVC处理请求的流程即为org.springframework.web.servlet.DispatcherServlet#doDispatch方法的执行过程,其中步骤
2、3、4、5是核心步骤

  1. 检查是否是文件上传的请求
  2. 调用getHandler()获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler+拦截器)
  3. 调用getHandlerAdapter();获取能够执行1中Handler的适配器
  4. 适配器调用Handler执行ha.handle(总会返回一个ModelAndView对象)
  5. 调用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框架

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值