使用springmvc运行流程分析,手写spring框架尝试

目录
  • 1.配置阶段
  • 2.初始化阶段
  • 3.运行阶段
  • springMVC介绍以及执行流程
    • 什么是SpringMVC?
    • SpringMVC的优势是什么?

该文章主要是分析Springmvc启动的流程(配置阶段、初始化阶段和运行阶段),可以让自己对spring框架有更深一层的理解。对框架比较感兴趣的朋友都可以了解阅读下,对于我所描述的内容有错误的还望能不吝指出。

对于springmvc中的整个流程我个人把他分为这几个阶段,包括个人手写的spring也是参照此按阶段实现:

1.配置阶段

根据web.xml ,先定义DispatcherServlet并且定义该sevlet传入的参数和路径。

2.初始化阶段

初始化阶段中又可以分为IOC、DI和MVC阶段:

  • (1)IOC:初始化配置文件和IOC容器,扫描配置的包下的类,通过反射机制将需要实例化的类放入IOC容器,既将带有spring注解的类进行实例化后存放到 IOC 容器中。IOC容器的实质就是一个集合;
  • (2)DI:DI阶段(其实就是依赖注入)。对需要赋值的实例属性进行赋值(一般较多都是处理带有注解的@Autowrized的属性)
  • (3)MVC:构造出HandlerMapping集合,主要作用就是用于存放对外公开的API和Method之间的关系,一个API一般会对应一个可执行的Method.

3.运行阶段

运行阶段中,当接受到一个url后,会到HandleMapping集合中,找到对应Method、通过反射机制去执行invoker,再返回结果给调用方。

这样就大体完成了springmvc整个运行阶段,所描述的都仅为个人观点,如果有误请在评论中指出。

其整体流程可以参照下图:

接下来就来尝试手写一个类似springmvc的框架了,这个手写的过程还是相当有成就感的!

1.创建一个空的JavaWeb工程,引入依赖,其实因为我们是要手写spring,所以基本不需要什么外部的依赖工具,只需要导入servlet-api即可,如下:

<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
 </dependency>

2.根据上述的流程描述,接下来就是对web.xml进行配置:

     <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>com.wangcw.cwframework.sevlet.CwDispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>application.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/*</url-pattern>
     </servlet-mapping>

对于配置中的CwDispatcherServlet其实就是个人自定义一个作用与spring中DispatcherServlet相同的Servlet,此处先创建一个空的CwDispatcherServlet,继承 javax.servlet.http.HttpServlet即可,具体实现后面会描述。

此处因为是手写spring的部分功能,所以配置也不用写太多,此处仅拿一个包扫描的配置(scanPackage),各位少侠可自行拓展。

CwDispatcherServlet中初始化的配置文件application.properties内容如下:

 scanPackage=com.wangcw

3.相信spring中又一部分注解都是大家比较熟悉的,接下来我们先从这几个注解着手吧。(此处就不指出各个注解的作用了,相信百度上已经很多了)

spring注解 自定义注解
@Controller @CwController
@Autowired @CwAutowired
@RequestMapping @CwRequestMapping
@RequestParam @CwRequestParam
@Service @CwService

然后实现下各个自定义的注解,直接贴代码:

/*
* 创建一个类似@Controller作用的注解类
*/

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwController {

    String value() default "";
}
/*
* 创建一个类似@Autowried作用的注解类
*/

import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwAutowried {

    String value() default "";
}
/*
* 创建一个类似@RequestMapping作用的注解类
*/

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwRequestMapping {

    String value() default "";
}
/*
* 创建一个类似@RequsetParam作用的注解类
*/

import java.lang.annotation.*;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwRequestParam {

    String value() default "";
}
/*
* 创建一个类似@Service作用的注解类
*/

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CwService {

    String value() default "";
}

4.创建一个简单的控制层和业务层交互 Demo,加上自定的注解,具体注解的功能,后面赘述。

Controller.java

@CwController
@CwRequestMapping("/demo")
public class Controller {  

    @CwAutowried
    private Service service;  

   @CwRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp,@CwRequestParam("name") String name) throws IOException {
            resp.getWriter().write(service.query(name));
    }  

    @CwRequestMapping("/add")
    public void add(HttpServletRequest req, HttpServletResponse resp, @CwRequestParam("a") Integer a, @CwRequestParam("b") Integer b) throws IOException {
        resp.getWriter().write("a+b="+(a+b));
    }
}  

Service.java

public interface Service {

         String query(String name);

}

ServiceImpl.java

@CwService
public class ServiceImpl implements Service{  

    @Override
    public String query(String name) {  

        return "I am "+name;
    }
}  

