本文转自上善若水的博客,原文出处:http://www.blogjava.net/DLevin/archive/2012/07/10/382678.html。感谢作者的无私分享。

LoggerRepository从字面上理解,它是一个Logger的容器,它会创建并缓存Logger实例,从而具有相同名字的Logger实例不会多次创建,以提高性能。它的这种特性有点类似Spring的IOC概念。Log4J支持两种配置文件:properties文件和xml文件。Configurator解析配置文件,并将解析后的信息添加到LoggerRepository中。LogManager最终将LoggerRepository和Configurator整合在一起。

LoggerRepository接口

LoggerRepository是一个Logger的容器,它负责创建、缓存Logger实例,同时它也维护了Logger之间的关系,因为在Log4J中,所有Logger都组装成以RootLogger为根的一棵树,树的层次由Logger的Name来决定,其中以’.’分隔。

除了做为一个Logger容器,它还有一个Threshold属性,用于过滤所有在Threshold级别以下的日志。以及其他和Logger操作相关的方法和属性。

LoggerRepository的接口定义如下:

 1 public interface LoggerRepository {

 2     public void addHierarchyEventListener(HierarchyEventListener listener);

 3     boolean isDisabled(int level);

 4     public void setThreshold(Level level);

 5     public void setThreshold(String val);

 6     public void emitNoAppenderWarning(Category cat);

 7     public Level getThreshold();

 8     public Logger getLogger(String name);

 9     public Logger getLogger(String name, LoggerFactory factory);

     public Logger getRootLogger();

     public abstract Logger exists(String name);

     public abstract void shutdown();

     public Enumeration getCurrentLoggers();

     public abstract void fireAddAppenderEvent(Category logger, Appender appender);

     public abstract void resetConfiguration();

 }

Hierarchy类

Hierarchy是Log4J中默认对LoggerRepository的实现类,它用于表达其内部的Logger是以层次结构存储的。在对LoggerRepository接口的实现中,getLogger()方法是其最核心的实现,因而首先从这个方法开始。

Hierarchy中用一个Hashtable来存储所有Logger实例,它以CategoryKey作为key,Logger作为value,其中CategoryKey是对Logger中Name字符串的封装,之所以要引入这个类是出于性能考虑,因为它会缓存Name字符串的hash
code,这样在查找过程中计算hash code时就可以直接取得而不用每次都计算。

 1 class CategoryKey {

 2     String name;

 3     int hashCache;

 4 

 5     CategoryKey(String name) {

 6         this.name = name;

 7         hashCache = name.hashCode();

 8     }

 9     final public int hashCode() {

         return hashCache;

     }

     final public boolean equals(Object rArg) {

         if (this == rArg)

             return true;

         if (rArg != null && CategoryKey.class == rArg.getClass())

             return name.equals(((CategoryKey) rArg).name);

         else

             return false;

     }

 }

getLogger()方法中有一个重载函数提供LoggerFactory接口,它用于没有在LoggerRepository中找到Logger实例时创建相应的Logger实例,默认实现直接创建一个Logger实例,用户可以通过自定义LoggerFactory实现创建自己的Logger实例。

 public interface LoggerFactory {

     public Logger makeNewLoggerInstance(String name);

 }

 class DefaultCategoryFactory implements LoggerFactory {

     public Logger makeNewLoggerInstance(String name) {

         return new Logger(name);

     }

 }

getLogger()方法首先根据传入name创建CategoryKey实例,而后从缓存ht字段中查找:

1.       如果找到对应的Logger实例,则直接返回该实例。

2.       如果没有找到任何实例,则使用LoggerFactory创建新的Logger实例,并将该实例缓存到ht集合中,同时更新新创建Logger实例的parent属性。更新parent属性最简单的做法是从后往前以’.’为分隔符截取字符串,使用截取后的字符串从ht集合中查找是否存在Logger实例,如果存在,则新创建的Logger实例的parent即为找到的实例,若在整个遍历过程中都没有找到相应的parent实例,则其parent实例为root。然而如果一个“x.y.z.w”Logger起初的parent设置为root,而后出现“x.y.z”Logger实例,那么就需要更新“x.y.z.w”Logger的parent为“x.y.z”Logger实例,此时就会遇到一个如何找到在集合中已经存在的“x.y.z”Logger实例子节点的问题。当然一种简单的做法是遍历ht集合中所有实例,判断那个实例是不是“x.y.z”Logger实例的子节点,是则更新其parent节点。由于每次的遍历会引起一些性能问题,因而Log4J使用ProvisionNode事先将所有的可能相关的子节点保存起来,并将ProvisionNode实例添加到ht集合中,这样只要找到对应的ProvisionNode实例,就可以找到所有相关的子节点了。比如对“x.y.z.w”Logger实例,它会产生三个ProvisionNode实例(当然如果相应的实例已经存在,则直接添加而无需创建,另外,如果相应节点已经是Logger实例,那么将“x.y.z.w”Logger实例的parent直接指向它即可):ProvisionNode(“x”),
ProvisionNode(“x.y”), ProvisionNode(“x.y.z”),他们都存储了“x.y.z.w”Logger实例作为其子节点。

 1 class ProvisionNode extends Vector {

 2     ProvisionNode(Logger logger) {

 3         super();

 4         this.addElement(logger);

 5     }

 6 }

 7 final private void updateParents(Logger cat) {

 8     String name = cat.name;

 9     int length = name.length();

     boolean parentFound = false;

     // if name = "x.y.z.w", loop thourgh "x.y.z", "x.y" and "x"

     for (int i = name.lastIndexOf('.', length - 1); i >= 0; i = name

             .lastIndexOf('.', i - 1)) {

         String substr = name.substring(0, i);

         CategoryKey key = new CategoryKey(substr); 

         Object o = ht.get(key);

         if (o == null) {

             ProvisionNode pn = new ProvisionNode(cat);

             ht.put(key, pn);

         } else if (o instanceof Category) {

             parentFound = true;

             cat.parent = (Category) o;

             break; // no need to update the ancestors of the closest

                     // ancestor

         } else if (o instanceof ProvisionNode) {

             ((ProvisionNode) o).addElement(cat);

         } else {

             Exception e = new IllegalStateException(

                     "unexpected object type " + o.getClass() + " in ht.");

             e.printStackTrace();

         }

     }

     // If we could not find any existing parents, then link with root.

     if (!parentFound)

         cat.parent = root;

 }

3.   如果找到的是ProvisionNode实例,首先使用factory创建新的Logger实例,将该实例添加到ht集合中,然后更新找到的ProvisionNode内部所有Logger的parent字段以及新创建Logger的parent字段。更新过程中需要注意ProvisionNode中的Logger实例已经指向了正确的parent了,所以只要更新那些ProvisionNode中Logger实例指向的parent比新创建的Logger本身层次要高的那些parent属性。比如开始插入“x.y.z”Logger实例,而后插入“x.y.z.w”Logger实例,此时ProvisionNode(“x”)认为“x.y.z”Logger实例和“x.y.z.w”Logger实例都是它的子节点,而后插入“x”Logger实例,那么只需要更新“x.y.z”Logger的父节点为“x”Logger实例即可,而不用更新“x.y.z.w”Logger实例的父节点。

 1 final private void updateChildren(ProvisionNode pn, Logger logger) {

 2     final int last = pn.size();

 3     for (int i = 0; i < last; i++) {

 4         Logger l = (Logger) pn.elementAt(i);

 5         // Unless this child already points to a correct (lower) parent,

 6         // make cat.parent point to l.parent and l.parent to cat.

 7         if (!l.parent.name.startsWith(logger.name)) {

 8             logger.parent = l.parent;

 9             l.parent = logger;

         }

     }

 }

