首页 > 编程技术 > java

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

发布时间:2021-10-30 20:00 作者:JdbcUtils

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

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

1.配置阶段

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

2.初始化阶段

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

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、清晰的角色划分:

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框架的核心。

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

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

三大组件:

执行流程图:

在这里插入图片描述

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

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

原文出处:https://wangcw.blog.csdn.net/article/details/80645205

标签:[!--infotagslink--]

您可能感兴趣的文章: