手写Spring IOC、MVC

提示:以下重点为部分流程介绍!!! 

前言

提示:maven导入jar包和tomcat插件


提示:以下是本篇文章正文内容,下面案例可供参考


一、web.xml

二、执行步骤

tomcat解析web.xml后进入自定义mvc核心类(故事的开始)


 核心类实现HttpServlet方法:public void init(ServletConfig config) throws ServletException {},进行初始化配置

代码如下(示例):

 public void init(ServletConfig config) throws ServletException {
        System.out.println("tomcat启动,进入HDispatcherServlet的int方法,初始化成功...");
//第一步:HApplicationContext为自定义上下文配置类
     context = new HApplicationContext(BootstrapApplication.class);       
//第五步:加载springmvc的九大组件
     ​​​​​​initStrategies(context);
}
HApplicationContext类:

public HApplicationContext(Class<?> applicationClass) {
    HBeanDefinitionReader hBeanDefinitionReader = new HBeanDefinitionReader(applicationClass);

//第二步:拿到第一步中实例化好的beanDefinitions,将beanName与BeanDefinition的关系重新维护到一个Map集合中)
    List<HBeanDefinition> beanDefinitions = hBeanDefinitionReader.getBeanDefinitions();
    //把beanName与BeanDefinition的关系维护起来(本代码中不要这步也可以实现后续逻 辑,但Spring源码中有这一步,顾采用相同和源码方式)
    doBeanNameDefinition(beanDefinitions);

//第三步:添加进IoC容器
    doIoC();

//第四步:依赖注入
    populateBean();
}

//第五步具体方法
private void initStrategies(HApplicationContext context) {

//第六步: 把url与method之间的关系封装成对象,并且保存到list集合中
        initHandlerMappings(context);

//第七步:根据一个handlerMapping寻找具体的handlerAdapter进行处理
        initHandlerAdapters(context);
//第八步: 初始化视图解析器:html   freemark   xxx等
        initViewResolvers(context);
    }

第一步后续详解====>>>执行流程

    1.构建一个HBeanDefinitionReader类,作用:读取配置文件或者配置类(此处为读取配置类)

    2.HBeanDefinitionReader类,执行doConfig (配置类) 方法,判断BootstrapApplication.class是否为一个配置类(利用反射判断类上是否有特定注解),否,则故事结束。

    3..判断结果为配置类,拿到配置类下的@HComponentScand的值,执行

replace(".", "/")  拿到需要扫描的路径,执行  例中为:com/mky 
                                               
.getFile()  获取文件获取文件后,对该文件下的子文件夹进行递归处理,判断最后最后文件是否以.class结尾。是,执行

.replace(".class","")  将所有以.class结尾的文件路径保存在 classNames的List集合中。

   

4.执行loadBeanDefinitions()方法,用一个循环套反射,判断classNames的List集合中每一    个路径对应的类上,是否有需要springIoC管理的注解配置(如@Controller,@Service 等), 如有,则表示要交给Spring管理)

   5.定义一个HBeanDefinition类,储存对应的beanName和bean对应的实例化对象类。

      判断4中需要管理的类,注解上是否有显示的定义value,如有则beanName等于该值,如没有,利用反射获取4中需要管理的类的类名(可定义一个工具类,将其改首字母小写)beanName为小写后的类名。(serviceImpl实现的接口别忘记也要处理,后面依赖注入时,要使用)。

      bean的实例化对象可直接通过反射直接获取。将beanName和bean对应的实例化对象,赋值给一个HBeanDefinition对象,将每个HBeanDefinition对象用一个List集合(List<HBeanDefinition> beanDefinitions)管理起来。

启动类代码码如下(示例):

@HConfiguration  // 表示我当前就是一个配置类
@HComponentScan(basePackage = "com.mky")
public class BootstrapApplication {

    public static void main(String[] args) {
        // 类比springboot 可将 apache tomcat启动的源码放到当前位置
    }
}

第二步后续详解====>>>执行流程

      定义一个Map<String, HBeanDefinition> beanDefinitionMap集合,遍历参数beanDefinitions,  利用 beanDefinitionMap.put(beanDefinition.getBeanName(),beanDefinition);将二者关系管理      到beanDefinitionMap集合中。