综合起来,getLogger()方法的实现代码如下:

 1 public Logger getLogger(String name, LoggerFactory factory) {

 2     CategoryKey key = new CategoryKey(name);

 3     Logger logger;

 4     synchronized (ht) {

 5         Object o = ht.get(key);

 6         if (o == null) {

 7             logger = factory.makeNewLoggerInstance(name);

 8             logger.setHierarchy(this);

 9             ht.put(key, logger);

             updateParents(logger);

             return logger;

         } else if (o instanceof Logger) {

             return (Logger) o;

         } else if (o instanceof ProvisionNode) {

             logger = factory.makeNewLoggerInstance(name);

             logger.setHierarchy(this);

             ht.put(key, logger);

             updateChildren((ProvisionNode) o, logger);

             updateParents(logger);

             return logger;

         } else {

             // It should be impossible to arrive here

             return null; // but let's keep the compiler happy.

         }

     }

 }

其他的方法实现则比较简单,对LoggerRepository来说,它也可以像其注册HierarchyEventListener监听器,每当向一个Logger添加或删除Appender,该监听器就会触发。

 1 public interface HierarchyEventListener {

 2     public void addAppenderEvent(Category cat, Appender appender);

 3     public void removeAppenderEvent(Category cat, Appender appender);

 4 }

 5 private Vector listeners;

 6 public void addHierarchyEventListener(HierarchyEventListener listener) {

 7     if (listeners.contains(listener)) {

 8         LogLog.warn("Ignoring attempt to add an existent listener.");

 9     } else {

         listeners.addElement(listener);

     }

 }

 public void fireAddAppenderEvent(Category logger, Appender appender) {

     if (listeners != null) {

         int size = listeners.size();

         HierarchyEventListener listener;

         for (int i = 0; i < size; i++) {

             listener = (HierarchyEventListener) listeners.elementAt(i);

             listener.addAppenderEvent(logger, appender);

         }

     }

 }

 void fireRemoveAppenderEvent(Category logger, Appender appender) {

     if (listeners != null) {

         int size = listeners.size();

         HierarchyEventListener listener;

         for (int i = 0; i < size; i++) {

             listener = (HierarchyEventListener) listeners.elementAt(i);

             listener.removeAppenderEvent(logger, appender);

         }

     }

 }

Hierarchy中保存了threshold字段,用户可以设置threshold。而对root实例,它在够着Hierarchy时就被指定了。getCurrentLoggers()方法将ht集合中所有的Logger实例取出。shutdown()方法遍历所有Logger实例以及root实例,调用所有附加其上的Appender的close()方法,并将所有Appender实例从Logger中移除,最后触发AppenderRemove事件。resetConfiguration()方法将root字段初始化、调用shutdown()方法移除Logger中的所有Appender、初始化所有Logger实例当不将其从LoggerRepository中移除、清楚rendererMap和throwableRender中的数据。

RendererSupport接口

RendererSupport接口支持用户为不同的类设置相应的ObjectRender实例,从而可以从被渲染的类中或许更多的信息而不是默认的调用其toString()方法。

 1 public interface RendererSupport {

 2     public RendererMap getRendererMap();

 3     public void setRenderer(Class renderedClass, ObjectRenderer renderer);

 4 }

 5 Hierarchy类实现了RenderedSupprt接口,而且它的实现也很简单:

 6 RendererMap rendererMap;

 7 public Hierarchy(Logger root) {

 8     

 9     rendererMap = new RendererMap();

     

 }

 public void addRenderer(Class classToRender, ObjectRenderer or) {

     rendererMap.put(classToRender, or);

 }

 public RendererMap getRendererMap() {

     return rendererMap;

 }

 public void setRenderer(Class renderedClass, ObjectRenderer renderer) {

     rendererMap.put(renderedClass, renderer);

 }

