美文网首页撸一个mvc框架
动手撸一个 mvc 框架1

动手撸一个 mvc 框架1

作者: 想54256 | 来源:发表于2020-04-22 16:59 被阅读0次

    title: 动手撸一个 mvc 框架1
    date: 2020/04/21 17:06


    本节内容 & 思考题

    1. 增加与 web 应用相关的 WebApplicaitonContext
    2. 搭建出 SpringMVC 的 DispatcherServlet 的结构

    新增 WebApplicaitonContext

    /**
     * 部分配置在 Spring 中采用 ConfigurableWebApplicationContext 接口封装
     */
    public interface WebApplicationContext extends ApplicationContext {
    
        /**
         * 成功启动时将 Root WebApplicationContext 绑定到 ServletContext 中的属性。
         */
        String WEB_APPLICATION_CONTEXT_ATTRIBUTE_NAME = WebApplicationContext.class.getName() + ".ROOT";
    
        void setServletContext(ServletContext servletContext);
    
        ServletContext getServletContext();
    
        void setServletConfig(ServletConfig servletConfig);
    
        ServletConfig getServletConfig();
    }
    
    
    public class JsonWebApplicationContext extends FileSystemJsonApplicationContext implements WebApplicationContext {
    
        private ServletContext servletContext;
    
        private ServletConfig servletConfig;
    
        // 在 SpringMVC 中 spring-mvc.xml 文件的位置是在 setServletContext() 中,从 setServletContext() 中获取到的,然后调用 refresh 方法
        // 此处为了迎合我们的写法(把加载 bd 放到了构造中),所以必须要这样写
        // 注:在 Spring 中加载 bd 是 bf 的方法,new 出 bd 使用者(ApplicationContext)自己调用的。
        public JsonWebApplicationContext(String[] locations) {
            super(locations);
        }
    
        public JsonWebApplicationContext(String[] locations, ApplicationContext parent) {
            super(locations, parent);
        }
    
        // -----> WebApplicationContext 中的方法
    
    
        @Override
        public ServletContext getServletContext() {
            return servletContext;
        }
    
        @Override
        public void setServletContext(ServletContext servletContext) {
            this.servletContext = servletContext;
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return servletConfig;
        }
    
        @Override
        public void setServletConfig(ServletConfig servletConfig) {
            this.servletConfig = servletConfig;
        }
    
        @Override
        protected Environment createEnvironment() {
            return new StandardServletEnvironment();
        }
    }
    
    其中 StandardServletEnvironment 你应该还记的吧。
    
    public class StandardServletEnvironment extends StandardEnvironment implements WebEnvironment {
    
        /**
         * 从 servletContext 和 servletConfig 取出属性,初始化 env
         */
        @Override
        public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
            // 我不会写
            // StandardServletEnvironment 中 Spring 给他添加了这些东西 servletContextInitParams、servletConfigInitParams
    
        }
    }
    

    增加 DispatcherServlet

    1、HttpServletBean

    它的作用是从 ServletContext 中读取出我们需要的参数

    public class HttpServletBean extends HttpServlet {
    
        /**
         * 当第一次请求到来的时候会调用
         */
        @Override
        public void init() throws ServletException {
    
            // 从 ServletContext 取出必须的参数设置到当前对象,因为我们没有,就不写了
    
            // 调用子类的初始化方法
            this.initServletBean();
        }
    
        /**
         * 子类可以重写此方法以执行自定义初始化。在调用此方法之前,将设置此servlet的所有bean属性。此默认实现不执行任何操作。
         */
        protected void initServletBean() {
    
        }
    }
    

    2、子类 FrameworkServlet

    FrameworkServlet 重写了 initServletBean 方法,将 web 容器进行了初始化,并注册了事件,等待容器刷新的时候调用:

    public abstract class FrameworkServlet extends HttpServletBean {
    
        /**
         * WebApplicationContext for this servlet
         */
        private WebApplicationContext wac;
    
        // 是否已经触发刷新事件
        private boolean refreshEventReceived;
    
        // 放入 ServletContext 中的属性的前缀
        public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
    
        /**
         * 子类可以重写此方法以执行自定义初始化。在调用此方法之前,将设置此servlet的所有bean属性。此默认实现不执行任何操作。
         */
        @Override
        protected void initServletBean() {
            this.createWebApplicationContext();
            this.initFrameworkServlet();
        }
    
        private void createWebApplicationContext() {
    
            // 获取 servlet 上下文
            ServletContext servletContext = super.getServletContext();
    
            // 寻找是否具有父容器(Listener 中加载的)
            WebApplicationContext rootContext = (WebApplicationContext) servletContext.getAttribute(WebApplicationContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE_NAME);
    
            // 如果当前的 web 容器为空,则检查 ServletContext 中有没有
            if (wac == null) {
                wac = (WebApplicationContext) servletContext.getAttribute(this.getServletContextAttributeName());
            }
    
            // 如果当前的 web 容器为空,则创建一个
            if (wac == null) {
                // SpringMVC 配置文件地址
                String mvcConfigLocation = super.getServletConfig().getInitParameter("mvcConfigLocation");
                String realPath = servletContext.getRealPath(mvcConfigLocation);
                String[] locations = new String[]{realPath};
                wac = new JsonWebApplicationContext(locations, rootContext);
                this.configureAndRefreshWebApplicationContext();
            }
    
            // 如果还没刷新 DispatchServlet 则进行刷新
            if (!refreshEventReceived) {
                onRefresh(wac);
            }
    
            // 将当前 web 容器放进 ServletContext 中
            String attrName = this.getServletContextAttributeName();
            servletContext.setAttribute(attrName, wac);
        }
    
        public String getServletContextAttributeName() {
            return SERVLET_CONTEXT_PREFIX + super.getServletName();
        }
    
        private void configureAndRefreshWebApplicationContext() {
            wac.setServletContext(super.getServletContext());
            wac.setServletConfig(super.getServletConfig());
    
            // SpringMVC 中还使用 SourceFilteringListener 包装了一下,为了方便这里就不包装了
            wac.addApplicationListener(new ContextRefreshListener());
    
            // 获取到 applicationContext 的环境,将 ServletContext 中的参数放进 env 中
            Environment env = wac.getEnvironment();
            if (env instanceof StandardServletEnvironment) {
                ((StandardServletEnvironment) env).initPropertySources(super.getServletContext(), super.getServletConfig());
            }
        }
    
        // 空实现,在 MVC 中也没有人重写它
        protected void initFrameworkServlet() {
        }
    
        private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    
            @Override
            public void onApplicationEvent(ContextRefreshedEvent event) {
                FrameworkServlet.this.onApplicationEvent(event);
            }
    
            /**
             * 获取事件类型
             */
            @Override
            public Class<ContextRefreshedEvent> getEventType() {
                return ContextRefreshedEvent.class;
            }
        }
    
        private void onApplicationEvent(ContextRefreshedEvent event) {
            this.refreshEventReceived = true;
            this.onRefresh(event.getApplicationContext());
        }
    
        /**
         * DispatchServlet 重写了这个方法
         */
        protected void onRefresh(ApplicationContext applicationContext) {
            // For subclasses: do nothing by default.
        }
    }
    

    3、增加 DispatcherServlet

    当容器刷新的时候会触发事件调用 onRefresh 方法,onRefresh 方法的作用就是初始化各种处理器。

    这些处理器就是 SpringMVC 的核心,我们接下来就要讲他们。

    public class DispatcherServlet extends FrameworkServlet {
    
        /**
         * This implementation calls {@link #initStrategies}.
         */
        @Override
        protected void onRefresh(ApplicationContext context) {
            initStrategies(context);
        }
    
        /**
         * 初始化此servlet使用的策略对象。
         */
        protected void initStrategies(ApplicationContext context) {
    //        initMultipartResolver(context);
    //        initLocaleResolver(context);
    //        initThemeResolver(context);
    //        initHandlerMappings(context);
    //        initHandlerAdapters(context);
    //        initHandlerExceptionResolvers(context);
    //        initRequestToViewNameTranslator(context);
    //        initViewResolvers(context);
    //        initFlashMapManager(context);
        }
    
    }
    

    Spring 4.0

    DispatcherServlet 的 init 方法

    image image image

    我们点进去 tag1

    image image

    注:这个 nameSpace 和 spring-mv.xml 这个配置文件有关。

    image

    最终调用到了 DispatcherServlet 的 onRefresh 方法:

    image

    我们看下是在什么时候触发的事件:

    wac.refesh();

    image

    点进去

    image

    我们回到 tag1 继续走

    image

    相关文章

      网友评论

        本文标题:动手撸一个 mvc 框架1

        本文链接:https://www.haomeiwen.com/subject/zwkwihtx.html