回顾

DispatcherServlet

◆解析请求路径和请求方法
◆依赖容器,建立并维护Controller方法与请求的映射
◆用合适得Controller方法去处理特定的请求

image-20220116144830604

由于给DispatcherServlet标记了@WebServlet("/"),所以在tomcat启动之后会将DispatcherServlet给加载进来。

因为设置了/匹配所有的的url-pattern,而且在tomcat插件中的设置了项目的根路径,< path>/${project.artifactId}< /path> 可以获取项目的名字,这样设置之后以项目名为根路径的请求都会经由DispatcherServlet来处理,里面的service方法就是用来处理请求的。

image-20220116145239362

相关的请求就会交给DispatcherServlet来处理,在DispatcherServlet加载到tomcat里面之后,在首次接收外部请求的时候,会调用里面init方法来完成自身的初始化,初始化之后调用service来处理相关的请求。后续的请求再到来时不会再执行初始化方法,直接调用service方法来处理请求。

HttpServletRequest里面包含了请求路径和请求方法以及请求里面的业务参数。通过类似简单工厂方法的模式解析请求方法和请求路径,按照请求路径和请求方法转发到对应的controller方法处理。

image-20220116151411257

在实际的使用中可以选择将返回的数据转换为json格式返回给前端,或者也可以生成相关的页面视图返回给前端去做渲染。

自研框架MVC的实现

由于/的优先级比较低,当直接访问某个jsp页面的时候,会由jsp的servlet去处理而不是由当前的servlet来处理。框架就要负责拦截所有的请求,不管是静态的还是controller请求。可以将/ 改为/*

MVC架构草图:

image-20220116152804531

大致流程

获取http请求和需要回发的http响应对象,之后将他们委托给RequestProcessorChain处理。我们只处理get和post方法的请求,RequestProcessorChain参照的是责任链模式的后置处理器的处理逻辑,里面保存了处理RequestProcessor接口的多个不同的实现类,之所以会有多个不同的实现类对应为DispatcherServlet是项目里面所有请求的唯一入口。这些请求里即会有获取jsp页面的请求,也会有获取静态资源的请求、直接获取json数据的请求等,针对不同的请求会使用不同的RequestProcessor来处理。

RequestProcessor矩阵:

PreRequestProcessor处理器:主要负责对请求的编码以及对路径做一些前置处理。

剩余的处理器都会去解析请求,以看看请求是否由该处理器去处理的。

StaticResourceRequestProcessor处理器:对静态资源请求

JspRequestProcessor处理器:对jsp页面的访问请求进行处理(不仅过controller直接访问页面的请求)

ControllerRequestProcessor处理器:将请求派发到对应的controller方法里面进行处理

Render矩阵

渲染,处理了相关的请求之后,需要将结果以不同的形式给展现出来,并且处理的过程中可能会出现各种各样的异常,也需要去做体现。Render负责对结果进行包装并展现。

当处理器处理完之后就会调用特定的实现了Render接口的实现类,对处理结果进行展现。

DefaultResultRender:当请求处理成功后,用户只需要返回一个成功的状态码

JsonResultRender:用户发送的请求是想要获取json格式的返回结果

ViewResultRender:针具页面的渲染需求,类似于ModelAndView对象

InternalErrorResultRender:对异常的处理

ResourceNotFoundResultRende: 资源无法找到的异常

init方法

init方法:对常驻变量进行初始化

servlet是程序执行的入口:对容器进行初始化并将相关的bean加载进来,同时完成AOP相关逻辑的织入,以及相关的IoC依赖注入等操作。因为后面是采用责任链模式来实现RequestProcessor矩阵的,将对应的处理器添加到处理器列表中

将RequestProcessor矩阵按序添加到缓存列表里

@Override
public void init() throws ServletException {
    //1、初始化容器
    BeanContainer beanContainer = BeanContainer.getInstance();
    beanContainer.loadBeans("com.imooc");
    new AspectWeaver().doAop();
    new DependencyInjector().doIoc();
    //2、初始化请求处理器责任链
    PROCESSOR.add(new PreRequestProcessor());
    PROCESSOR.add(new StaticResourceRequestProcessor());
    PROCESSOR.add(new JspRequestProcessor());
    PROCESSOR.add(new ControllerRequestProcessor());
}

之所以按照图示的顺序进行添加,是因为我们的请求经过编码和路径的处理之后下能进行后续的处理。将ControllerRequestProcessor放在最后是因为它的处理会比较耗时,需要将请求和controller的方法实例进行匹配。

service方法
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1、创建责任链对象实例
    RequestProcessChain requestProcessChain = new RequestProcessChain(PROCESSOR.iterator(),req,resp);
    //2、通过责任链模式来依次调用请求处理器对请求进行处理
    requestProcessChain.doRequestProcessorChain();
    //3、对处理结果进行渲染
    requestProcessChain.doRender();
}

resultRender请求结果渲染器

DispatcherServlet好比是枪,RequestProcessorChain好比是弹夹

注解标记在方法参数上,用来建立controller方法的参数与请求参数的映射

@Target(ElementType.PARAMETER)
//请求的方法的参数名称
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParam {
    //方法参数名称
    String value() default "";
    //该参数是否是必须的
    boolean required() default true;
}
ControllerRequestProcessor的功能

◆针对特定请求,选择匹配的Controller方法进行处理
◆解析请求里的参数及其对应的值,并赋值给Controller方法的参娄
◆选择合适的Render ,为后续请求处理结果的渲染做准备

DispatcherServlet初始化的时候已经把bean容器初始化,并且加载了容器种所有的bean,进行了aop织入和依赖注入。controller处理器晚于DisparcherServlet加载,所以只需要调用容器的get方法得到容器实例即可。

/**
 * 依靠容器的能力,建立起请求路径、请求方法与Controller方法实例的映射
 */
public ControllerRequestProcessor(){
    this.beanContainer=BeanContainer.getInstance();
    Set<Class<?>> requestMappingSet = beanContainer.getClassesByAnnotation(RequestMapping.class);
    initPathControllerMethodMap(requestMappingSet);
}

为了实现简单,凡是方法被@RequestMapping标记的方法,只要有参数都需要用@RequestParam标记,方法参数未获取到该标签直接抛出错误。

ResultRender矩阵

例如

jsonRender

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.6</version>
</dependency>


/**
 * json渲染器
 */
public class JsonResultRender implements ResultRender {
    private Object jsonData;

    public JsonResultRender(Object jsonData) {
        this.jsonData=jsonData;
    }

    @Override
    public void render(RequestProcessorChain requestProcessorChain) throws Exception {
        //设置响应头
        requestProcessorChain.getResponse().setContentType("application/json");
        requestProcessorChain.getResponse().setCharacterEncoding("UTF-8");
        //响应流写入经过gson格式化之后得处理结果
        try(PrintWriter writer=requestProcessorChain.getResponse().getWriter()){
            Gson gson=new Gson();
            writer.write(gson.toJson(jsonData));
            writer.flush();
        }
    }
}

viewRender



/**
 * 存储处理完后的结果数据以及显示该数据的视图
 */
public class ModelAndView {
    @Getter
    private String view;//页面所在的路径
    @Getter
    private Map<String,Object> model=new HashMap<>();//页面的data数据

    public ModelAndView setView(String view){
        this.view=view;
        return this;
    }

    public ModelAndView addViewData(String attributeName,Object attributeValue){
        model.put(attributeName,attributeValue);
        return this;
    }
}

为什么setter返回类实例?

为了获取调用链进行调用

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