在RendererMap类实现中,它使用Hastable保存被渲染的类实例和相应的ObjectRender实例,在查找一个类是否存在注册的渲染类时,如果它本身没有找到,需要向上尝试其父类和接口是否有注册相应的ObjectRender类,如果都没有找到,则返回默认的ObjectRender。

 1 public ObjectRenderer get(Class clazz) {

 2     ObjectRenderer r = null;

 3     for (Class c = clazz; c != null; c = c.getSuperclass()) {

 4         r = (ObjectRenderer) map.get(c);

 5         if (r != null) {

 6             return r;

 7         }

 8         r = searchInterfaces(c);

 9         if (r != null)

             return r;

     }

     return defaultRenderer;

 }

 ObjectRenderer searchInterfaces(Class c) {

     ObjectRenderer r = (ObjectRenderer) map.get(c);

     if (r != null) {

         return r;

     } else {

         Class[] ia = c.getInterfaces();

         for (int i = 0; i < ia.length; i++) {

             r = searchInterfaces(ia[i]);

             if (r != null)

                 return r;

         }

     }

     return null;

 }

 public ObjectRenderer getDefaultRenderer() {

     return defaultRenderer;

 }

 public void put(Class clazz, ObjectRenderer or) {

     map.put(clazz, or);

 }

ThrowableRendererSupport接口

ThrowableRendererSupport接口用于支持设置和获取ThrowableRenderer,从而用户可以自定义对Throwable对象的渲染。

 public interface ThrowableRendererSupport {

     ThrowableRenderer getThrowableRenderer();

     void setThrowableRenderer(ThrowableRenderer renderer);

 }

Hierarchy类以属性的方式实现了该接口,因而每个Hierarchy实例只能有一个全局的ThrowableRenderer,而不能像ObjectRender那样为不同的类定义不同的render。当时这种设计也是合理的,因为对Throwable的渲染最主要的就是其栈的渲染,其他的没什么大的不同,而且对栈渲染方式保持相同的格式会比较好。

 1 private ThrowableRenderer throwableRenderer = null;

 2 public Hierarchy(Logger root) {

 3     

 4     defaultFactory = new DefaultCategoryFactory();

 5     

 6 }

 7 public void setThrowableRenderer(final ThrowableRenderer renderer) {

 8     throwableRenderer = renderer;

 9 }

 public ThrowableRenderer getThrowableRenderer() {

     return throwableRenderer;

 }

Configurator接口

Configurator接口用于定义对配置文件的解析。在Log4J中配置文件解析出来的所有信息都可以放在LoggerRepository中,因而Configurator接口的定义非常简单。

 public interface Configurator {

     public static final String INHERITED = "inherited";

     public static final String NULL = "null";

     void doConfigure(URL url, LoggerRepository repository);

 }

Log4J支持两种文件形式的配置文件:properties文件和xml文件,他们风别对应PropertyConfigurator类和DOMConfigurator类。

PropertyConfigurator类

PropertyConfigurator类解析properties文件的中的配置信息,可以设置log4j.debug为true以打开Log4J内部的日志信息;另外PropertyConfigurator还支持Linux风格的变量,即所有${variable}形式的变量都会被系统中对应的属性或配置文件内部定义的属性替换(先查找系统中的属性,后查找配置文件内部定义的属性);但是PropertyConfigurator不支持一些Log4J中的高级功能,如自定义ErrorHandler和定义AsyncAppender等。

Configurator中最重要的方法是doConfigure()方法,在PropertyConfigurator实现中,首先将配置文件对应的URL读取成Properties对象:

 1 public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {

 2     Properties props = new Properties();

 3     

 4         uConn = configURL.openConnection();

 5         uConn.setUseCaches(false);

 6         istream = uConn.getInputStream();

 7         props.load(istream);

 8     

 9     doConfigure(props, hierarchy);

 }

而后检查是否设置了log4j.debug、log4j.reset、log4j.threshold等属性,如果有则做相应的设置。这里通过OptionConverter.findAndSubst()方法实现属性的查找和变量信息的替换。

 1 public void doConfigure(Properties properties, LoggerRepository hierarchy) {

 2     repository = hierarchy;

 3     String value = properties.getProperty(LogLog.DEBUG_KEY);

 4     if (value == null) {

 5         value = properties.getProperty("log4j.configDebug");

 6         if (value != null)

 7             LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");

 8     }

 9     if (value != null) {

         LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));

     }

     String reset = properties.getProperty(RESET_KEY);

     if (reset != null && OptionConverter.toBoolean(reset, false)) {

         hierarchy.resetConfiguration();

     }

     String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX,

             properties);

     if (thresholdStr != null) {

         hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr,

                 (Level) Level.ALL));

         LogLog.debug("Hierarchy threshold set to ["

                 + hierarchy.getThreshold() + "].");

     }

     

 }

