精华内容
下载资源
问答
  • 项目初始化——HTML模板

    千次阅读 2018-03-26 15:40:50
    我们在项目初始化的时候,都会有一个html模板,里面会做各种兼容和声明等工作。PC端如果需要适配IE8需要加入很多垫片,而且还要做好双核浏览器的优先选择配置等;M端更是需要做到不同分辨率屏幕的适配,另外还有...

    我们在项目初始化的时候,都会有一个html的模板,里面会做各种兼容和声明等工作。PC端如果需要适配IE8需要加入很多垫片,而且还要做好双核浏览器的优先选择配置等;M端更是需要做到不同分辨率屏幕的适配,另外还有300ms延迟问题。这里送上笔者在项目当中整理的两个模板,已经经过了一定项目的考验,供大家参考。

    另外,本文中涉及的M端适配并非rem,而是修改viewport的显示比例,这个方案相较rem来说起码有2个优点:一是省字符,二是不用计算,设计稿是多少就是多少。不过在实践当中,UC浏览器貌似不认user-scalabe=0,还是会被双击放大,不过笔者认为还是可以接受的。如果还遇到了其他bug,请反馈,谢谢。

    PC模板

    <!DOCTYPE html>
    <html lang="zh">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
        <meta name="renderer" content="webkit" />
        <title>Document</title>
    <!--[if lt IE 9]>
        <script src="//cdn.staticfile.org/html5shiv/r29/html5.min.js"></script>
        <script src="//cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
        <script src="//cdn.staticfile.org/es5-shim/4.5.9/es5-shim.min.js"></script>
        <script src="//cdn.staticfile.org/es5-shim/4.5.9/es5-sham.min.js"></script>
    <![endif]-->
        <link rel="stylesheet" href="//cdn.staticfile.org/normalize/6.0.0/normalize.min.css">
    </head>
    <body>
    </body>
    </html>

    M模板

    <!DOCTYPE html>
    <html lang="zh">
    <head>
        <meta charset="UTF-8" />
        <meta name="format-detection" content="telephone=no" />
        <title>Document</title>
        <script>
        (function() {
            var w = 750; // 设计稿尺寸
            document.write('<meta name="viewport" content="width=' + w + ', initial-scale=' + window.screen.width / w + ', user-scalable=0" />');
        })()
        </script>
        <link rel="stylesheet" href="//cdn.staticfile.org/normalize/6.0.0/normalize.min.css">
    </head>
    <body>
        <script src="//cdn.staticfile.org/fastclick/1.0.6/fastclick.min.js"></script>
        <script>
        document.addEventListener('DOMContentLoaded', function() {
            Origami.fastclick(document.body);
        }, false);
        </script>
    </body>
    </html>

    公共部分说明

    1、<!DOCTYPE html>
    在 HTML 4.01 中,<!DOCTYPE> 声明引用 DTD,因为 HTML 4.01 基于 SGML。DTD 规定了标记语言的规则,这样浏览器才能正确地呈现内容。HTML5 不基于 SGML,所以不需要引用 DTD。
    请始终向 HTML 文档添加 <!DOCTYPE> 声明,这样浏览器才能获知文档类型。它不是 HTML 标签,是指示 web 浏览器关于页面使用哪个 HTML 版本进行编写的指令;必须是 HTML 文档的第一行,位于 <html> 标签之前,没有结束标签,对大小写不敏感;
    参考文献: http://www.w3school.com.cn/tags/tag_doctype.asp 

    2、<html lang="zh">
    HTML 的 lang 属性可用于网页或部分网页的语言。这对搜索引擎和浏览器是有帮助的。根据 W3C 推荐标准,您应该通过 <html> 标签中的 lang 属性对每张页面中的主要语言进行声明。
    参考文献: http://www.w3school.com.cn/tags/html_ref_language_codes.asp

    3、<meta charset="UTF-8" />
    当你的 html 文件是以 UTF-8 编码保存的,而且里面有中文,你试试加与不加在 Chrome 的效果你就知道有没有区别了

    4、<meta name="format-detection" content="telephone=no,email=no,adress=no" />

    告诉浏览器是否识别特定格式的文本。根据项目需求修改值。


    PC部分说明

    1、<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    控制IE渲染内核的选择。chrome=1针对装了chrome frame插件的IE浏览器起作用,以防万一写上。
    参考文献: https://msdn.microsoft.com/en-us/library/ff955275(v=vs.85).aspx

    2、<meta name="renderer" content="webkit|ie-comp|ie-stand" />
    控制双核浏览器渲染引擎,content的取值为webkit、ie-comp、ie-stand之一,区分大小写,分别代表用webkit内核,IE兼容内核,IE标准内核。
    参考文献: http://se.360.cn/v6/help/meta.html

    3、<!--[if lt IE 9]> //code here
    兼容IE9以下(不含)的写法,只有IE认。(例子中js为IE9-的垫片)
    参考文献: http://www.weste.net/2013/8-9/93104.html

    M部分说明

    1、js输出viewport缩放屏幕,适配不同大小设备
    (function() {
        var w = 750; //设计稿设备宽度
        document.write('<meta name="viewport" content="width=' + w + ', initial-scale=' + window.screen.width / w + ', user-scalable=0" />');
    })()
    加入以上代码,需修改变量值为设计稿的设备宽度,样式全部按照设计稿的数值和单位写就可以。详见:移动端常用布局方法
    参考文献: http://www.cnblogs.com/2050/p/3877280.html

    2、fastclick解决iphone等手机的300ms延迟问题
    <script src="//st01.chrstatic.com/themes/chr-cdn/fastclick/v1.0.6/fastclick.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            Origami.fastclick(document.body);
        }, false);
    </script>
    先加载fastclick.min.js,之后将需要去除300ms的dom挂上,压缩版写Origami.fastclick(document.body);,非压缩版写FastClick.attach(document.body);即可
    参考文献: https://github.com/ftlabs/fastclick/


    展开全文
  • 动态添加dom元素的场景非常常见,如点击某页面上修改用户资料的按钮,发送ajax请求去查询用户资料,然后通过模板引擎将事先写在页面里的静态模板编译成HTML字符串,最后将HTML字符串append到页面显示出来,一般情况...
  • cms软件系统后台管理登录页面模板html下载cms软件系统后台管理登录页面模板html下载
  • .ctrl+shift+p 输入settings.json 在这个文件输入 { "emmet.variables": { "lang": "zh" } }

    .ctrl+shift+p 输入settings.json

    在这个文件输入

    {
        "emmet.variables": {
            "lang": "zh"
        }
    }

     

    展开全文
  • Asp.net动态页面静态初始NVelocity模板引擎 静态页面是网页的代码都在页面中,不需要执行asp,php,jsp,.net等程序生成客户端网页代码的网页,静态页面网址中一般不含“?”、“=”、“&”等特殊符号。静态页面不...

    Asp.net动态页面静态化之初始NVelocity模板引擎

    静态页面是网页的代码都在页面中,不需要执行asp,php,jsp,.net等程序生成客户端网页代码的网页,静态页面网址中一般不含“?”、“=”、“&”等特殊符号。静态页面不能自主管理发布更新的页面,如果想更新网页内容,要通过FTP软件把文件DOWN下来用网页制作软件修改(通过fso等技术例外) 常见的静态页面举例:.html扩展名的、.htm扩展名的。 注意:静态页面并非网站上没有动画的就是静态页面。

    现在我们就通过NVelocity模板引擎简单的实现一下登陆过程的动态页面静态化

    首先我们需要有一个html静态页面

    <html>
    <head><title></title>
    </head>
    <body>
        <strong><font>登录</font></strong><form action='Handler1.ashx'>
        <input type='text' name='username' value="$username" />
        <input type='password' name='password'value='$password' />
        <input type='submit' value='登录' /></form>
        <p> $msg</p>
    </body>
    </html>

    我们可以发现 页面中的Value值都是以$开头的定义符 这是为了方便接下来的传值

    下面我们新建一个c#一般处理程序 编写代码

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using NVelocity.Runtime;
    using NVelocity;
    using NVelocity.App;
    
    namespace czbk
    {
        /// <summary>
        /// Handler1 的摘要说明
        /// </summary>
        public class Handler1 : IHttpHandler
        {
    
            public void ProcessRequest(HttpContext context)
            {
                context.Response.ContentType = "text/html";
                string username = context.Request["username"];
                string password = context.Request["password"];
                if (string.IsNullOrEmpty(username) && string.IsNullOrEmpty(password))
                {
                    VelocityEngine vltEngine = new VelocityEngine();//初始化VelocityEngine引擎
                    vltEngine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file");//模板文件位于文件系统中
                    vltEngine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
                        System.Web.Hosting.HostingEnvironment.MapPath("~/"));//模板文件所在的文件夹 MapPath服务器端路径 映射为物理路径
                                                                             //'~/'位于项目根文件下
                    vltEngine.Init();//实例对象初始化
                    VelocityContext vltContext = new VelocityContext();//创建一个上下文对象
                    // vltContext.Put("data", data);//设置参数,在模板中可以通过$data来引用
                    vltContext.Put("username", "");
                    vltContext.Put("password", "");
                    vltContext.Put("msg", "");
                    Template vltTemplate = vltEngine.GetTemplate("login.html");
                    System.IO.StringWriter vltWriter = new System.IO.StringWriter();
                    vltTemplate.Merge(vltContext, vltWriter);
                    string html = vltWriter.GetStringBuilder().ToString();
                    context.Response.Write(html);
                }
                else
                {
                    if (username == "admin" && password == "123")
                    {
                        VelocityEngine vltEngine = new VelocityEngine();//初始化VelocityEngine引擎
                        vltEngine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file");//模板文件位于文件系统中
                        vltEngine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
                            System.Web.Hosting.HostingEnvironment.MapPath("~/"));//模板文件所在的文件夹 MapPath服务器端路径 映射为物理路径
                        //'~/'位于项目根文件下
                        vltEngine.Init();//实例对象初始化
                        VelocityContext vltContext = new VelocityContext();//创建一个上下文对象
                        // vltContext.Put("data", data);//设置参数,在模板中可以通过$data来引用
                        vltContext.Put("username", username);
                        vltContext.Put("password", password);
                        vltContext.Put("msg", "登陆成功");
                        Template vltTemplate = vltEngine.GetTemplate("login.html");
                        System.IO.StringWriter vltWriter = new System.IO.StringWriter();
                        vltTemplate.Merge(vltContext, vltWriter);
                        string html = vltWriter.GetStringBuilder().ToString();
                        context.Response.Write(html);
                    }
                    else
                    {
                        VelocityEngine vltEngine = new VelocityEngine();//初始化VelocityEngine引擎
                        vltEngine.SetProperty(RuntimeConstants.RESOURCE_LOADER, "file");//模板文件位于文件系统中
                        vltEngine.SetProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
                            System.Web.Hosting.HostingEnvironment.MapPath("~/"));//模板文件所在的文件夹 MapPath服务器端路径 映射为物理路径
                        //'~/'位于项目根文件下
                        vltEngine.Init();//实例对象初始化
                        VelocityContext vltContext = new VelocityContext();//创建一个上下文对象
                        // vltContext.Put("data", data);//设置参数,在模板中可以通过$data来引用
                        vltContext.Put("username", username);
                        vltContext.Put("password", password);
                        vltContext.Put("msg", "登陆失败");
                        Template vltTemplate = vltEngine.GetTemplate("login.html");
                        System.IO.StringWriter vltWriter = new System.IO.StringWriter();
                        vltTemplate.Merge(vltContext, vltWriter);
                        string html = vltWriter.GetStringBuilder().ToString();
                        context.Response.Write(html);
                    }
                }
            }  
            public bool IsReusable
            {
                get
                {
                    return false;
                }
            }
        }
    }

    这样一个简单的登陆功能页面静态化就做好了
    ps

    一般来说一些不经常更新内容的页面就可以考虑进行静态操作,比如新闻页面,小说的章节页面,毕竟这些内容一旦发布了,就很少会进行改动。而且可以使用CDN(CDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。)技术进行加速。

      优点:

      1、利于SEO,体现在友好的URL路径和快速的访问速度,asp.net mvc网站可以忽略URL的影响。

      2、一个字:就是快!!!静态页面访问速度快,用户体验好,结合CDN技术、缓存和相应的架构,提速效果更加明显!

      3、相比动态页面一定程度上减轻服务器压力

      缺点:

      1、可能需要使用大量硬盘空间

      2、增加网站开发复杂度,不利于程序维护和管理。

      3、不太灵活,静态化操作是需要触发开关的,不可能频繁的进行操作,可能导致数据更新慢,需要进行相应的设计。

    展开全文
  • 快速初始化HTML5 EJS样板(可立即完成HTML5 EJS样板) Quicint是使用EJS作为模板引擎构建HTML5网站的样板。它适用于静态建筑项目,因为它相对容易批量生产页面。 Quicint是使用EJS作为模板引擎Plate来构建HTML5...
  • 基于最新Spring 5.x,详细介绍了Spring MVC 初始化流程的源码,主要包括DispatcherServlet与MVC子容器的初始化,以及各种MVC组件的初始化

      基于最新Spring 5.x,详细介绍了Spring MVC 初始化流程的源码,主要包括DispatcherServlet与MVC子容器的初始化,以及各种MVC组件的初始化。

      上一篇文章我们讲解了ContextLoaderListener监听器与根上下文容器的初始化。
      ContextLoaderListenercontextInitialized方法回调完毕之后,Root WebApplicationContext初始化完毕,随后会初始化全部的Filter,并且执行init回调,最后会按顺序初始化全部的即时创建的Servlet,对于Spring MVC来说,最重要的就是DispatcherServlet,该过程同时会涉及到MVC子容器的创建和初始化,以及各种MVC组件的初始化。一起来看看DispatcherServlet的初始化源码吧!

      下面的源码版本基于5.2.8.RELEASE

    Spring MVC源码 系列文章

    Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化

    Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】

    Spring MVC 初始化源码(3)—<mvc:annotation-driven >配置标签的源码解析

    Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析

    Spring MVC 请求执行流程的源码深度解析【两万字】

    1 DispatcherServlet的概述

      DispatcherServlet作为Spring MVC的核心类,基本上所有的请求都是通过该Servlet来进行分发。此前的Spring MVC的学习系列文章中我们已经详细学习了它的功能和流程,现在我们来学习它的源码。
      DispatcherServlet的uml类图如下:
    在这里插入图片描述
      可以看到DispatcherServlet最终继承了HttpServlet方法,实现了Servlet接口,因此它也是一个JavaEE中的Servlet标准实现,并且主要处理HTTP请求。
      HttpServletBean是HttpServlet的简单的抽象实现,主要功能是解析web.xml文件中的<servlet/>标签下面的<init-param/>标签配置的参数,将其设置为bean的对应的属性。该Servlet将具体的请求处理留给子类实现,仅仅继承HttpServlet的默认行为(doGet,doPost等)。
      FrameworkServlet是Spring Web框架的基础servlet。该类的功能有两个:

    1. 为每个该类型的servlet关联一个子WebApplicationContext实例。Servlet的配置由Servlet名称空间中的bean确定。
    2. 发布有关请求处理的事件,无论是否成功处理了请求。子类必须实现doService方法以执行真正的请求处理。

      DispatcherServlet是HTTP请求处理程序/控制器的中央调度程序,在初始化的时候它会初始化各个功能组件的实现类,并且在后续实现具体的请求处理流程,主要是通过调度各个组件来处理请求,几乎可以处理所有请求。
      DispatcherServlet的一系列初始化操作基本都是在init方法中完成的,这个init方法就是Servelet接口提供初始化方法,因此我们从该方法入手。

    /**
     * GenericServlet的属性
     * 保存了ServletConfig参数
     */
    private transient ServletConfig config;
    
    /**
     * GenericServlet的init方法
     *
     * @param config Servlet配置
     */
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        //调用初始化方法
        this.init();
    }
    
    /**
     1. GenericServlet的init方法
     2. <p>
     3. 应该被子类重写的方法
     */
    public void init() throws ServletException {}
    

      该方法整体来说做了三件事:

    1. 将当前ServletConfiginit parameters参数填充到当前DispatcherServlet的实例的对应的属性中。可以在web.xml中对应的<Servlet>标签下通过设置<init-param>标签来配置各种属性。主要是HttpServletBean#init()方法。
    2. 初始化于此Servlet关联的MVC子容器,该容器的父容器就是Root Application(可能为null),随后会以属性名:org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletNamevalue为此容器的方法存入ServletContext的属性中。主要是FrameworkServlet#initServletBean方法。
    3. 初始化Servlet使用的MVC组件。主要是DispatcherServlet#onRefresh方法。

    2 HttpServletBean#init()设置init属性

      HttpServletBeaninit()方法就是将当前ServletConfiginit parameters参数填充到当前DispatcherServlet的实例的对应的属性中。
      也就是将web.xml<servlet/>标签下的<init-param/>标签定义的属性设置到对应Servlet实例的对应名字的属性中,最常见属性就是contextConfigLocation、namespace等等。
      在属性填充完毕之后们将会调用initServletBean方法,HttpServletBean提供了空的实现,主要用于被子类重写并实现自定义的扩展逻辑。它的子类FrameworkServlet就重写了该方法。

    /**
     * GenericServlet的init方法
     * <p>
     * 应该被子类重写的方法
     */
    public void init() throws ServletException {
    }
    
    /**
     * HttpServletBean的属性
     * <p>
     * 必须的参数集合,默认是一个空集合
     */
    private final Set<String> requiredProperties = new HashSet<>(4);
    
    /**
     * HttpServletBean的方法
     * <p>
     * 将配置参数映射到该servlet的bean属性上,并调用子类初始化。
     *
     * @throws ServletException 如果bean属性无效(或缺少必需的属性),或者子类初始化失败。
     */
    @Override
    public final void init() throws ServletException {
        /*
         * 1 新建一个ServletConfigPropertyValues实例,根据Servlet的init参数设置bean属性
         * 就是将ServletConfig内部的init parameters初始化参数存储到ServletConfigPropertyValues中
         * 如果有requiredProperties中的必须属性没有设置,那么抛出ServletException
         */
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        //如果有参数
        if (!pvs.isEmpty()) {
            try {
                //获取当前DispatcherServlet 对象的BeanWrapper对象,以JavaBeans的样式便捷的访问属性。
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                //ServletContext的资源加载器
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                //自定义的属性编辑器
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                //初始化DispatcherServlet的BeanWrapper,该方法是一个空实现
                initBeanWrapper(bw);
                /*
                 * 2 通过BeanWrapper便捷的将解析出来的属性集合设置给DispatcherServlet的各种属性
                 */
                bw.setPropertyValues(pvs, true);
            } catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
        /*
         * 3 扩展接口,让子类进行他们自定义的初始化
         * 子类FrameworkServlet就重写了该方法
         */
        initServletBean();
    }
    
    /**
     * HttpServletBean的方法
     * <p>
     * 子类可以重写此方法以执行自定义初始化。
     * 在调用此方法之前,将设置此servlet的所有bean属性。
     * <p>
     * 此默认实现为空。
     */
    protected void initServletBean() throws ServletException {
    }
    

    3 FrameworkServlet#initServletBean初始化MVC容器

      FrameworkServlet重写的initServletBean方法。
      其内部首先调用initWebApplicationContext方法用于初始化并发布此Servlet关联的WebApplicationContext容器,随后调用initFrameworkServlet方法初始化FrameworkServlet本身,子类可以重写此方法以执行其所需的任何初始化,默认实现为空。

    /**
     * FrameworkServlet的属性
     * <p>
     * 此Servlet关联的WebApplicationContext
     */
    @Nullable
    private WebApplicationContext webApplicationContext;
    
    /**
     1. FrameworkServlet的方法
     2. <p>
     3. 设置所有bean属性后调用的方法,主要目的是创建此Servlet的WebApplicationContext。
     */
    @Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
        if (logger.isInfoEnabled()) {
            logger.info("Initializing Servlet '" + getServletName() + "'");
        }
        //当前时间戳
        long startTime = System.currentTimeMillis();
    
        try {
            /*
             * 1 初始化并发布此Servlet关联的WebApplicationContext,核心方法
             */
            this.webApplicationContext = initWebApplicationContext();
            /*
             * 2 初始化FrameworkServlet,在设置任何bean属性并加载WebApplicationContext后,将调用此方法。
             *
             * 子类可以重写此方法以执行其所需的任何初始化,默认实现为空。
             */
            initFrameworkServlet();
        } catch (ServletException | RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            throw ex;
        }
    
        if (logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ?
                    "shown which may lead to unsafe logging of potentially sensitive data" :
                    "masked to prevent unsafe logging of potentially sensitive data";
            logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                    "': request parameters and headers will be " + value);
        }
    
        if (logger.isInfoEnabled()) {
            logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }
    }
    

    3.1 initWebApplicationContext初始化子MVC容器

      该方法用于初始化并发布此Servlet关联的WebApplicationContext,也就是子容器。实际上大部分情况是通过内部的createWebApplicationContext方法实际创建上下文,可以在子类中覆盖。

    1. 获取Root WebApplicationContext作为父上下文,Root WebApplicationContext是通过ContextLoaderListener初始化的,可以为null。
    2. 如果webApplicationContext属性不为null,那么直接调用configureAndRefreshWebApplicationContext配置并刷新该WebApplicationContext,一般都是null。
    3. 如果此前没有关联的WebApplicationContext,那么首先会尝试查找现成的context。首先当前Servlet中获取名为contextAttribute<init-param/>初始化参数的值,该值作为属性名用于检索该servlet应该使用的WebApplicationContext。然后从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值。如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回,如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。一般都不会找到。
    4. 如果在属性中没有指定的WebApplicationContext实例,一般来说都没有。那么调用createWebApplicationContext实例化此servlet关联的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)
    5. 如果此前还没有调用onRefresh方法,那么调用一次onRefresh方法,该方法是一个可以被子类重写的模板方法,用于特定的servlet的添加自定义的刷新工作,在成功刷新WebApplicationContext后调用。
      1. 如果是新建子MVC容器,容器刷新完毕后会发送ContextRefreshedEvent事件,会触发ContextRefreshListener监听器回调。该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法,内部就会执行该方法,并更改标志为true。
      2. 如果上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新,那么标志就是false。
    6. 如果应该将此WebApplicationContext发布为ServletContext的属性(默认需要设置),那么就设置为ServletContext的一个属性。属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT.+servletName
    /**
     * FrameworkServlet的方法
     * <p>
     * 初始化并发布此Servlet关联的WebApplicationContext。
     * 通过内部的createWebApplicationContext方法实际创建上下文,可以在子类中覆盖。
     *
     * @return WebApplicationContext实例
     */
    protected WebApplicationContext initWebApplicationContext() {
        /*
         * 1 获取Root WebApplicationContext
         * 父上下文通过ContextLoaderListener初始化,可以为null
         */
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        /*
         * 2 如果webApplicationContext属性不为null,那么直接配置并刷新该WebApplicationContext
         */
        if (this.webApplicationContext != null) {
            //到这里表示在构造时注入了一个上下文实例->直接使用它
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                // 上下文尚未刷新->提供诸如设置父上下文,设置应用程序上下文ID等服务。
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        //上下文实例是在没有显式父级的情况下注入的->将根应用程序上下文(如果有可能为null)设置为父级
                        cwac.setParent(rootContext);
                    }
                    //配置并初始化此上下文容器
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        /*
         * 3 如果wac为null,即此前没有关联的WebApplicationContext
         *
         * 首先当前Servlet中获取名为contextAttribute的<init-param/>初始化参数的值,
         * 该值作为属性名用于检索该servlet应该使用的WebApplicationContext。
         * 然后从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值。
         * 如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回,
         * 如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。
         */
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        /*
         * 4 如果wac还是为null,即在属性中没有指定的WebApplicationContext实例,一般来说都没有
         *
         * 那么实例化此servlet关联的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。
         */
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            wac = createWebApplicationContext(rootContext);
        }
        /*
         * 5 如果此前还没有调用onRefresh方法
         *
         * 那么调用一次onRefresh方法,该方法是一个可以被子类重写的模板方法
         * 用于特定的servlet的添加自定义的刷新工作,在成功刷新WebApplicationContext后调用。
         *
         * 如果是新建子MVC容器,容器刷新完毕后会发送ContextRefreshedEvent事件,会触发ContextRefreshListener监听器回调
         * 该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法,内部就会执行该方法,并更改标志为true
         * 如果上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新,那么标志就是false
         */
        if (!this.refreshEventReceived) {
            //上下文是不具有刷新支持的ConfigurableApplicationContext,或者在构造时注入的上下文已被刷新
            //那么在此处手动触发onRefresh初始化方法
            synchronized (this.onRefreshMonitor) {
                onRefresh(wac);
            }
        }
        /*
         * 6 如果应该将此WebApplicationContext发布为ServletContext的属性,那么就设置为属性,默认需要设置
         */
        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            //属性名为org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletName
            String attrName = getServletContextAttributeName();
            //设置为ServletContext的一个属性
            getServletContext().setAttribute(attrName, wac);
        }
        return wac;
    }
    
    
    /**
     * FrameworkServlet的方法
     * <p>
     * WebApplicationContext的ServletContext属性的前缀
     */
    public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
    
    /**
     1. 返回此Servlet关联的WebApplicationContext在ServletContext中的属性名称。
     2. 默认实现返回SERVLET_CONTEXT_PREFIX + servletName
     */
    public String getServletContextAttributeName() {
        return SERVLET_CONTEXT_PREFIX + getServletName();
    }
    

    3.1.1 configureAndRefreshWebApplicationContext配置并刷新子容器

      该方法用于配置并刷新此Servlet关联的MVC子容器,步骤类似于此前文章中讲过的Root容器的同名方法。
      大概步骤如下:

    1. 设置该应用程序上下文的id(一般用不到),默认id就是"org.springframework.web.context.WebApplicationContext:"+项目路径+"/"+servletName,可以通过在web.xml中的<Servlet/>标签下配置名为contextId<init-param/>初始化参数来自定义子容器id。
    2. 将此项目的ServletContext设置给上下文的servletContext属性,将此Servlet的ServletConfig设置给上下文的servletConfig属性,将此Servlet的namespace设置给上下文的namespace属性,手动添加一个监听器SourceFilteringListener
      1. SourceFilteringListener内部包装了一个ContextRefreshListener,容器刷新完毕后会发送ContextRefreshedEvent事件,此时会触发监听器的回调,该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法
    3. 获取容器的Environment环境变量对象,随后调用initPropertySources方法手动初始化Servlet属性源,该方法在refresh()刷新容器的方法之前执行,以确保servlet属性源已准备就绪,可以被refresh()方法正常使用。
    4. 调用postProcessWebApplicationContext方法,在刷新给定的WebApplicationContext并将其激活关联为该Servlet的上下文之前,对其进行后处理,即自定义容器。目前是一个空实现,子类可以重写。
    5. 调用applyInitializers方法用于继续对容器执行自定义操作。默认实现是通过web.xml中配置的<context-param>全局参数globalInitializerClasses,和来自当前<Servlet/>内部的<init-param>初始化参数contextInitializerClasses来确定指定了哪些ApplicationContextInitializer类,并执行初始化,随后使用AnnotationAwareOrderComparator排序(支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序),最后按照排序优先级从高到低依次调用每一个实例的initialize方法来初始化给定的servletContext。
      6 调用容器的refresh方法执行刷新操作,这是核心方法,我们在此前IoC容器初始化源码部分已经着重讲解了。该方法将会初始化容器,包括解析配置文件,创建Spring bean实例,执行各种回调方法等等……操作(源码非常多)。
    /**
     * FrameworkServlet的属性
     * <p>
     * 要分配的WebApplicationContext ID,可通过<init-param/>参数配置
     */
    @Nullable
    private String contextId;
    
    /**
     * FrameworkServlet的方法
     * <p>
     * 配置并刷新新建的mvc WebApplicationContext
     * 该方法中会配置一系列Servlet的属性,初始化并调用ApplicationContextInitializer的扩展点(用于自定义root context),最后会执行refresh刷新容器。
     *
     * @param wac 此servlet关联的上下文
     */
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        /*
         * 1 如果wac的全路径identity字符串形式等于wac的id,那么设置应用程序上下文的id
         */
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            //应用程序上下文ID仍设置为其原始默认值->可以通过<init-param/>参数配置
            if (this.contextId != null) {
                wac.setId(this.contextId);
            } else {
                //产生预设id,默认规则就是"org.springframework.web.context.WebApplicationContext:"+项目路径+"/"+servletName
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
            }
        }
        /*
         * 2 配置一系列属性
         */
        //将此项目的ServletContext设置给上下文的servletContext属性
        wac.setServletContext(getServletContext());
        //将此Servlet的ServletConfig设置给上下文的servletConfig属性
        wac.setServletConfig(getServletConfig());
        //将此Servlet的namespace设置给上下文的namespace属性
        wac.setNamespace(getNamespace());
        //手动添加一个监听器SourceFilteringListener
        //在容器刷新完毕之后会发送ContextRefreshedEvent事件,此时就会触发监听器的回调
        //该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法
        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    
        /*
         * 3 获取容器的Environment环境变量对象,随后调用initPropertySources方法手动初始化属性源
         * 该方法在refresh()刷新容器的方法之前执行,以确保servlet属性源已准备就绪,可以被正常使用
         */
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
        }
        /*
         * 4 在刷新给定的WebApplicationContext并将其激活关联为该Servlet的上下文之前,对其进行后处理
         * 目前是一个空实现,子类可以重写
         */
        postProcessWebApplicationContext(wac);
        /*
         * 5 对当前的WebApplicationContext实例应用给定的ApplicationContextInitializer,以实现自定义上下文的逻辑
         * 这类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法
         */
        applyInitializers(wac);
        /*
         * 6 刷新(初始化)容器
         * 这是核心方法,我们在此前IoC容器初始化源码部分已经着重讲解了
         */
        wac.refresh();
    }
    

    3.1.1.1 getNamespace获取名称空间

      Servlet对应的容器的nameSpace就被设置为Servlet的namespace,可以通过设置该Servlet的名为nameSpace<init-param>初始化参数手动指定名称空间,如未指定,那么默认名称空间为servletName+"-servlet",即如果此servlet的servlet-name为"test",则该servlet使用的默认名称空间将解析为"test-servlet"

    /**
     * FrameworkServlet中的常量属性
     * <p>
     * WebApplicationContext名称空间的后缀。
     * 如果此类的servlet在上下文中被命名为"test",则servlet使用的默认名称空间将解析为"test-servlet"。
     */
    public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
    
    /**
     * FrameworkServlet的方法
     * <p>
     * 获取此nameSpace
     * <p>
     * 返回此servlet的名称空间,如果未设置自定义名称空间,则返回默认方案:
     * 即默认nameSpace为servletName+"-servlet",也可以通过设置Servlet的nameSpace属性手动指定名称空间
     */
    public String getNamespace() {
        return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
    }
    

    3.1.1.2 applyInitializers应用ApplicationContextInitializer扩展

      基于ApplicationContextInitializer自定义MVC子容器,这个方法类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法。
      默认实现是通过web.xml中配置的<context-param>全局参数globalInitializerClasses,和来自当前<Servlet/>内部的<init-param>初始化参数contextInitializerClasses来确定指定了哪些ApplicationContextInitializer类,并执行初始化,随后使用AnnotationAwareOrderComparator排序(支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序),最后按照排序优先级从高到低依次调用每一个实例的initialize方法来初始化给定的servletContext。

    //FrameworkServlet的属性
    
    /**
     * 实际要应用于上下文的ApplicationContextInitializer实例。
     */
    private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
            new ArrayList<>();
    
    /**
     * 在设置的ApplicationContextInitializer的全路径类名
     */
    @Nullable
    private String contextInitializerClasses;
    
    /**
     * FrameworkServlet的方法
     * <p>
     * 在对给定容器应用刷新之前调用所有的ApplicationContextInitializer,一起带实现自定义容器的逻辑
     * 这个方法类似于在初始化Root WebApplicationContext的时候调用的customizeContext方法
     *
     * @param wac 配置的WebApplicationContext(尚未刷新)
     */
    protected void applyInitializers(ConfigurableApplicationContext wac) {
        /*
         * 1 获取并初始化全局ApplicationContextInitializer
         */
        //从servletContext中尝试获取globalInitializerClasses全局参数的值
        String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
        //如果设置了该全局参数
        if (globalClassNames != null) {
            //根据",; \t\n"拆分值字符串
            for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
                //根据给定的全路径名字符串初始化指定的ApplicationContextInitializer实例并加入contextInitializers集合中
                this.contextInitializers.add(loadInitializer(className, wac));
            }
        }
        /*
         * 1 获取并初始化当前Servlet的ApplicationContextInitializer
         */
        //当前Servlet如果设置了名为contextInitializerClasses的<init-param>初始化参数
        if (this.contextInitializerClasses != null) {
            //根据",; \t\n"拆分值字符串
            for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
                //根据给定的全路径名字符串初始化指定的ApplicationContextInitializer实例并加入contextInitializers集合中
                this.contextInitializers.add(loadInitializer(className, wac));
            }
        }
        /*
         * 3 对该集合进行Order排序,可以支持PriorityOrdered接口、Ordered接口、@Ordered注解、@Priority注解的排序
         * 比较优先级为PriorityOrdered>Ordered>@Ordered>@Priority,排序规则是order值越小排序越靠前,优先级越高
         * 没有order值则默认排在尾部,优先级最低。
         */
        AnnotationAwareOrderComparator.sort(this.contextInitializers);
        /*
         * 4 按照优先级从高到低依次调用ApplicationContextInitializer的initialize方法,传递的参数就是当前的应用程序上下文容器
         */
        for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
            initializer.initialize(wac);
        }
    }
    

    3.1.2 findWebApplicationContext查找上下文容器

      如果此前没有已存在的关联的容器,那么首先会尝试查找指定的容器。
      首先当前Servlet中获取名为contextAttribute<init-param/>初始化参数的值,该值作为属性名用于检索该servlet应该使用的WebApplicationContext。然后从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值。如果获取的属性值不为null,那么将该值作为已初始化完成了的WebApplicationContext实例返回,如果获取的属性值为null,那么抛出:“err.servlet_config_not_initialized“异常。
      这个逻辑一般是不会走的,因为Servlet的名为contextAttribute<init-param/>初始化参数一般没有设置。

    /**
     * FrameworkServlet的方法
     * <p>
     * 使用配置的名称从ServletContext属性检索已配置好的WebApplicationContext。
     * 子类可以重写此方法以提供不同的WebApplicationContext检索策略。
     *
     * @return 此Servlet的WebApplicationContext;如果找不到,则为null
     */
    @Nullable
    protected WebApplicationContext findWebApplicationContext() {
        //尝试获取该Servlet的名为contextAttribute的<init-param/>初始化参数的值
        String attrName = getContextAttribute();
        //如果参数值为null,那么返回null
        if (attrName == null) {
            return null;
        }
        //如果参数值不为null,那么从当前ServletContext中调用getAttribute方法基于该属性名获取对应属性值
        WebApplicationContext wac =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
        //如果参数值为null,那么抛出"No WebApplicationContext found: initializer not registered?"异常
        if (wac == null) {
            throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
        }
        //返回结果
        return wac;
    }
    
    
    /**
     * ServletContext的属性名字符串,可在其中找到WebApplicationContext。
     */
    @Nullable
    private String contextAttribute;
    
    /**
     1. 返回ServletContext属性的名称,该属性应用于检索该servlet应该使用的WebApplicationContext。
     */
    @Nullable
    public String getContextAttribute() {
        return this.contextAttribute;
    }
    

    3.1.3 createWebApplicationContext创建子上下文容器

      如果没有找到上下文容器,那么创建与此Servlet绑定的MVC上下文容器。其核心步骤和此前创建Root容器的步骤差不多,包含了创建和刷新的工作。

    1. 获取给定的上下文类Class,默认是XmlWebApplicationContext.class,可以通过此Servlet的名为contextClass<init-param>初始化参数配置。
    2. 用无参构造器,根据给定的Class创建一个上下文实例。
    3. 设置WebApplicationContext的配置路径,通过此Servlet的名为contextConfigLocation<init-param>初始化参数配置。
    4. 调用configureAndRefreshWebApplicationContext方法配置并刷新新建的mvc WebApplicationContext,这个方法我们在此前介绍过了。
    /**
     * FrameworkServlet的方法
     * <p>
     * 实例化此servlet的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。
     * 内部委托给createWebApplicationContext(ApplicationContext)方法
     *
     * @param parent 要使用的父WebApplicationContext;如果没有,则为 null
     * @return 该servlet关联的WebApplicationContext
     */
    protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
        return createWebApplicationContext((ApplicationContext) parent);
    }
    
    
    /**
     * FrameworkServlet的方法
     * <p>
     * 实例化此servlet的WebApplicationContext,可以是默认的XmlWebApplicationContext或自定义上下文类(如果已设置)。
     *
     * @param parent 要使用的父ApplicationContext;如果没有,则为null
     * @return 该servlet关联的WebApplicationContext
     */
    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        /*
         * 1 获取给定的上下文类,默认是XmlWebApplicationContext.class
         * 可以通过此Servlet的名为contextClass的<init-param>初始化参数配置
         */
        Class<?> contextClass = getContextClass();
        //如果不是ConfigurableWebApplicationContext类型则抛出异常
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                            "': custom WebApplicationContext class [" + contextClass.getName() +
                            "] is not of type ConfigurableWebApplicationContext");
        }
        /*
         * 2 调用无参构造器,根据给定的Class创建一个上下文实例
         */
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        //设置环境变量
        wac.setEnvironment(getEnvironment());
        //设置父容器
        wac.setParent(parent);
        /*
         * 3 设置WebApplicationContext的配置路径
         */
        //获取上下文的配置路径,通过此Servlet的名为contextConfigLocation的<init-param>初始化参数配置
        String configLocation = getContextConfigLocation();
        if (configLocation != null) {
            //如果配置了该初始参数,则设置为容器的configLocation参数,支持使用",; \t\n"拆分
            wac.setConfigLocation(configLocation);
        }
        /*
         * 4 配置并刷新新建的mvc WebApplicationContext
         */
        configureAndRefreshWebApplicationContext(wac);
    
        return wac;
    }
    
    /**
     * FrameworkServlet的属性
     * <p>
     * 默认创建的WebApplicationContext实现类,就是XmlWebApplicationContext.class
     */
    private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
    
    /**
     * FrameworkServlet的属性
     * <p>
     * 指定上下文配置位置。
     */
    @Nullable
    private String contextConfigLocation;
    
    /**
     * FrameworkServlet的属性
     * <p>
     * 返回使用的上下文类
     */
    public Class<?> getContextClass() {
        return this.contextClass;
    }
    
    /**
     * 返回显式指定的上下文配置位置(如果有)。
     */
    @Nullable
    public String getContextConfigLocation() {
        return this.contextConfigLocation;
    }
    

    3.1.4 ContextRefreshListener监听器

      configureAndRefreshWebApplicationContext方法中,会手动添加一个手动添加一个监听器SourceFilteringListenerSourceFilteringListener内部包装了一个ContextRefreshListener,容器刷新完毕后会发送ContextRefreshedEvent事件,此时会触发监听器的回调,该监听器的回调就是执行FrameworkServlet的onApplicationEvent方法。

    /**
     * ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
     * only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
     */
    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    
       @Override
       public void onApplicationEvent(ContextRefreshedEvent event) {
          FrameworkServlet.this.onApplicationEvent(event);
       }
    }
    

      onApplicationEvent方法的默认实现调用onRefresh方法,触发此Servlet的上下文相关状态的刷新。

    //FrameworkServlet的方法
    
    /**
     * 用于检测是否已调用onRefresh的标志。
     */
    private volatile boolean refreshEventReceived = false;
    
    /**
     * 监视同步的onRefresh方法执行的同步监视器
     */
    private final Object onRefreshMonitor = new Object();
    
    /**
     * FrameworkServlet的方法
     * <p>
     * 从此Servlet的WebApplicationContext接收刷新事件的回调。
     * 默认实现调用onRefresh,触发此Servlet的上下文相关状态的刷新。
     *
     * @param event 传入的ApplicationContext事件
     */
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //标志位改为true
        this.refreshEventReceived = true;
        //添加同步
        synchronized (this.onRefreshMonitor) {
            //调用onRefresh方法
            onRefresh(event.getApplicationContext());
        }
    }
    
    /**
     * FrameworkServlet的方法
     * <p>
     * 可以重写的模板方法以添加特定于servlet的刷新工作,成功刷新上下文后调用。
     * 此实现为空,子类可重写该方法
     *
     * @param context 当前的WebApplicationContext
     */
    protected void onRefresh(ApplicationContext context) {
        // For subclasses: do nothing by default.
    }
    

    4 DispatcherServlet#onRefresh初始化MVC组件

      FrameworkServletonRefresh方法的默认实现为,子类DispatcherServlet重写了该方法,用于初始化此servlet使用的各种MVC组件对象(通过initStrategies方法),这同样是一个核心方法。该方法执行之前,Root WebApplicationContext和DispatcherServlet关联的Child WebApplicationContext都已经创建并初始化完毕。
      需要注意的是,通过DispatcherServlet.properties默认加载的组件仅会创建对象实例,仅提供最基本的mvc功能而并没有激活其他默认配置,而如果通过@EnableWebMvc注解或者<mvc:annotation-driven/>标签开启mvc配置之后,除了会注册这些组件之外,还会加载一些默认配置,并且支持自定义配置。
      比如,如果不开启MVC配置,那么mvc就不支持application/json请求和响应(即使有jackson的依赖也不会自动注册MappingJackson2HttpMessageConverter),也就无法提供REST风格的请求、响应和异常处理,没有默认的conversionService

    /**
     * DispatcherServlet的方法
     * <p>
     * 此实现内部调用initStrategies方法
     */
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
    
    /**
     * DispatcherServlet的方法
     * <p>
     * 默认实现是初始化此DispatcherServlet使用的组件对象,可以在子类中重写以实现其他的自定义逻辑
     */
    protected void initStrategies(ApplicationContext context) {
        /*
         * 1 初始化MultipartResolver,用于处理上传请求
         *
         * 文件上传时就可以使用MultipartResolver来解析上传请求中的文件数据,方便快捷的实现文件上传!
         */
        initMultipartResolver(context);
        /*
         * 2 初始化LocaleResolver,区域解析器
         *
         * 用户的区域也称为Locale,Locale信息是可以由前端直接获取的,可以根据不同的用户区域展示不同的视图,
         * 比如为不同区域的用户可以设置不同的语言和时区,也就是提供国际化视图支持。
         */
        initLocaleResolver(context);
        /*
         * 3 初始化ThemeResolver,主题解析器
         *
         * 主题就是系统的整体样式或风格,可通过Spring MVC框架提供的主题(theme)
         * 设置应用不同的整体样式风格,提高用户体验。Spring MVC的主题就是一些静态资源的集合,
         * 即包括样式及图片,用来控制应用的视觉风格。主题也支持国际化,同一个主题不同区域也可以显示不同的风格。
         */
        initThemeResolver(context);
        /*
         * 4 初始化HandlerMapping,处理器映射器
         *
         * 用于查找能够处理请求的Handler,将请求映射为HandlerExecutionChain 对象
         * (包含一个Handler处理器对象、多个 HandlerInterceptor 拦截器)对象。
         */
        initHandlerMappings(context);
        /*
         * 5 初始化HandlerAdapter,处理器适配器
         * 帮助 DispatcherServlet调用请求映射到的Handler,但是不管Handler实际如何调用。
         * 将会返回一个ModelAndView对象,其中model是一个Map结构,存放了我们返回的所有数据,view是逻辑视图名,即ViewName。
         */
        initHandlerAdapters(context);
        /*
         * 6 初始化HandlerExceptionResolver,异常解析器
         * 如果在前面执行handler的过程中抛出了某个异常,将会走异常解析器的方法!
         * 在异常解析器中可以将此错误映射到其他handler、HTML 错误视图(错误页面)或统一抛出自己的异常。
         */
        initHandlerExceptionResolvers(context);
        /*
         * 7 初始化RequestToViewNameTranslator,请求到视图名的转换器
         *
         * 当未明确提供视图名称时,用于将传入的HttpServletRequest转换为逻辑视图名称的转换器。
         */
        initRequestToViewNameTranslator(context);
        /*
         * 8 初始化ViewResolver,视图解析器
         *
         * ViewResolver根据handler执行之后返回的ModelAndView中的String类型的逻辑视图名解析成物理视图名,即具体的资源地址
         * 再生成对应的View视图对象。但是具体的事务解析以及数据填充工作由View视图自己完成(View. render方法)。
         */
        initViewResolvers(context);
        /*
         * 9 初始化FlashMapManager,FlashMap管理器
         *
         * 用于存储并检索FlashMap,这些FlashMap可用于将属性从一个请求传递到另一个请求,通常是用在重定向中。
         * 也就是说FlashMap主要用在redirect中传递参数,而FlashMapManager则用于管理这些FlashMap。
         */
        initFlashMapManager(context);
    }
    

      从源码中我们可以看到,该方法会尝试初始化各种常见或者不常见的组件,包括MultipartResolver、LocaleResolver、ThemeResolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolver、RequestToViewNameTranslator、ViewResolver、FlashMapManager
      下面我们看看常见组件的初始化源码!

    4.1 initMultipartResolver

      初始化MultipartResolver,会尝试从此Servlet关联的ApplicationContext容器中获取beanName为multipartResolver,类型为MultipartResolver的bean。
      找到的multipartResolver对象将绑定给此DispatcherServlet对象的multipartResolver属性,如果没找到则不会抛出异常,而是该属性赋值为null,即不提供multipart处理功能

    //DispatcherServlet的的属性
    
    /**
     * Bean工厂中此servlet使用的MultipartResolver对象的beanName
     */
    public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
    
    /**
     * 此servlet使用的MultipartResolver。
     */
    @Nullable
    private MultipartResolver multipartResolver;
    
    /**
     * DispatcherServlet的方法
     * <p>
     * 初始化此类使用的MultipartResolver。
     * 如果在BeanFactory中没有给定名称为"multipartResolver"以及类型为MultipartResolver的bean,则不会提供multipart处理功能。
     */
    private void initMultipartResolver(ApplicationContext context) {
        try {
            //从容器中获取名称为"multipartResolver"以及类型为MultipartResolver的多部件处理器,并赋给multipartResolver
            this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Detected " + this.multipartResolver);
            } else if (logger.isDebugEnabled()) {
                logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
            }
        } catch (NoSuchBeanDefinitionException ex) {
            // Default is no multipart resolver.
            //如果没找到就赋值为null,即不提供multipart处理功能,而不是抛出异常
            this.multipartResolver = null;
            if (logger.isTraceEnabled()) {
                logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
            }
        }
    }
    

    4.2 initHandlerMappings

      初始化HandlerMapping,HandlerMapping可以通过配置名为detectAllHandlerMappings<init-param>初始化参数来控制是否尝试从此Servlet关联的ApplicationContext容器中查找所有的HandlerMapping的bean或者仅仅在容器中查找名为“handlerMapping”的单个HandlerMapping的bean,该参数默认为true
      如果以上两种方式都没有在容器中找到至少一个HandlerMapping的bean实例,那么将尝试注册默认的HandlerMapping来确保至少有一个HandlerMapping,spring-webmvc-5.2.8.RELEASE的依赖中默认的HandlerMapping有三个,类型为org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping、org.springframework.web.servlet.function.support.RouterFunctionMapping
      所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。

    //DispatcherServlet的属性
    
    /**
     * 此servlet使用的HandlerMapping列表
     */
    @Nullable
    private List<HandlerMapping> handlerMappings;
    
    /**
     * 是否需要检测所有HandlerMappings还是只期望检测单个名为"handlerMapping"的bean?
     */
    private boolean detectAllHandlerMappings = true;
    
    /**
     * Bean工厂中的HandlerMapping对象的beanName,仅在关闭detectAllHandlerMappings=false时使用。
     */
    public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";
    
    /**
     * DispatcherServlet的方法
     * <p>
     * 初始化此类使用的HandlerMappings。
     * 如果在BeanFactory中没有为此Servlet定义HandlerMapping的Bean,则默认为BeanNameUrlHandlerMapping。
     */
    private void initHandlerMappings(ApplicationContext context) {
        //handlerMappings置为null
        this.handlerMappings = null;
        //是否需要检测所有的HandlerMapping,默认需要,可以通过init-param初始化参数指定
        //如果需要
        if (this.detectAllHandlerMappings) {
            // 在ApplicationContext中找到所有HandlerMapping实例,包括Root ApplicationContext,不包括非单例的
            //返回一个map key为beanName,value为对应的实例
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            //如果ApplicationContext中具有至少一个HandlerMapping的bean
            if (!matchingBeans.isEmpty()) {
                //获取map的所有value,即所有的HandlerMapping实例集合
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                //最后使用AnnotationAwareOrderComparator比较器对handlerMappings进行排序,这说明handlerMapping支持order排序
                //该比较器支持Ordered、PriorityOrdered接口,以及@Order、@Priority注解的排序,比较优先级为PriorityOrdered>Ordered>@Ordered>@Priority,
                //排序规则是order值越小排序越靠前,优先级越高,没有order值则默认排在尾部,优先级最低。
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        } else {
            //如果不需要
            try {
                //那么仅仅获取名为"handlerMapping"类型为HandlerMapping的单个bean实例
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                //仅仅使用单个handlerMapping
                this.handlerMappings = Collections.singletonList(hm);
            } catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
                //如果没有找到符合条件的bean,那么该异常被忽略,稍后我们将添加默认的HandlerMapping。
            }
        }
    
        /*
         * 1 如果handlerMappings还是为null,那说明
         * 在ApplicationContext中找不到任何HandlerMapping的bean或者找不到符合条件的HandlerMapping
         * 那么通过注册默认的HandlerMapping来确保至少有一个HandlerMapping
         *
         * 在Spring 5.2.8.RELEASE中,默认注册的HandlerMapping有三个,分别为
         * org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
         * org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
         * org.springframework.web.servlet.function.support.RouterFunctionMapping
         * 最重要的就是RequestMappingHandlerMapping了
         */
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isTraceEnabled()) {
                logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                        "': using default strategies from DispatcherServlet.properties");
            }
        }
    }
    

    4.2.1 getDefaultStrategies获取默认组件

      在初始化上面的那些组件的时候,如果在容器中没有指定组件bean,那么都会调用该方法获取默认的组件。该方法用于获取默认组件对象的列表,默认实现使用"DispatcherServlet.properties"文件(与DispatcherServlet类位于同一包中)来确定使用的默认组件。
      最开始获取的都是默认组件的全路径名字符串,随后会转换为Class并通过ApplicationContext内部的BeanFactory调用createDefaultStrategy方法来实例化指定具体类型的组件对象,该过程中将会执行Bean的完整初始化应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调,但是通过此方法创建的默认实例不会被加入Spring容器中管理。

    /**
     * DispatcherServlet的方法
     * <p>
     * 获取默认组件对象的列表,默认实现使用"DispatcherServlet.properties"文件(与DispatcherServlet类位于同一包中)来确定使用的默认组件。
     * 通过ApplicationContext内部的BeanFactory来实例化组件对象,将会执行Bean的完整初始化
     * 应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调。
     *
     * @param context           当前的WebApplicationContext
     * @param strategyInterface 组件接口类型
     * @return 相应组件对象的列表
     */
    @SuppressWarnings("unchecked")
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        //组件接口的全路径名字符串
        String key = strategyInterface.getName();
        //获取对应的默认的组件类型字符串
        String value = defaultStrategies.getProperty(key);
        //如果有默认组件
        if (value != null) {
            //依据","拆分字符串,因为value中可能包含多个组件的全路径名,且使用","分隔。
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            //默认的组件实例集合
            List<T> strategies = new ArrayList<>(classNames.length);
            //遍历拆分后的组件全路径名
            for (String className : classNames) {
                try {
                    //转化为Class
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    //通过ApplicationContext内部的BeanFactory来实例化组件对象,将会执行Bean的完整初始化
                    //应用所有适用的BeanPostProcessor,还会填充带注入注解的字段和方法,以及应用所有标准的bean初始化回调。
                    Object strategy = createDefaultStrategy(context, clazz);
                    //加入到集合中
                    strategies.add((T) strategy);
                } catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find DispatcherServlet's default strategy class [" + className +
                                    "] for interface [" + key + "]", ex);
                } catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                                    className + "] for interface [" + key + "]", err);
                }
            }
            //返回
            return strategies;
        } else {
            //返回空集合
            return new LinkedList<>();
        }
    }
    

      源码中一个非常重要的属性就是defaultStrategies,该属性在DispatcherServlet这个类被加载的时候就在静态块中被初始化了,其内部的数据实际上就是从相对于DispatcherServlet类的DispatcherServlet.properties配置文件中加载的数据,就是一些默认的组件。key为组件接口全路径名,value为默认组件实例的全路径名字符串,多个默认组件名使用“,”分隔。

    /**
     * 定义DispatcherServlet的默认组件名称的类路径资源的名称(相对于DispatcherServlet类)。
     */
    private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
    
    /**
     * DispatcherServlet.properties加载之后的map
     */
    private static final Properties defaultStrategies;
    
    /*DispatcherServlet的静态块,加载默认组件资源*/
    static {
        // 从属性文件加载默认组件实现。
        // 当前这严格是内部的,并不意味着应由应用程序开发人员自定义。
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            //加载到defaultStrategies集合中
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException ex) {
            throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
        }
    }
    

      DispatcherServlet.properties配置文件在spring-webmvc的依赖中,和DispatcherServlet同一路径。
    在这里插入图片描述
      通过查看该配置文件的内容,即可知道,某个版本的Spring MVC将会默认使用哪些组件, spring-webmvc-5.2.8.RELEASE版本中的配置文件内容如下:

    org.springframework.web.servlet.LocaleResolver=org.springframework.web.ser
    vlet.i18n.AcceptHeaderLocaleResolver
    
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
       org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
       org.springframework.web.servlet.function.support.RouterFunctionMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
       org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
       org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
       org.springframework.web.servlet.function.support.HandlerFunctionAdapter
    
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
       org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
       org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    
    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
    
    org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
    
    org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
    
    

    4.3 initLocaleResolver

      初始化LocaleResolver,首先会尝试从此Servlet关联的ApplicationContext容器中查找名为“localeResolver”的单个LocaleResolver的bean。
      如果没有在容器中找到指定的LocaleResolver的bean实例,那么将尝试注册默认的LocaleResolver,spring-webmvc-5.2.8.RELEASE的依赖中默认的LocaleResolver类型为org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
      所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。

    //DispatcherServlet的属性
    
    /**
     * 此servlet使用的LocaleResolver。
     */
    @Nullable
    private LocaleResolver localeResolver;
    
    /**
     * Bean工厂中的LocaleResolver对象的beanName。
     */
    public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
    
    /**
     1. DispatcherServlet的方法
     2. <p>
     3. 初始化此Servlet使用的LocaleResolver。
     4. 如果在BeanFactory中没有为此给定名称的bean,则我们默认为AcceptHeaderLocaleResolver。
     */
    private void initLocaleResolver(ApplicationContext context) {
        try {
            //从此Servlet关联的ApplicationContext容器中查找名为“handlerMapping”的单个LocaleResolver的bean。
            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
            if (logger.isTraceEnabled()) {
                logger.trace("Detected " + this.localeResolver);
            } else if (logger.isDebugEnabled()) {
                logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
            }
        } catch (NoSuchBeanDefinitionException ex) {
            // We need to use the default.
            //如果没有获取到,那么使用默认的localeResolver,即org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
            if (logger.isTraceEnabled()) {
                logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
                        "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
            }
        }
    }
    

    4.4 其他组件的初始化

      initHandlerAdapters、initHandlerExceptionResolvers、initViewResolvers这三个方法的源码原理和上面的initHandlerMappings方法非常相似,initThemeResolver、initRequestToViewNameTranslator、initFlashMapManager这三个方法的源码原理和上面的initLocaleResolver方法非常相似,在此不再赘述。所有的默认组件都被定义在与DispatcherServlet同级包路径的DispatcherServlet.properties文件中。
      spring-webmvc-5.2.8.RELEASE的依赖中:

    1. 默认的HandlerAdapter有四个,类型为org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter、org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter、org.springframework.web.servlet.function.support.HandlerFunctionAdapter。
    2. 默认的HandlerExceptionResolver有三个,类型为org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver、org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver、org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver。
    3. 默认的HandlerExceptionResolver有一个,类型为org.springframework.web.servlet.view.InternalResourceViewResolver。
    4. 默认的ThemeResolver有一个,类型为org.springframework.web.servlet.theme.FixedThemeResolver。
    5. 默认的RequestToViewNameTranslator有一个,类型为org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator。
    6. 默认的FlashMapManager有一个,类型为org.springframework.web.servlet.support.SessionFlashMapManager。

    5 总结

      本次我们学习了DispatcherServlet与子容器的初始化,以及各种MVC组件的初始化,核心方法就是Servlet的init()方法,该方法大概又可以分为三步,分别涉及到三个核心子方法:

    1. 将当前ServletConfiginit parameters参数填充到当前DispatcherServlet的实例的对应的属性中,方便后面的步骤中使用。可以在web.xml中对应的<Servlet>标签下通过设置<init-param>标签来配置各种属性。主要是HttpServletBean#init()方法。
    2. 初始化于此Servlet关联的MVC子容器,该容器的父容器就是Root Application(可能为null),随后会以属性名:org.springframework.web.servlet.FrameworkServlet.CONTEXT. + servletNamevalue为此容器的方法存入ServletContext的属性中。主要是FrameworkServlet#initServletBean()方法。
    3. 初始化Servlet使用的MVC组件。主要是DispatcherServlet#onRefresh()方法。

    相关文章:
      https://spring.io/
      Spring Framework 5.x 学习
      Spring MVC 5.x 学习
      Spring Framework 5.x 源码

    如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

    展开全文
  • 初始化

    千次阅读 2016-10-22 01:46:38
    初始化Flask程序创建一个Flask类的对象,使用WSGI协议把客户端的所有请求转发给这个对象。from flask import Flask app = Flask(__name__)Flask类的构造函数只有一个必须制定的参数,即程序主模块或包的名字。 ...
  • 初始化下拉框——layui

    千次阅读 2019-05-07 21:28:29
    页面代码: <div id="levelView">...下拉框html模板 <script id="level" type="text/html"> <div class="layui-input-block"> <select name="level" id="_level" la...
  • angularJS 初始化

    千次阅读 2016-11-10 15:59:54
    通过绑定来进行angular的初始化,会把js代码侵入到html中。 ng-app是angular的一个指令,代表一个angular应用(也叫模块)。使用ng-app或ng-app=""来标记一个DOM结点,让框架会自动加载。也就是说,ng-app是可以...
  • 本文介绍一下笔者用到的两种方式:调用Ajax动态更新页面,或用自定义标签来初始化。 调用Ajax动态更新:  这是笔者使用的一种基本方式,也是主要方法。前端页面预留一个显示表格的区域或弹框,给用户提供一个触发点...
  • CSS样式初始化代码

    万次阅读 多人点赞 2016-07-30 06:26:25
    建站老手都知道,这是为了考虑到浏览器的兼容问题,其实不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面差异。当然,初始化样式会对SEO有一定的影响,但鱼和熊掌不可兼得,但...
  • CSS初始化样式

    千次阅读 2017-12-08 09:44:35
    为什么初始化css? 考虑到浏览器兼容性问题,不同浏览器对标签的默认值是不同的通用初始化样式 -------body{ margin: 0;padding: 0;font-family: "微软雅黑";overflow: hidden;} body,...
  • 这一篇,我们开始搭建留言板的模板布局,我们的布局也将遵从W3C的规范用DIV+CSS,我们的书写规范也将遵从一定的规范,本人之前是没有什么规范的,都是乱写一通,但是我们要趁着这个机会,将这个坏习惯改掉,所以大家...
  • 让vue-cli初始化后的项目集成支持SSR

    万次阅读 2017-11-14 18:56:23
    初始化的只适合在浏览器的运行,所以要改造两端都可以使用的文件,同样为了避免产生 单例 的影响,这里将导出一个 createApp 的工厂函数: import Vue from 'vue' import App from './App' import { ...
  • Beetl页面模板文档

    万次阅读 2017-12-28 17:04:53
    自定义占位符和控制语句起始符号,这有利于减小模板语法对模板的倾入性,比如在html模板中,如果定义控制语句符号是 ,那么,大部分模板文件都能通过浏览器打开。有的使用者仅仅采用了单个符号@ (或者单个符号“~”...
  • 前端Vue项目——初始化及导航栏

    千次阅读 2019-06-24 21:08:19
    一、项目初始化  创建webpack模板项目如下所示: MacBook-Pro:PycharmProjects hqs$ vue init webpack luffy_project ? Project name luffy_project ? Project description A Vue.js project ? Author hqs ? Vue ...
  • //父页面向子页面传值,定义的子页面的父级,然后从父页面向子页面传值 var body = layui.layer.getChildFrame('form', index);*/ //父页面定义参数属性 datas=data; //子页面接收父类的参数...
  • Vite 项目初始化配置

    千次阅读 2021-02-19 17:08:49
    一、环境变量设置 (dev,prod) 新建.env.development.env....直接下载npm install sass -D 页面style lang="scss" 即可使用 无需下载node-sass sass-loader等webpack loader 三、样式的兼容性添加(autoprefi...
  • Vue初始化加载顺序

    千次阅读 2019-08-20 17:14:13
    1、computed是在HTML DOM加载后执行的,如赋值; 2、methods方法必需一定条件的触发才会执行,如点击事件; 3、watch用于观察vue实例上的数据变动,对应一个对象,键是观察表达式,值是对应回掉,值也可以是方法名...
  • 1、页面代码如下,弹出窗是用的layer: &lt;input type="button" class="btn_default" style="width: 100px;" onclick="investigation();" value="导入心理咨询...
  • Vue实例初始化的选项配置对象详解

    千次阅读 2019-06-25 08:57:34
    Vue的实例是Vue框架的入口,其实也就是前端的ViewModel,它包含了页面中的业务逻辑处理、数据模型等,当然...前面我们已经用了很多次 new Vue({…})的代码,而且Vue初始化的选项都已经用了data、methods、el、comput...
  • 随着互联网技术的发展,页面静态的需求越来越明显,传统的jsp动态页面逐渐的被html静态页面+ajax异步请求所替代,模板技术解决了静态页面的数据更新问题。这篇博客主要是结合spring boot 来介绍一下 thymeleaf模板...
  • KindEditor初始化参数列表

    万次阅读 2014-08-14 02:57:26
    插入模板 anchor 插入锚点 noDisableItems designMode   为false时,要保留的工具栏图标。 数据类型: Array 默认值: [‘source’, ‘fullscreen’] ...
  • 1.安装插件html-webpack-plugin 在终端输入命令如下: cnpm install html-webpack-plugin –save-dev2.进入webpack.config.js,加入一行代码,引用该插件,var ...同时写入plugins属性,对插件进行初始化plugins:
  • Kendo UI开发教程(3): 初始化Data 属性

    万次阅读 多人点赞 2013-06-19 07:36:40
    前面在介绍准备Kendo UI开发环境时我们使用jQuery的方法将一个HTML元素转换成一个Kendo UI控件: $(“#datepicker”).kendoDatePicker();除了使用jQuery插件的方法来初始化...使用初始化Data属性的方法在页面上包含有
  • 本篇文章拿springboot启动后,自动打开1个浏览器访问项目的html5页面来演示 介绍2种方法,推荐第2种 第一种 手动给spring加入监听任务,比较麻烦 第1步:先写1个线程类,在springboot启动加载完,自动执行的...
  • CppCMS模板系统

    千次阅读 热门讨论 2012-04-15 21:07:45
    CppCMS不知不觉已经用了很久了,...本篇从自己的体会谈谈模板系统(template system).下面这篇文章系统的介绍了template system。本文部分选取其中的例子,但是从我的角度重新解释。作者的文章有时候不是那么容易理解。...
  • 页面上一些基础数据或者其他页面经常用到部分,可以独立出来做成小组件,组件预留调用入口,需要的页面直接调用即可。 如图,页面中的展示分类和搜索标签在多个页面重复使用,可以将这部分内容独立出来,做成组件,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 84,201
精华内容 33,680
关键字:

系统初始化页面html模板