第三步后续详解====>>>执行流程

      1.遍历第二步中实例化好的beanDefinitionMap集合,通过方法 .getValue()拿到 hBeanDefinition 对象,通过方法(默认返回false,表示非懒加载).isLazyInit() 判断是否懒 加 载。(不是懒加载,就表示要随着Spring启动而实例化

      2.自定义IoC缓存容器

// IoC容器[缓存]: 原生 key:beanName    Object:instance  clazz.newInstance
private Map<String, Object> beanOriginCache = new ConcurrentHashMap<>();

// IoC容器[缓存]:   key   value:instance[wrapper]   这里的Object就是包装后的bean 
二级缓存
private Map<String, Object> beanWrapperCache = new ConcurrentHashMap<>();

// IoC容器[缓存]:   key   value:instance[proxy]   这里的Object就是通过动态代理创建出来的动态代理对象
private Map<String, Object> beanProxyCache = new ConcurrentHashMap<>();

     

       3.获取beanDefinitionMap的key,判断IoC容器:beanOriginCache中是否已经存在。

      4.不存在,beanOriginCache.put(beanName,instance);参数均可通过beanDefinitionMap加反    射获取。instance为实例化的对象。到此原生的bean注入成功。

     

      5.包装一下bean。创建一个HBeanWrapper 类,用于封装4中得到的instance(实例化对象)和 实例化对象对应的类。

     

     6.将5中封装好的实例放到oC缓存中 beanWrapperCache.put(beanName,beanWrapper);

第四步后续详解====>>>执行流程( 至此IoC完成)

     1.通过方法 beanOriginCache.isEmpty() 判断IoC容器是否有值。

     2.遍历 beanOriginCache容器,判断每一个有IoC管理的类下字段上(遍历一个类下所有字段)  是否有IoC注入的相关注解(如@Autowired)

     3.如果有,判断该注解的value是否显示配置,有则:beanName为该值,没有则默认beanName 为字段类型的首字母小写值。

     4.执行方法:字段.setAccessible(true); 反之私有情况,注入失败,

     5.执行方法进行注入:字段.set(字段所在的类, 要注入的实例化对象)

(开始MVC了)!!!

核心类实现HttpServlet doGet和doPost方法:

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    try {
        doDispatch(req, resp);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

第五步后续详解====>>>执行流程(开始MVC)

   科普:springmvc九大组件为:

HandlerMapping、HandlerAdapter、handlerExceptionResolver、ViewResolver、RequestToViewNameTranslator、 ThemResolver、FlasMapManager。(此处我们仅选择部分重构)

       1.执行方法:private void initStrategies(HApplicationContext context){};进行九大组件配置;

第六步后续详解====>>>执行流程

        1.创建一个HHandlerMapping对象,作用:将请求url (服务器默认设置以外部分,如:user/test), url 对应执行的方法,以及方法所在的类的实例(即Controller类的实例)封装在HHandlerMapping对象中。

        2.通过方法:context.getBeanDefinitionCount(),判断第二步中 beanDefinitionMap集合是否  为空,如为空,则说明IoC容器为空,故事结束。

        3.如不为空,则通过方法:context.getBeanNames()获取 beanDefinitionMap集合中所有key    值的数组(beanName)

         4.遍历该数组,通过方法:context.getBean(beanName)获取 beanName对应的实例化对象    ( 方法中传入的beanName,与方法三中beanDefinitionMap (IoC)缓存容器中beanName匹配,即可拿到beanDefinitionMap中缓存的对应实例化对象)

         5.通过方法:实例化对象.getClass() 拿到对应的类,判断类上是否有@RequestMapping注解,有,拿到value值;在遍历类中所有方法,判断方法上是否有@RequestMapping注解,有,拿到value值,和类上的value值拼接。

        6.将5中得到的拼接后的值、对应方法、4中获取到的实例化对象封装到 hHandlerMapping 对象中,将遍历得到的每一个该对象,添加到 List<HHandlerMapping> handlerMappings 集合中。

第七步后续详解====>>>执行流程

        1.创建一个HHandlerAdapter对象

        2.遍历第七步中的,handlerMappings集合,执行方法handlerAdpaters.put(handlerMapping,new HHandlerAdapter()) 将handlerMappings与一个             对应的具体 hHandlerAdapte对象,添加到 Map<HHandlerMapping, HHandlerAdapter> handlerAdpaters集合中。

 第八步后续详解====>>>执行流程

       1.解析配置文件或者配置类,拿到视图在那个文件夹下。执行 this.getClass().getClassLoader().getResource(拿到的文件夹路径).getFile(),获取文件              路径,执行  viewResolver = new HViewResolver(file)   创建一个视图解析器viewResolver。

       

前端开始发送请求,执行doPost中的 doDispatch(req, resp)方法!!

 第九步(处理请求)后续详解====>>>执行流程

      1.执行 req.getRequestURI().replaceAll("/+", "/") 拿到请求中,项目路径后面的部分url.

      2.写一个方法,作用:遍历第六步中初始化好的handlerMappings,判断其中是否有相同的url,  如没有,返回null, 有,则返回对应的handlerMappings 对象。

      3.判断返回结果,是否为空。如为空,将视图名:404.html 传给步骤八中的视图解析器;

      4.视图解析器,中构建一个方法,判断传入的视图名是否以.html结尾(此处不做否情况详解      ,均默认跳转到.html,如果不是,尾部默认拼接上.html);拿到步骤八中 viewResolver = new HViewResolver(file),传递的视图所在文件夹 file,执行file.getPath()+"/"+viewName).replaceAll("/+","/");拿到目标视图的路径.

      5.构建一个HView,表示视图,创建一个hView对象,将4中获取的路径封装到hView中。

      6.执行 hView.render(null, req, resp) 方法:对视图经行渲染(渲染代码在文章末尾给出,此处  不做过多讲解),返回给浏览器。

     回到3中另一种情况!!!

      1.如果返回结果非空。通过方法:handlerAdapter=getHandlerAdapter(handlerMapping),遍历步骤七中handlerAdpaters集合,对应的handlerAdapter实例对象

      2. handlerAdapter实例对象执行.handle(req,resp,handlerMapping)方法(该方法的主要目的为  利用反射执行url对应目标方法,以及拿到方法返回类型)。

      3.handle(req,resp,handlerMapping)方法中,通过handlerMapping拿到url对应需要执行方法

 ,方法所在类,以及所需参数(对参数赋值需要小心参数对应位置信息,此处不详述),执行 Object returnValue = method.invoke(obj, params),执行方法并拿到对应返回值。

      4.构建一个类HModelAndView 作用封装返回的页面名称和携带数据。

      5.判断3中的返回类型是否为HModelAndView类型,如过不是返回null (此处没做处理,可自己  丰富【可参考实现@ResponseBody】)

      6.返回HModelAndView结果,执行方法:modelAndView.getViewName()拿到对应视图名,执行方法:Map<String, ?> model = modelAndView.getModel();拿到所要携带的参数,传给步骤八中的视图解析器;

     同上逻辑!!!

      7.视图解析器,中构建一个方法,判断传入的视图名是否以.html结尾(此处不做否情况详解       ,均默认跳转到.html,如果不是,尾部默认拼接上.html);拿到步骤八中 viewResolver = new HViewResolver(file),传递的视图所在文件夹 file,执行  file.getPath()+"/"+viewName).replaceAll("/+","/");拿到目标视图的路径.

      8.构建一个HView,表示视图,创建一个view对象,将4中获取的路径封装到hView中。

      9.执行view.render(model,req,resp)对视图经行渲染,返回给浏览器。