然后分三步解析配置信息:

1.       解析Root Logger配置

首先找到log4j.rootLogger的值,它以逗号’,’分隔,其中第一个值时root的Level信息,之后是要添加到root的Appender名字。对Level信息,直接设置给root就行。对Appender名字,继续解析。

 1 void parseCategory(Properties props, Logger logger, String optionKey,

 2         String loggerName, String value) {

 3     LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value

 4             + "].");

 5     StringTokenizer st = new StringTokenizer(value, ",");

 6     if (!(value.startsWith(",") || value.equals(""))) {

 7         if (!st.hasMoreTokens())

 8             return;

 9         String levelStr = st.nextToken();

         LogLog.debug("Level token is [" + levelStr + "].");

         if (INHERITED.equalsIgnoreCase(levelStr)

                 || NULL.equalsIgnoreCase(levelStr)) {

             if (loggerName.equals(INTERNAL_ROOT_NAME)) {

                 LogLog.warn("The root logger cannot be set to null.");

             } else {

                 logger.setLevel(null);

             }

         } else {

             logger.setLevel(OptionConverter.toLevel(levelStr,

                     (Level) Level.DEBUG));

         }

         LogLog.debug("Category " + loggerName + " set to "

                 + logger.getLevel());

     }

     logger.removeAllAppenders();

     Appender appender;

     String appenderName;

     while (st.hasMoreTokens()) {

         appenderName = st.nextToken().trim();

         if (appenderName == null || appenderName.equals(","))

             continue;

         LogLog.debug("Parsing appender named \"" + appenderName + "\".");

         appender = parseAppender(props, appenderName);

         if (appender != null) {

             logger.addAppender(appender);

         }

     }

 }

相同的Appender可以添加到不同的Logger中,因而PropertyConfigurator对Appender做了缓存,如果能从缓存中找到相应的Appender类,则直接返回找到的Appender。

而后解析以下键值名以及对应类的属性信息:

log4j.appender.appenderName=…

log4j.appender.appenderName.layout=…

log4j.appender.appenderName.errorhandler=…

log4j.appender.appenderName.filter.filterKey.name=…

 1 Appender parseAppender(Properties props, String appenderName) {

 2     Appender appender = registryGet(appenderName);

 3     if ((appender != null)) {

 4         LogLog.debug("Appender \"" + appenderName

 5                 + "\" was already parsed.");

 6         return appender;

 7     }

 8     String prefix = APPENDER_PREFIX + appenderName;

 9     String layoutPrefix = prefix + ".layout";

     appender = (Appender) OptionConverter.instantiateByKey(props, prefix,

             org.apache.log4j.Appender.class, null);

     if (appender == null) {

         LogLog.error("Could not instantiate appender named \""

                 + appenderName + "\".");

         return null;

     }

     appender.setName(appenderName);

     if (appender instanceof OptionHandler) {

         if (appender.requiresLayout()) {

             Layout layout = (Layout) OptionConverter.instantiateByKey(

                     props, layoutPrefix, Layout.class, null);

             if (layout != null) {

                 appender.setLayout(layout);

                 LogLog.debug("Parsing layout options for \"" + appenderName

                         + "\".");

                 PropertySetter.setProperties(layout, props, layoutPrefix

                         + ".");

                 LogLog.debug("End of parsing for \"" + appenderName + "\".");

             }

         }

         final String errorHandlerPrefix = prefix + ".errorhandler";

         String errorHandlerClass = OptionConverter.findAndSubst(

                 errorHandlerPrefix, props);

         if (errorHandlerClass != null) {

             ErrorHandler eh = (ErrorHandler) OptionConverter

                     .instantiateByKey(props, errorHandlerPrefix,

                             ErrorHandler.class, null);

             if (eh != null) {

                 appender.setErrorHandler(eh);

                 LogLog.debug("Parsing errorhandler options for \""

                         + appenderName + "\".");

                 parseErrorHandler(eh, errorHandlerPrefix, props, repository);

                 final Properties edited = new Properties();

                 final String[] keys = new String[] {

                         errorHandlerPrefix + "." + ROOT_REF,

                         errorHandlerPrefix + "." + LOGGER_REF,

                         errorHandlerPrefix + "." + APPENDER_REF_TAG };

                 for (Iterator iter = props.entrySet().iterator(); iter

                         .hasNext();) {

                     Map.Entry entry = (Map.Entry) iter.next();

                     int i = 0;

                     for (; i < keys.length; i++) {

                         if (keys[i].equals(entry.getKey()))

                             break;

                     }

                     if (i == keys.length) {

                         edited.put(entry.getKey(), entry.getValue());

                     }

                 }

                 PropertySetter.setProperties(eh, edited, errorHandlerPrefix

                         + ".");

                 LogLog.debug("End of errorhandler parsing for \""

                         + appenderName + "\".");

             }

         }

         PropertySetter.setProperties(appender, props, prefix + ".");

         LogLog.debug("Parsed \"" + appenderName + "\" options.");

     }

     parseAppenderFilters(props, appenderName, appender);

     registryPut(appender);

     return appender;

 }