5.上面的controller层和service层已经把我们上述的自定注解都使用上去了,接下来我们开始手写spring的核心功能了,也就是实现CwDispatcherServlet.java这个HttpServlet的子类。

首先需要重写父类中的init方法,因为我们要在Init过程中实现出跟spring一样的效果。

理一理init()过程中都需要做哪些事情呢?整理了一下init()中主要需要以下几步操作

 @Override
    public void init(ServletConfig config)  {

        /* 1.加载配置文件*/
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        /* 2.扫描scanPackage配置的路径下所有相关的类*/
        doScanner(contextConfig.getProperty("scanPackage"));

        /* 3.初始化所有相关联的实例,放入IOC容器中*/
        doInstance();

        /*4.实现自动依赖注入 DI*/
        doAutowired();

        /*5.初始化HandlerMapping  */
        initHandlerMapping();

    }

第一步很简单,在类中定义一个Properties实例,用于存放Servlet初始化的配置文件。导入配置代码略过,IO常规读写即可。

private Properties contextConfig = new Properties();

第二步通过上面获取到的配置,取到需要扫描的包路径,然后在根据路径找到对应文件夹,做一个递归扫描即可。将扫描到的文件名去除后缀,保存到一个集合中,那么该集合就存放了包下所有类的类名。

String  scanFileDir = contextConfig.getProperty("scanPackage");
 /* 用于存放扫描到的类 */
    private List<String> classNames = new ArrayList<String>();
  /*扫描获取到对应的class名,便于后面反射使用*/
                String  className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);

第三步就是IOC阶段,简而言之就是对上面集合中所有的类进行遍历,并且创建一个IOC容器,将带有@CwController和@CwService的类置于容器内。

 /* 创建一个IOC容器 */
    private Map<String, Object> IOC = new HashMap<String, Object>();
 for (String classNme : classNames){
                if( 对加了 @CwController 注解的类进行初始化){
                       /* 对于初始化的类还需要放入IOC容器,
                          对于存入IOC的实例,key值是有一定规则的,默认类名首字母小写;*/
   /* toLowerFirstCase是自定义的一个工具方法,用于将传入的字符串首字母小写 */
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    IOC.put(beanName, clazz.newInstance());
                } else if (对加了 @CwService 注解的类进行初始化){
                    /* 对于存入IOC的实例,key值是有一定规则的,而Service层的规则相对上面更复杂一些,因为注解可以有自定义实例名,并且可能是接口实现类 */
      IOC.put(beanName, instance);

                } else {
                    //对于扫描到的没有注解的类,忽略初始化行为
                    continue;
                }
            }

第四步是DI操作,将IOC容器中需要赋值的实例属性进行赋值,即带有Autowired注解的实例属性。伪代码如下:

  /*遍历IOC中的所有实例*/
        for(Map.Entry<String, Object> entry : IOC.entrySet()){
           /* 使用getDeclaredFields暴力反射 */
           Field [] fields = entry.getValue().getClass().getDeclaredFields();
           for (Field field : fields){
               /*1.判断属性是否有加注解@CwAutowried.对于有注解的属性才需要赋值*/
    ....

               /*属性授权*/
               field.setAccessible(true);
      field.set(entry.getValue(), IOC.get(beanName));
           }
        }

第五步要处理Controller层的Method与请求url的匹配关系,让请求能准确的请求到对应的url。篇幅问题,此处还是上传伪代码。

 /* 创建HandlerMapping存放url,method的匹配关系
  其中类Handler是我自己定义的一个利用正则去匹配url和method,
  只要用户传入url,Handler就可以响应出其对应的method*/
  private List<Handler> handlerMapping = new ArrayList<Handler>();

  /* 遍历IOC容器 */
  for (Map.Entry<String, Object> entry : IOC.entrySet()){
      Class<?> clazz = entry.getValue().getClass();
      /* 只对带有CwController注解的类进行处理 */

     定义一个url,由带有CwController的实例类上的@CwRequestMapping注解的值和Method上@CwRequestMapping注解的值组成

        /* (1).判断类上是否有CwRequestMapping注解 ,进行拼接 url*/

        /* (2).遍历实例下每个Method,并且需要判断该方法是否有【 @CwRequestMapping 】注解,拼接url*/

       /* 最后将匹配关系以正则的形式,放到HandlerMapping集合中 */
       String regex = (url);
       Pattern pattern = Pattern.compile(regex);
       handlerMapping.add(new Handler(pattern,method));
      }

到这里就基本完成了springmvc的初始化阶段,之后的工作就是重写一下CwDispatcherServlet.java父类的doGet()/doPost()方法。根据request中的URI和参数来执行对应的Method,并且响应结果。

/* 利用反射执行其所匹配的方法 */
        handler.method.invoke(handler.controller, paramValues);

到此整个步骤就完成了,此时可以愉快的启动项目,并访问对应的url进行测试了。

根据上面Controller定义的方法可以知道其匹配的url为 : /demo/query 和 /demo/add,并且有使用@CwRequestParam注解定义了其各个参数的名称。

测试结果如下:

http://localhost:8080/spring/demo/query?name=James

http://localhost:8080/spring/demo/add?a=222222&b=444444

再来测试个url,是controller中没有声明出@CwRequestMapping注解的,看看结果。

http://localhost:8080/spring/demo/testNoUrl

springMVC介绍以及执行流程

什么是SpringMVC?

SpringMVC是一个实现了MVC设计模式的轻量级web层框架,使用起来简单方便。

SpringMVC的优势是什么?

1、清晰的角色划分:

  • 前端控制器(DispatcherServlet)
  • 请求到处理器映射(HandlerMapping)
  • 处理器适配器(HandlerAdapter)
  • 视图解析器(ViewResolver)
  • 处理器或页面控制器(Controller)
  • 验证器( Validator)
  • 命令对象(Command 请求参数绑定到的对象就叫命令对象)
  • 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。

2、分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。

3、由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。

4、和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。

5、可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。

6、可定制性, HandlerMapping、 ViewResolver 等能够非常简单的定制。

7、功能强大的数据验证、格式化、绑定机制。

8、利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。

9、本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。

10、强大的 JSP 标签库,使 JSP 编写更容易。

还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。

与Struts2的对比:

共同点:都是基于MVC设计模式的表现层框架,底层实现都离不开原始的Servlet,处理请求的机制都是一个核心控制器;

区别:Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter

Spring MVC 是基于方法设计的,而 Struts2 是基于类, Struts2 每次执行都会创建一个动作类。所以 Spring MVC 会稍微比 Struts2 快些。

相比来说,SpringMVC已经全面超越了Struts2。

执行流程:

DispatcherServlet: 是整个springmvc框架的核心。

前端控制器/核心控制器:所有的请求和响应都由此控制器进行分配。

前端控制器的所有工作都是基于组件完成的:

三大组件:

  • HandlerMapping: 它负责根据客户端的请求寻找对应的hadler,找到以后把寻找的handler返回给DispatcherServlet;
  • HandlerAdapter:它负责执行寻找到的Handler的方法,方法执行完后将返回值给HandlerAdapter, HandlerAdapter将返回值传给DispatcherServlet;
  • ViewResolver:它根据DispatcherServlet指定的返回结果寻找对应的页面,找到后将结果返回给DispatcherServlet。
  • DispatcherServlet负责最终的响应,默认是转发的操作。

执行流程图:

图画的有些灵魂,但是大致的流程就是这样,大家可以参考着理解下。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringMvc框架的简介与执行流程详解

    目录 一.SpringMvc框架简介 1.Mvc设计理念 2.SpringMvc简介 二.SpringMvc执行流程 1.流程图解 2.步骤描述 3.核心组件 三.整合Spring框架配置 1.spring-mvc配置 2.Web.xml配置 3.测试接口 4.常用注解说明 四.常见参数映射 1.普通映射 2.指定参数名 3.数组参数 4.Map参数 5.包装参数 6.Rest风格参数 五.源代码地址 一.SpringMvc框架简介 1.Mvc设计理念 M:代表模型Model 模型就是数据,应用

  • 手把手写Spring框架

    目录 初始化阶段 运行阶段 HandlerAdapter 形参列表:编译后就能拿到值 实参列表:要运行时才能拿到值 最后反射 总结: 这部分目标是MVC! 主要完成3个重要组件: HandlerMapping:保存URL映射关系 HandlerAdapter:动态参数适配器 ViewResolvers:视图转换器,模板引擎 SpringMVC核心组件执行流程: 相对应的,用以下几个类来实现上述的功能: 初始化阶段 在DispatcherServlet这个类的init方法中,将mvc部分替换为in

  • Springmvc的运行流程图文详解

    一.常见类及其作用 HandlerExecutionChain : Handler执行链对象, 包含了请求处理器对象 以及所有的拦截器对象. HandlerMapping : 定义了所有的请求与 所有的请求处理器之间的映射. HandlerAdaptor: 请求处理器适配器对象, 负责完成请求处理器对象的调用 . 方法的执行等- 二. 运行流程: 浏览器端发送请求到服务器端: 1.1 当前的请求在DispatcherServlet中不存在对应的映射 ① 是否配置mvc:default-servl

  • 使用springmvc运行流程分析,手写spring框架尝试

    目录 1.配置阶段 2.初始化阶段 3.运行阶段 springMVC介绍以及执行流程 什么是SpringMVC? SpringMVC的优势是什么? 该文章主要是分析Springmvc启动的流程(配置阶段.初始化阶段和运行阶段),可以让自己对spring框架有更深一层的理解.对框架比较感兴趣的朋友都可以了解阅读下,对于我所描述的内容有错误的还望能不吝指出. 对于springmvc中的整个流程我个人把他分为这几个阶段,包括个人手写的spring也是参照此按阶段实现: 1.配置阶段 根据web.xml

  • 如何从零开始手写Koa2框架

    01.介绍 Koa-- 基于 Node.js 平台的下一代 web 开发框架 Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小.更富有表现力.更健壮的基石. 与其对应的 Express 来比,Koa 更加小巧.精壮,本文将带大家从零开始实现 Koa 的源码,从根源上解决大家对 Koa 的困惑 本文 Koa 版本为 2.7.0, 版本不一样源码可能会有变动 02.源码目录介绍 Koa 源码目录截图 通过源码目录可以

  • 如何手写一个Spring Boot Starter

    何为 Starter ? 想必大家都使用过 SpringBoot,在 SpringBoot 项目中,使用最多的无非就是各种各样的 Starter 了.那何为 Starter 呢?你可以理解为一个可拔插式的插件(组件).或者理解为场景启动器. 通过 Starter,能够简化以前繁杂的配置,无需过多的配置和依赖,它会帮你合并依赖,并且将其统一集成到一个 Starter 中,我们只需在 Maven 或 Gradle 中引入 Starter 依赖即可.SpringBoot 会自动扫描需要加载的信息并启动

  • Java面试题冲刺第七天--Spring框架1

    目录 面试题1:能简单说一下你对Spring框架的理解么? 追问1:常见的Core组件有哪些? 面试题2:谈谈对Spring IOC的理解 追问1:Spring中的bean的作用域有哪些? 追问2:Spring中的bean生命周期? 追问3: Spring 中的 bean 是线程安全的吗? 面试题3:说一下 SpringMVC 运行流程? 追问1:能介绍一下SpringMVC各组件的作用么? 总结 面试题1:能简单说一下你对Spring框架的理解么? 我们一般说的Spring框架就是Spring

  • Spring Boot应用通过Docker发布部署的流程分析

    目录 手动部署 1.idea创建spring boot项目 2.项目打成 Jar 包 3.构建 docker image 4.查看并运行镜像 插件部署 运行推送命令 将Spring Boot项目部署到docker中有两种方法,手动部署和插件部署 手动部署 1.idea创建spring boot项目 pom.xml文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http:/

  • SpringBoot工程中Spring Security应用实践记录流程分析

    目录 SpringSecurity 应用 简介 认证授权分析 SpringSecurity 架构设计 快速入门实践 创建项目 添加项目依赖 启动服务访问测试 自定义认证逻辑 认证流程分析 定义security配置类 定义数据访问层对象 定义UserDetailService接口实现类 自定义登陆页面 启动服务进行访问测试 授权逻辑设计及实现 修改授权配置类 定义资源访问对象 启动服务实现访问测试 总结(Summary) SpringSecurity 应用 简介 Spring Security是一

  • Mybatis源码分析之存储过程调用和运行流程

    这一篇我们学习一下Mybatis调用存储过程的使用和运行流程.首先我们先创建一个简单的存储过程 DELIMITER $ CREATE PROCEDURE mybatis.ges_user_count(IN age INT, OUT user_count INT) BEGIN SELECT COUNT(*) FROM users WHERE users.age=age INTO user_count; END $ 这个存储过程的含义其实比较简单的,就是输入age,然后执行select count(

  • 详解Spring IOC 容器启动流程分析

    使用 Spring 时,XML 和注解是使用得最多的两种配置方式,虽然是两种完全不同的配置方式,但对于 IOC 容器来说,两种方式的不同主要是在 BeanDefinition 的解析上.而对于核心的容器启动流程,仍然是一致的. AbstractApplicationContext 的 refresh 方法实现了 IOC 容器启动的主要逻辑,启动流程中的关键步骤在源码中也可以对应到独立的方法.接下来以  AbstractApplicationContext 的实现类  ClassPathXmlAp

  • Visual Studio Code 配置C、C++环境/编译并运行的流程分析

    总体流程: 下载安装vscode 安装cpptools插件 安装编译.调试环境 修改vscode调试配置文件 下载安装vscode https://code.visualstudio.com/Download 安装cpptools插件 打开vscode,按ctrl+p打开快速命令框,输入以下命令后等待 ext install cpptools vscode在短暂的联网查找后会列出插件列表,如图: 点击箭头所指处的按钮安装插件,安装过程可能会有些慢耐心等待 安装完成后vscode会提示你重启vsc

随机推荐