视图渲染代码

本例中为将视图中对应 !{}【参考;${}】部分替换成实际的值。

// TODO: 把结果输出到浏览器+把model中的key,value替换.html中的占位符
    /*
        <b>name: !{name}</b> <br/>
        <b>age: !{age}</b> <br/>
        <b>hobby: !{hobby}</b>
     */

public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) {
        try {
            StringBuffer sb = new StringBuffer();
            RandomAccessFile ra = new RandomAccessFile(this.file, "r");
            String line = null;
            while (null != (line = ra.readLine())) {
                line = new String(line.getBytes("iso-8859-1"), "utf-8");
                Pattern pattern = Pattern.compile("!\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
                Matcher matcher = pattern.matcher(line);
                while (matcher.find()) {
                    //  !{name}
                    String paramName = matcher.group();
                    paramName = paramName.replaceAll("!\\{|\\}", "");
                    Object paramValue = model.get(paramName);
                    if (null == paramValue) {
                        continue;
                    }
                    line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
                    matcher = pattern.matcher(line);
                }
                sb.append(line);

            }
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(sb.toString());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    private String makeStringForRegExp(String str) {
        return str.replace("\\", "\\\\").replace("*", "\\*")
                .replace("+", "\\+").replace("|", "\\|")
                .replace("{", "\\{").replace("}", "\\}")
                .replace("(", "\\(").replace(")", "\\)")
                .replace("^", "\\^").replace("$", "\\$")
                .replace("[", "\\[").replace("]", "\\]")
                .replace("?", "\\?").replace(",", "\\,")
                .replace(".", "\\.").replace("&", "\\&");
    }

总结

        AoP部分正在开始码.....。本文仅为简单实现流程,实际Spring的使用,还有很多情况和细节。上面讲述的不太好,仅为个人的思路,可能看着很空洞,难懂。如果大家感兴趣可去研究spring源码。

       当然,对该手写感兴趣的朋友,可加QQ:2722173496  免费提供该手写和AOP手写源码供大家参考!!!  以上如有错误,也欢迎大家在评论指出。 编程的学习是一个漫长的过程,建议大家多面向各类API、百度编程,切勿照搬讲解。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值