2.       解析LoggerFactory配置

查找log4j.loggerFactory的值,保存创建的LoggerFactory实例,使用log4j.loggerFactory.propName的方式设置LoggerFactory实例的属性。

 1 protected void configureLoggerFactory(Properties props) {

 2     String factoryClassName = OptionConverter.findAndSubst(

 3             LOGGER_FACTORY_KEY, props);

 4     if (factoryClassName != null) {

 5         LogLog.debug("Setting category factory to [" + factoryClassName

 6                 + "].");

 7         loggerFactory = (LoggerFactory) OptionConverter

 8                 .instantiateByClassName(factoryClassName,

 9                         LoggerFactory.class, loggerFactory);

         PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX

                 + ".");

     }

 }

3.       解析非Root Logger和ObjectRender配置

解析log4j.logger.、log4j.renderer.、log4j.throwableRenderer.等信息。

另外,PropertyConfigurator还通过PropertyWatchLog类支持每个一段时间检查一次,如果发现配置文件有改动,则自动重新加载配置信息。

DOMConfigurator类

DOMConfigurator使用DOM解析所有Log4J配置文件中的元素,并根据DOM中的元素查找对应的RootLogger、Logger、Appender、Layout等模块。另外DOMConfigurator也支持每隔一段时间检查文件是否有修改,若有,则重新载入新修改后的配置文件。这里DOMConfigurator的实现方式和PropertyConfigurator的实现方式类似,不再详细介绍。

LogManager类

LogManager将Configurator和LoggerRepository整合在一起,它在初始化的时候找到Log4J配置文件,并且将其解析到LoggerRepository中。

 1 static {

 2     Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));

 3     repositorySelector = new DefaultRepositorySelector(h);

 4     String override = OptionConverter.getSystemProperty(

 5             DEFAULT_INIT_OVERRIDE_KEY, null);

 6     if (override == null || "false".equalsIgnoreCase(override)) {

 7         String configurationOptionStr = OptionConverter.getSystemProperty(

 8                 DEFAULT_CONFIGURATION_KEY, null);

 9         String configuratorClassName = OptionConverter.getSystemProperty(

                 CONFIGURATOR_CLASS_KEY, null);

         URL url = null;

         if (configurationOptionStr == null) {

             url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);

             if (url == null) {

                 url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);

             }

         } 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,

                         LogManager.getLoggerRepository());

             } catch (NoClassDefFoundError e) {

                 LogLog.warn("Error during default initialization", e);

             }

         } else {

             LogLog.debug("Could not find resource: ["

                     + configurationOptionStr + "].");

         }

     } else {

         LogLog.debug("Default initialization of overridden by "

                 + DEFAULT_INIT_OVERRIDE_KEY + "property.");

     }

 }

