`
Donald_Draper
  • 浏览: 951971 次
社区版块
存档分类
最新评论

Log4j初始化详解

阅读更多
java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
Java transient关键字使用小记:http://www.cnblogs.com/lanxuezaipiao/p/3369962.html
Log4j初始化详解:http://donald-draper.iteye.com/blog/2332385
Log4j日志输出详解 :http://donald-draper.iteye.com/blog/2332395
slf4j + Log4j的使用:http://donald-draper.iteye.com/blog/2332407
上一篇简单学习了Log4j的使用,今天来看一下,日志初始化,
我们就从下面这一句来看:
private static Logger log = Logger.getLogger(testLog4j.class);

查看Logger
public class Logger extends Category
{
    protected Logger(String name)
    {
        super(name);
    }
    //获取Logger
    public static Logger getLogger(String name)
    {
        return LogManager.getLogger(name);
    }
    public static Logger getLogger(Class clazz)
    {
        return LogManager.getLogger(clazz.getName());
    }
    static 
    {
        FQCN = (org.apache.log4j.Logger.class).getName();
    }
}

来看LogManager的getLogger方法
//LogManager
public class LogManager
{
 public static Logger getLogger(String name)
    {
        return getLoggerRepository().getLogger(name);
    }
 //获取本机LoggerRepository
 public static LoggerRepository getLoggerRepository()
    {
        if(repositorySelector == null)
        {
            repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository());
            guard = null;
            Exception ex = new IllegalStateException("Class invariant violation");
            String msg = "log4j called after unloading, see http://logging.apache.org/log4j/1.2/faq.html#unload.";
            if(isLikelySafeScenario(ex))
                LogLog.debug(msg, ex);
            else
                LogLog.error(msg, ex);
        }
        return repositorySelector.getLoggerRepository();
    }
    public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
    static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
    public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
    public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass";
    public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";
    private static Object guard = null;
    private static RepositorySelector repositorySelector;
    //加载log4j配置文件,先加载log4j.xml,如果log4j.xml不存在,
    //则加载log4j.properties文件
    static 
    {
        Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
        repositorySelector = new DefaultRepositorySelector(h);
        String override = OptionConverter.getSystemProperty("log4j.defaultInitOverride", null);
        if(override == null || "false".equalsIgnoreCase(override))
        {
            String configurationOptionStr = OptionConverter.getSystemProperty("log4j.configuration", null);
            String configuratorClassName = OptionConverter.getSystemProperty("log4j.configuratorClass", null);
            URL url = null;
            if(configurationOptionStr == null)
            {
                url = Loader.getResource("log4j.xml");
                if(url == null)
                    url = Loader.getResource("log4j.properties");
            } else
            {
                try
                {
                    url = new URL(configurationOptionStr);
                }
                catch(MalformedURLException ex)
                {
                    url = Loader.getResource(configurationOptionStr);
                }
            }
            if(url != null)
            {
                LogLog.debug("Using URL [" + url + "] for automatic log4j configuration.");
                try
                {
                    OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository());
                }
	   }
	}
    }
}

//DefaultRepositorySelector
public class DefaultRepositorySelector
    implements RepositorySelector
{
    public DefaultRepositorySelector(LoggerRepository repository)
    {
        this.repository = repository;
    }
    public LoggerRepository getLoggerRepository()
    {
        return repository;
    }
    final LoggerRepository repository;
}

//NOPLoggerRepository
public final class NOPLoggerRepository
    implements LoggerRepository
{
    //从这里看,Logger实际上为NOPLogger
      public Logger getLogger(String name)
    {
        return new NOPLogger(this, name);
    }
}

//NOPLogger
public final class NOPLogger extends Logger
{

    public NOPLogger(NOPLoggerRepository repo, String name)
    {
        super(name);
        repository = repo;
        level = Level.OFF;
        parent = this;
    }
}

而Logger继承Category
public class Logger extends Category
{

    protected Logger(String name)
    {
        super(name);
    }
}

//Category
public class Category
    implements AppenderAttachable
{

    protected Category(String name)
    {
        additive = true;
        this.name = name;
    }
    protected String name;
    //volatile
    protected volatile Level level;
    protected volatile Category parent;
    private static final String FQCN;
    protected ResourceBundle resourceBundle;
    protected LoggerRepository repository;
    AppenderAttachableImpl aai;
    protected boolean additive;

    static 
    {
        FQCN = (org.apache.log4j.Category.class).getName();
    }
}

//Level
public class Level extends Priority
    implements Serializable
{
      protected Level(int level, String levelStr, int syslogEquivalent)
    {
        super(level, levelStr, syslogEquivalent);
    }
    public static final int TRACE_INT = 5000;
    public static final Level OFF = new Level(2147483647, "OFF", 0);
    public static final Level FATAL = new Level(50000, "FATAL", 0);
    public static final Level ERROR = new Level(40000, "ERROR", 3);
    public static final Level WARN = new Level(30000, "WARN", 4);
    public static final Level INFO = new Level(20000, "INFO", 6);
    public static final Level DEBUG = new Level(10000, "DEBUG", 7);
    public static final Level TRACE = new Level(5000, "TRACE", 7);
    public static final Level ALL = new Level(-2147483648, "ALL", 7);
    static final long serialVersionUID = 3491141966387921974L;
}

//Priority
public class Priority
{

    protected Priority()
    {
        level = 10000;
        levelStr = "DEBUG";
        syslogEquivalent = 7;
    }

    protected Priority(int level, String levelStr, int syslogEquivalent)
    {
        this.level = level;
        this.levelStr = levelStr;
        this.syslogEquivalent = syslogEquivalent;
    }
    //比较日志级别
      public boolean isGreaterOrEqual(Priority r)
    {
        return level >= r.level;
    }
    transient int level;
    transient String levelStr;
    transient int syslogEquivalent;
    public static final int OFF_INT = 2147483647;
    public static final int FATAL_INT = 50000;
    public static final int ERROR_INT = 40000;
    public static final int WARN_INT = 30000;
    public static final int INFO_INT = 20000;
    public static final int DEBUG_INT = 10000;
    public static final int ALL_INT = -2147483648;
    public static final Priority FATAL = new Level(50000, "FATAL", 0);
    public static final Priority ERROR = new Level(40000, "ERROR", 3);
    public static final Priority WARN = new Level(30000, "WARN", 4);
    public static final Priority INFO = new Level(20000, "INFO", 6);
    public static final Priority DEBUG = new Level(10000, "DEBUG", 7);
}

我们在回到LogManager加载log4j属性文件,关键在这一句
OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository());

这个LoggerRepository实际为NOPLoggerRepository
public class OptionConverter
{
 public static void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy)
    {
        Configurator configurator = null;
        String filename = url.getFile();
        if(clazz == null && filename != null && filename.endsWith(".xml"))
            clazz = "org.apache.log4j.xml.DOMConfigurator";
	//XML配置解析
        if(clazz != null)
        {
            LogLog.debug("Preferred configurator class: " + clazz);
            configurator = (Configurator)instantiateByClassName(clazz, org.apache.log4j.spi.Configurator.class, null);
           
        } else
        {
	    //java属性文件解析log4j.properties
            configurator = new PropertyConfigurator();
        }
	//配置NOPLoggerRepository
        configurator.doConfigure(url, hierarchy);
    }
    static String DELIM_START = "${";
    static char DELIM_STOP = '}';
    static int DELIM_START_LEN = 2;
    static int DELIM_STOP_LEN = 1;

  }
}

来看log4j.properties属性文件解析
//PropertyConfigurator
public class PropertyConfigurator
    implements Configurator
{
    protected Hashtable registry;
    private LoggerRepository repository;
    protected LoggerFactory loggerFactory;
    static final String CATEGORY_PREFIX = "log4j.category.";
    static final String LOGGER_PREFIX = "log4j.logger.";
    static final String FACTORY_PREFIX = "log4j.factory";
    static final String ADDITIVITY_PREFIX = "log4j.additivity.";
    static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
    static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
    static final String APPENDER_PREFIX = "log4j.appender.";
    static final String RENDERER_PREFIX = "log4j.renderer.";
    static final String THRESHOLD_PREFIX = "log4j.threshold";
    private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
    private static final String LOGGER_REF = "logger-ref";
    private static final String ROOT_REF = "root-ref";
    private static final String APPENDER_REF_TAG = "appender-ref";
    public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
    private static final String RESET_KEY = "log4j.reset";
    private static final String INTERNAL_ROOT_NAME = "root";

    public PropertyConfigurator()
    {
        registry = new Hashtable(11);
        loggerFactory = new DefaultCategoryFactory();
    }
    //加载URL文件到Properties
    public void doConfigure(URL configURL, LoggerRepository hierarchy)
    {
        Properties props;
        InputStream istream;
        props = new Properties();
        istream = null;
        URLConnection uConn = null;
        URLConnection uConn = configURL.openConnection();
        uConn.setUseCaches(false);
        istream = uConn.getInputStream();
        props.load(istream);
	//加载URL文件到Properties
        doConfigure(props, hierarchy);
    }
    //根据properties文件配置NOPLoggerRepository
    public void doConfigure(Properties properties, LoggerRepository hierarchy)
    {
        repository = hierarchy;
        String value = properties.getProperty("log4j.debug");
        if(value == null)
        {
            value = properties.getProperty("log4j.configDebug");
            if(value != null)
                LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
        }
        if(value != null)
            LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
        String reset = properties.getProperty("log4j.reset");
        if(reset != null && OptionConverter.toBoolean(reset, false))
            hierarchy.resetConfiguration();
        String thresholdStr = OptionConverter.findAndSubst("log4j.threshold", properties);
        if(thresholdStr != null)
        {
            hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, Level.ALL));
            LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "].");
        }
	//配置RootCategory,rootLogger,appenders
        configureRootCategory(properties, hierarchy);
        configureLoggerFactory(properties);
        parseCatsAndRenderers(properties, hierarchy);
        LogLog.debug("Finished configuring.");
        registry.clear();
    }
}

//配置RootCategory,rootLogger,appenders
void configureRootCategory(Properties props, LoggerRepository hierarchy)
    {
        String effectiveFrefix = "log4j.rootLogger";
        String value = OptionConverter.findAndSubst("log4j.rootLogger", props);
	if(value == null)
        {
            LogLog.debug("Could not find root logger information. Is this OK?");
        } else
        {
            Logger root = hierarchy.getRootLogger();
            synchronized(root)
            {
                parseCategory(props, root, effectiveFrefix, "root", value);
            }
        }
    }

来看 OptionConverter.findAndSubst
//返回key对应的属性
public static String findAndSubst(String key, Properties props)
    {
        String value;
        value = props.getProperty(key);
}

//NOPLoggerRepository,获取rootLogger
 public Logger getRootLogger()
    {
        return new NOPLogger(this, "root");
    }

//PropertyConfigurator
void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value)
    {
        StringTokenizer st = new StringTokenizer(value, ",");
        if(!value.startsWith(",") && !value.equals(""))
        {
            if(!st.hasMoreTokens())
                return;
            //获取rootLogger,日志级别
            String levelStr = st.nextToken();
            if("inherited".equalsIgnoreCase(levelStr) || "null".equalsIgnoreCase(levelStr))
            {
                if(loggerName.equals("root"))
                    LogLog.warn("The root logger cannot be set to null.");
                else
                    logger.setLevel(null);
            } else
            {
                //设置rootLogger,日志级别
                logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG));
            }
            LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
        }
	//移除Appenders
        logger.removeAllAppenders();
        do
        {
            if(!st.hasMoreTokens())
                break;
            String appenderName = st.nextToken().trim();
            if(appenderName != null && !appenderName.equals(","))
            {
                LogLog.debug("Parsing appender named \"" + appenderName + "\".");
		//根据appenderName解析Appender
                Appender appender = parseAppender(props, appenderName);
                if(appender != null)
		    //添加appender
                    logger.addAppender(appender);
            }
        } while(true);
    }

//根据appenderName解析Appender
Appender parseAppender(Properties props, String appenderName)
    {
        Appender appender = registryGet(appenderName);
        if(appender != null)
        {
            LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
            return appender;
        }
	//log4j.appender的前缀
        String prefix = "log4j.appender." + appenderName;
        //log4j.appender.appenderName.layout
        String layoutPrefix = prefix + ".layout";
	//加载Appender,Class,
	//log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
        appender = (Appender)OptionConverter.instantiateByKey(props, prefix, org.apache.log4j.Appender.class, null);
        appender.setName(appenderName);
        if(appender instanceof OptionHandler)
        {
            if(appender.requiresLayout())
            {
	        //加载Layout,class,
		//log4j.appender.D.layout = org.apache.log4j.PatternLayout
                Layout layout = (Layout)OptionConverter.instantiateByKey(props, layoutPrefix, org.apache.log4j.Layout.class, null);
                if(layout != null)
                {
                    appender.setLayout(layout);
                    LogLog.debug("Parsing layout options for \"" + appenderName + "\".");
		    //设置layout的属性
                    PropertySetter.setProperties(layout, props, layoutPrefix + ".");
                    LogLog.debug("End of parsing for \"" + appenderName + "\".");
                }
            }
            String errorHandlerPrefix = prefix + ".errorhandler";
            String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
            if(errorHandlerClass != null)
            {
                ErrorHandler eh = (ErrorHandler)OptionConverter.instantiateByKey(props, errorHandlerPrefix, org.apache.log4j.spi.ErrorHandler.class, null);
                if(eh != null)
                {
                    appender.setErrorHandler(eh);
                    LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\".");
                    parseErrorHandler(eh, errorHandlerPrefix, props, repository);
                    Properties edited = new Properties();
                    String keys[] = {
                        errorHandlerPrefix + "." + "root-ref", errorHandlerPrefix + "." + "logger-ref", errorHandlerPrefix + "." + "appender-ref"
                    };
                    Iterator iter = props.entrySet().iterator();
                    do
                    {
                        if(!iter.hasNext())
                            break;
                        java.util.Map.Entry entry = (java.util.Map.Entry)iter.next();
                        int i;
                        for(i = 0; i < keys.length && !keys[i].equals(entry.getKey()); i++);
                        if(i == keys.length)
                            edited.put(entry.getKey(), entry.getValue());
                    } while(true);
                    PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
                    LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\".");
                }
            }
	    //设置Appender属性
            PropertySetter.setProperties(appender, props, prefix + ".");
            LogLog.debug("Parsed \"" + appenderName + "\" options.");
        }
        parseAppenderFilters(props, appenderName, appender);
        //将Appender放入registry
        registryPut(appender);
        return appender;
    }

//OptionConverter.instantiateByKey
public static Object instantiateByKey(Properties props, String key, Class superClass, Object defaultValue)
    {
        String className = findAndSubst(key, props);
        if(className == null)
        {
            LogLog.error("Could not find value for key " + key);
            return defaultValue;
        } else
        {
            return instantiateByClassName(className.trim(), superClass, defaultValue);
        }
    }

//PropertyConfigurator
// protected Hashtable registry;HashTable<String,Appender>
//将Appender放入registry
void registryPut(Appender appender)
    {
        registry.put(appender.getName(), appender);
    }

下面我们来看看logger.addAppender,都做了些什么
public class Category
    implements AppenderAttachable
{
 //添加Appender
    public synchronized void addAppender(Appender newAppender)
    {
        if(aai == null)
            aai = new AppenderAttachableImpl();
        aai.addAppender(newAppender);
	//通知repository,添加Appender事件
        repository.fireAddAppenderEvent(this, newAppender);
    }
    //移除AllAppenders
public synchronized void removeAllAppenders()
    {
        if(aai != null)
        {
            Vector appenders = new Vector();
            for(Enumeration iter = aai.getAllAppenders(); iter != null && iter.hasMoreElements(); appenders.add(iter.nextElement()));
            aai.removeAllAppenders();
            for(Enumeration iter = appenders.elements(); iter.hasMoreElements(); fireRemoveAppenderEvent((Appender)iter.nextElement()));
            aai = null;
        }
    }
   
}

//AppenderAttachableImpl
public class AppenderAttachableImpl
    implements AppenderAttachable
{
//添加Appender
 public void addAppender(Appender newAppender)
    {
        if(newAppender == null)
            return;
        if(appenderList == null)
            appenderList = new Vector(1);
        if(!appenderList.contains(newAppender))
            appenderList.addElement(newAppender);
    }
    //Vector<Appender>
     protected Vector appenderList;
}

总结:
从以上我们可以看出:log4j,通过LogManager的static语句块,加载配置log4j.properties,由OptionConverter去加载log4j.properties,并委托给PropertyConfigurator,去配置RootLogger,根据RootLogger获取Appender,然后根据属性文件初始化Appender,并添加到RootLogger的appender集合中。


0
2
分享到:
评论

相关推荐

    Android4.X中SIM卡信息初始化过程详解

    本文实例讲述了Android4.X中SIM卡信息初始化过程详解。分享给大家供大家参考,具体如下: Phone 对象初始化的过程中,会加载SIM卡的部分数据信息,这些信息会保存在IccRecords 和 AdnRecordCache 中。SIM卡的数据...

    搞定J2EE:STRUTS+SPRING+HIBERNATE整合详解与典型案例 (1)

    8.4.3 改变初始化和销毁方式 8.4.4 改变异常处理的方式 8.5 小结 第九章 CVS使用指南 9.1 CVS介绍 9.1.1 CVS简介 9.1.2 为什么要使用CVS 9.2 建立CVS的开发环境 9.2.1 下载CVS 9.2.2 配置CVS 9.3 CVS的使用方法 ...

    SDK15 蓝牙5.0笔记 工程搭建与外设硬件篇

    蓝牙协议栈下按键的使用 SDK15 蓝牙5.0笔记12:(蓝牙工程搭建篇)蓝牙协议栈初始化详解 SDK15 蓝牙5.0笔记13:(蓝牙工程搭建篇)蓝牙5.0GAP与GATT详解 SDK15 蓝牙5.0笔记14:(蓝牙工程搭建篇)蓝牙广播初始化与...

    Java日志框架:logback详解

    不管是我参与的别人已经搭建好的项目还是我自己主导的项目,日志框架基本都换成了logback,总结一下,logback大约有以下的一些优点:内核重写、测试充分、初始化内存加载更小,这一切让logback性能和log4j相比有诸多...

    mzt-biz-log:支持Springboot,基于注解的可使用变量的通用操作日志组件

    Springboot-注解-通用操作日志组件此组件解决的问题是: 「谁」在「什么时间」对「什么」做了「什么事」本组件目前针对 Spring-boot 做了 Autoconfig,如果是 SpringMVC,也可自己在 xml 初始化 beanChange Log版本...

    log_archive_dest, log_archive_dest_n和standby_archive_dest

    在oracle的初始化参数中,与归档日志目录有关的有:log_archive_dest, log_archive_dest_n和standby_archive_dest, 那么这三个参数的相互关系如何呢,下面就通过试验进行详细讲解。实验环境为oracle11g。

    《程序天下:J2EE整合详解与典型案例》光盘源码

    8.4.3 改变初始化和销毁方式 8.4.4 改变异常处理的方式 8.5 小结 第九章 CVS使用指南 9.1 CVS介绍 9.1.1 CVS简介 9.1.2 为什么要使用CVS 9.2 建立CVS的开发环境 9.2.1 下载CVS 9.2.2 配置CVS 9.3 CVS的使用方法 ...

    搞定J2EE:STRUTS+SPRING+HIBERNATE整合详解与典型案例 (2)

    8.4.3 改变初始化和销毁方式 8.4.4 改变异常处理的方式 8.5 小结 第九章 CVS使用指南 9.1 CVS介绍 9.1.1 CVS简介 9.1.2 为什么要使用CVS 9.2 建立CVS的开发环境 9.2.1 下载CVS 9.2.2 配置CVS 9.3 CVS的使用方法 ...

    搞定J2EE:STRUTS+SPRING+HIBERNATE整合详解与典型案例 (3)

    8.4.3 改变初始化和销毁方式 8.4.4 改变异常处理的方式 8.5 小结 第九章 CVS使用指南 9.1 CVS介绍 9.1.1 CVS简介 9.1.2 为什么要使用CVS 9.2 建立CVS的开发环境 9.2.1 下载CVS 9.2.2 配置CVS 9.3 CVS的使用方法 ...

    微信小程序App生命周期详解

    onLaunch—-当小程序初始化完成时,会触发 onLaunch(全局只触发一次) onShow —-当小程序启动,或从后台进入前台显示,会触发 onShow onHide —-当小程序从前台进入后台,会触发 onHide onError —-当小程序发生...

    android中wifi原理详解.doc

    二:Wifi模块的初始化::在SystemServer启动的时候,会生成一个ConnectivityService的实例,try{Log.i(TAG,"StartingConnectivityService.");ServiceManager.addService(Context.CONNECTIVITY_SERVICE,...

    VBSCRIP5 -ASP用法详解

    Erase 语句 重新初始化固定数组的元素并重新分配动态数组的存储空间。 Err 对象 含有关于运行时错误的信息。 Eval 函数 计算并返回表达式的值。 Execute 方法 根据指定的字符串,执行正则表达式的搜索。 Execute...

    详解JSON.stringify()的5个秘密特性

    JSON.stringify() 方法能将一个 JavaScript 对象或值转换成一个...//初始化一个 user 对象 const user = { name : Prateek Singh, age : 26 } console.log(user); // 结果 // [object Object] 哦!console.log

    javascrit中undefined和null的区别详解

    1.定义变量,但是没有初始化时,如var a; 2.调用某个函数时,实参个数小于形参个数时,未实参化的形参在函数调用过程中的值是undefined; 3.调用某个对象还没有添加的属性时,也会返回undefined; var obj={} ...

    ES6中Set和Map用法实例详解

    Set函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。 // 例一 var set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] var s = new Set(); [2, 3, 5, 4, 5, 2, 2].map(x =&gt; s.add(x)); for...

    详解JS对象封装的常用方式

    JS是一门面向对象语言,其对象是用prototype属性来模拟的,下面,来看看如何封装JS对象. 常规封装 ...这种方式是比较常见的方式,比较直观,但是Person() 的职责是构造对象,如果把初始化的事情也放在里面完成,

    Nginx定时切割日志实现详解

    前言 ...#初始化 LOGS_PATH=/usr/local/nginx/logs YESTERDAY=$(date -d yesterday +%Y%m%d) #按天切割日志 mv ${LOGS_PATH}/bbs.52itstyle.com.access.log ${LOGS_PATH}/bbs.52itstyle.com.access_$

    深入C++ 函数映射的使用详解

    想想我们在遇到多语句分支时是不是首先想到的是 switc...2.使用数组,查询直接索引定位, 一般来讲我们是连续的初始化数组,也就意味索引(type_func)到函数的映射要连续,所以使用数组索引在扩展上来讲:例如增删元素是

    详解Angular 中 ngOnInit 和 constructor 使用场景

    constructor应该是ES6中明确使用constructor来表示构造函数的,构造函数使用在class中,用来做初始化操作。当包含constructor的类被实例化时,构造函数将被调用。 来看例子: class AppComponent { public name: ...

Global site tag (gtag.js) - Google Analytics