Log4j源码解析--LoggerRepository和Configurator解析的更多相关文章

  1. log4j源码解析-文件解析

    承接前文log4j源码解析,前文主要介绍了log4j的文件加载方式以及Logger对象创建.本文将在此基础上具体看下log4j是如何解析文件并输出我们所常见的日志格式 附例 文件的加载方式,我们就选举 ...

  2. Spring源码情操陶冶#task:scheduled-tasks解析器

    承接前文Spring源码情操陶冶#task:executor解析器,在前文基础上解析我们常用的spring中的定时任务的节点配置.备注:此文建立在spring的4.2.3.RELEASE版本 附例 S ...

  3. springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)

    在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...

  4. 曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  5. 曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  6. # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  7. 曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  8. Spring源码分析之AOP从解析到调用

    正文: 在上一篇,我们对IOC核心部分流程已经分析完毕,相信小伙伴们有所收获,从这一篇开始,我们将会踏上新的旅程,即Spring的另一核心:AOP! 首先,为了让大家能更有效的理解AOP,先带大家过一 ...

  9. Spring源码-IOC部分-Xml Bean解析注册过程【3】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

随机推荐

  1. JDK1.8中HashMap实现

    JDK1.8中的HashMap实现跟JDK1.7中的实现有很大差别.下面分析JDK1.8中的实现,主要看put和get方法. 构造方法的时候并没有初始化,而是在第一次put的时候初始化 putVal方 ...

  2. Windows下phpStudy配置独立站点详细步骤

    本文讲如何在phpStudy下配置 域名->站点 步骤. 开始之前,我们先添加几个本地域名(host文件),如果有域名映射到主机此步可以跳过,直接看后面的phpStudy配置部分. 首先打开ho ...

  3. Box布局

    import sys from PyQt4 import QtCore, QtGui class MainWindow(QtGui.QWidget): def __init__(self, paren ...

  4. python3之模块collections

    1.计数器(counter) counter是对字典的方法,用来追踪值的出现次数:具备字典的所有功能和自己的功能. >>> from collections import Count ...

  5. VSCode好用的Python插件及配置

    MS Python插件. 这是微软官方的Python插件,已经自带很多功能.下面是插件功能描述,其中部分内容我做了翻译. a)        Linting (Prospector, Pylint,  ...

  6. 从 0 到 1,Java Web 网站架构搭建的技术演进

    初始搭建 最开始,就是各种框架一搭,然后扔到 Tomcat 容器中跑,这时候我们的文件.数据库.应用都在一个服务器上. 服务分离 随着系统的上线,用户量也会逐步上升,很快一台服务器已经满足不了系统的负 ...

  7. Linux 内核死锁

    死锁是指多个进程(线程)因为长久等待已被其他进程占有的的资源而陷入阻塞的一种状态.当等待的资源一直得不到释放,死锁会一直持续下去.死锁一旦发生,程序本身是解决不了的,只能依靠外部力量使得程序恢复运行, ...

  8. shiro真正项目中的实战应用核心代码!!!

    欢迎转载!!!请注明出处!!! 说道shiro的学习之路真是相当坎坷,网上好多人发的帖子全是简单的demo样例,核心代码根本没有,在学习过程中遇到过N多坑. 经过自己的努力,终于整出来了,等你整明白之 ...

  9. 使用c语言实现linux数据库的操作

    前言:上一篇讲解了linux下使用命令行操作数据库,这篇继续讲解怎么使用c语言实现linux数据库的操作. 使用c语言实现环境搭建:既然我们要使用c语言实现linux数据库操作,那么首先我们得先把数据 ...

  10. JavaScript字符串转换成数字的三种方法

    在js读取文本框或者其它表单数据的时候获得的值是字符串类型的,例如两个文本框a和b,如果获得a的value值为11,b的value值为9 ,那么a.value要小于b.value,因为他们都是字符串形 ...