Tomcat源码阅读(三)Catalina.createStartDegester
来源:互联网 发布:淘宝店招如何全屏 编辑:程序博客网 时间:2024/06/15 06:49
在研究Catalina之前,首先转一下createStartDegester的解析说明,这是对Tomcat配置文件server.xml的解析并初始化到Tomcat中。本来想自己研究一番,再写阅读心得上来。但发现这个解析过程也是比较复杂,涉及东西挺多的,然后搜了一下其他研究Tomcat源码大神们的心得,发现这篇文章的确不错。详尽介绍了解析xml的过程,值得转载一番。感谢holly2k这位大神的杰作。
tomcat解析(八)Catalina.createStartDigester
在tomcat解析(四)中我们讲到了Catalina的load及start方法启动及准备整个tomcat服务器,而这两个方法最终又将该任务交由server的initialize及start方法处理,该变更将引用Server类的实例,但初始化时为空,因此我们需要该对象实例化过程,而该过程尽在Catalina.load方法的第三步骤里(可看tomcat解析四).首先我们需要了解一下其中的createStartDigester方法,该方法内容如下:
- /**
- * Create and configure the Digester we will be using for startup.
- */
- protected Digester createStartDigester() {
- long t1=System.currentTimeMillis();
- // Initialize the digester
- Digester digester = new Digester();
- digester.setValidating(false);
- digester.setRulesValidation(true);
- HashMap<Class, List<String>> fakeAttributes = new HashMap<Class, List<String>>();
- ArrayList<String> attrs = new ArrayList<String>();
- attrs.add("className");
- fakeAttributes.put(Object.class, attrs);
- digester.setFakeAttributes(fakeAttributes);
- digester.setClassLoader(StandardServer.class.getClassLoader());
- // Configure the actions we will be using
- digester.addObjectCreate("Server",
- "org.apache.catalina.core.StandardServer",
- "className");
- digester.addSetProperties("Server");
- digester.addSetNext("Server",
- "setServer",
- "org.apache.catalina.Server");
- digester.addObjectCreate("Server/GlobalNamingResources",
- "org.apache.catalina.deploy.NamingResources");
- digester.addSetProperties("Server/GlobalNamingResources");
- digester.addSetNext("Server/GlobalNamingResources",
- "setGlobalNamingResources",
- "org.apache.catalina.deploy.NamingResources");
- digester.addObjectCreate("Server/Listener",
- null, // MUST be specified in the element
- "className");
- digester.addSetProperties("Server/Listener");
- digester.addSetNext("Server/Listener",
- "addLifecycleListener",
- "org.apache.catalina.LifecycleListener");
- digester.addObjectCreate("Server/Service",
- "org.apache.catalina.core.StandardService",
- "className");
- digester.addSetProperties("Server/Service");
- digester.addSetNext("Server/Service",
- "addService",
- "org.apache.catalina.Service");
- digester.addObjectCreate("Server/Service/Listener",
- null, // MUST be specified in the element
- "className");
- digester.addSetProperties("Server/Service/Listener");
- digester.addSetNext("Server/Service/Listener",
- "addLifecycleListener",
- "org.apache.catalina.LifecycleListener");
- //Executor
- digester.addObjectCreate("Server/Service/Executor",
- "org.apache.catalina.core.StandardThreadExecutor",
- "className");
- digester.addSetProperties("Server/Service/Executor");
- digester.addSetNext("Server/Service/Executor",
- "addExecutor",
- "org.apache.catalina.Executor");
- digester.addRule("Server/Service/Connector",
- new ConnectorCreateRule());
- digester.addRule("Server/Service/Connector",
- new SetAllPropertiesRule(new String[]{"executor"}));
- digester.addSetNext("Server/Service/Connector",
- "addConnector",
- "org.apache.catalina.connector.Connector");
- digester.addObjectCreate("Server/Service/Connector/Listener",
- null, // MUST be specified in the element
- "className");
- digester.addSetProperties("Server/Service/Connector/Listener");
- digester.addSetNext("Server/Service/Connector/Listener",
- "addLifecycleListener",
- "org.apache.catalina.LifecycleListener");
- // Add RuleSets for nested elements
- digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
- digester.addRuleSet(new EngineRuleSet("Server/Service/"));
- digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
- digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
- digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Host/Cluster/"));
- digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
- // When the 'engine' is found, set the parentClassLoader.
- digester.addRule("Server/Service/Engine",
- new SetParentClassLoaderRule(parentClassLoader));
- digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Cluster/"));
- long t2=System.currentTimeMillis();
- if (log.isDebugEnabled())
- log.debug("Digester for server.xml created " + ( t2-t1 ));
- return (digester);
- }
先大概讲一下该方法的内容:
1.先实例化一个Digester类的对象,调用其setValidating、setClassLoader等方法设置其某些属性,这里不是本文的重点,因此不准备细讲。
2.调用了其addObjectCreate、addSetProperties、addSetNext、addRule及addRuleSet等方法
还记得在之前我们讲到Digester类的startElement等方法时,有一点讲到该类根据标签路径名获取到一年Rule类的List后,分别调用其begin、body及end方法,那个Rule List从何而来呢,答案即将揭晓,我们这里将通过详细讲解Digester的addObjectCreate等方法来向你揭示这些Ruler的意义。因为下面这些方法均调用多次,这里将只举例说明其作用。
如:
- digester.addObjectCreate("Server",
- "org.apache.catalina.core.StandardServer",
- "className");
方法内容如下:
- /**
- * Add an "object create" rule for the specified parameters.
- *
- * @param pattern Element matching pattern
- * @param className Default Java class name to be created
- * @param attr<mce:script type="text/javascript" src="http://hi.images.csdn.net/js/blog/tiny_mce/themes/advanced/langs/zh.js" mce_src="http://hi.images.csdn.net/js/blog/tiny_mce/themes/advanced/langs/zh.js"></mce:script><mce:script type="text/javascript" src="http://hi.images.csdn.net/js/blog/tiny_mce/plugins/syntaxhl/langs/zh.js" mce_src="http://hi.images.csdn.net/js/blog/tiny_mce/plugins/syntaxhl/langs/zh.js"></mce:script>ibuteName Attribute name that optionally overrides
- * the default Java class name to be created
- * @see ObjectCreateRule
- */
- public void addObjectCreate(String pattern, String className,
- String attributeName) {
- addRule(pattern,
- new ObjectCreateRule(className, attributeName));
- }
在这里将调用addRule方法,该方法如下:
- /**
- * <p>Register a new Rule matching the specified pattern.
- * This method sets the <code>Digester</code> property on the rule.</p>
- *
- * @param pattern Element matching pattern
- * @param rule Rule to be registered
- */
- public void addRule(String pattern, Rule rule) {
- rule.setDigester(this);
- getRules().add(pattern, rule);
- }
先看getRules(),该方法如下:
- /**
- * Return the <code>Rules</code> implementation object containing our
- * rules collection and associated matching policy. If none has been
- * established, a default implementation will be created and returned.
- */
- public Rules getRules() {
- if (this.rules == null) {
- this.rules = new RulesBase();
- this.rules.setDigester(this);
- }
- return (this.rules);
- }
我们可以看到该方法用于实例化一个RuleBase类对象,并且可以了解该方法以后每一次调用都只返回第一次生成的对象,因此是一个单例对象,在getRule方法返回后又调用了add方法,此时调用的将是RuleBase.add方法,该方法如下
- /**
- * Register a new Rule instance matching the specified pattern.
- *
- * @param pattern Nesting pattern to be matched for this Rule
- * @param rule Rule instance to be registered
- */
- public void add(String pattern, Rule rule) {
- // to help users who accidently add '/' to the end of their patterns
- int patternLength = pattern.length();
- if (patternLength>1 && pattern.endsWith("/")) {
- pattern = pattern.substring(0, patternLength-1);
- }
- List list = (List) cache.get(pattern);
- if (list == null) {
- list = new ArrayList();
- cache.put(pattern, list);
- }
- list.add(rule);
- rules.add(rule);
- if (this.digester != null) {
- rule.setDigester(this.digester);
- }
- if (this.namespaceURI != null) {
- rule.setNamespaceURI(this.namespaceURI);
- }
- }
RuleBase类里挂有一个变量名为cache的HashMap,该Map将以pattern为key,各种继承Rule类对象组成的List为value.我们再看一下在Digester.startElement里有如下语句:
List rules = getRules().match(namespaceURI, match);
如上边的解释,getRule方法将返回单例的RuleBase对象,然后以标签路径名调用其match方法,该方法内容如下:
- /**
- * Return a List of all registered Rule instances that match the specified
- * nesting pattern, or a zero-length List if there are no matches. If more
- * than one Rule instance matches, they <strong>must</strong> be returned
- * in the order originally registered through the <code>add()</code>
- * method.
- *
- * @param namespaceURI Namespace URI for which to select matching rules,
- * or <code>null</code> to match regardless of namespace URI
- * @param pattern Nesting pattern to be matched
- */
- public List match(String namespaceURI, String pattern) {
- // List rulesList = (List) this.cache.get(pattern);
- List rulesList = lookup(namespaceURI, pattern);
- if ((rulesList == null) || (rulesList.size() < 1)) {
- // Find the longest key, ie more discriminant
- String longKey = "";
- Iterator keys = this.cache.keySet().iterator();
- while (keys.hasNext()) {
- String key = (String) keys.next();
- if (key.startsWith("*/")) {
- if (pattern.equals(key.substring(2)) ||
- pattern.endsWith(key.substring(1))) {
- if (key.length() > longKey.length()) {
- // rulesList = (List) this.cache.get(key);
- rulesList = lookup(namespaceURI, key);
- longKey = key;
- }
- }
- }
- }
- }
- if (rulesList == null) {
- rulesList = new ArrayList();
- }
- return (rulesList);
- }
该方法将主要以调用lookup方法来完成获取对应Rule List的工作,lookup方法如下:
- /**
- * Return a List of Rule instances for the specified pattern that also
- * match the specified namespace URI (if any). If there are no such
- * rules, return <code>null</code>.
- *
- * @param namespaceURI Namespace URI to match, or <code>null</code> to
- * select matching rules regardless of namespace URI
- * @param pattern Pattern to be matched
- */
- protected List lookup(String namespaceURI, String pattern) {
- // Optimize when no namespace URI is specified
- List list = (List) this.cache.get(pattern);
- if (list == null) {
- return (null);
- }
- if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
- return (list);
- }
- // Select only Rules that match on the specified namespace URI
- ArrayList results = new ArrayList();
- Iterator items = list.iterator();
- while (items.hasNext()) {
- Rule item = (Rule) items.next();
- if ((namespaceURI.equals(item.getNamespaceURI())) ||
- (item.getNamespaceURI() == null)) {
- results.add(item);
- }
- }
- return (results);
- }
我们可以看到,该方法将以标签路径名pattern为key值,从cache中取出对应的Rule List,如果我们看一下Catalina.createStartDigester各方法调用时的传入的pattern值,你会发现是到server.xml一一匹配的,据此我们可大概地理解到这个框架的实现方式:
1.实例化Digester
2.以要解析的xml的结构为准,调用Digester类各add方法加入对应的Rule.
3.调用Digester.parse方法解析xml文件,则解析每一个标签时对每一个解析动作将触发对应Rule的begin、body及end方法。
尽管理解了该框架,但我们仍然需要看一下在Catalina.createStartDigester方法加入了哪些Rule来确定在解析该文件时服务器做了多少事及这些事对整个服务器的启动有何意义。下面我们先看一下其中几个比较重要的调用,如
1.addObjectCreate,有如下的调用
- digester.addObjectCreate("Server",
- "org.apache.catalina.core.StandardServer",
- "className");
所加入的Rule实现类为ObjectCreateRule,该类有begin方法如下
- /**
- * Process the beginning of this element.
- *
- * @param attributes The attribute list of this element
- */
- public void begin(Attributes attributes) throws Exception {
- // Identify the name of the class to instantiate
- String realClassName = className;
- if (attributeName != null) {
- String value = attributes.getValue(attributeName);
- if (value != null) {
- realClassName = value;
- }
- }
- if (digester.log.isDebugEnabled()) {
- digester.log.debug("[ObjectCreateRule]{" + digester.match +
- "}New " + realClassName);
- }
- // Instantiate the new object and push it on the context stack
- Class clazz = digester.getClassLoader().loadClass(realClassName);
- Object instance = clazz.newInstance();
- digester.push(instance);
- }
可以看到该方法内容为实例化一个类,类名可为实例化该类时初始化的className对象或在调用其begin方法时传入的Attribute对象(该对象用以表示标签的属性)里className对应的类名,实例化类后又调用digester.push()方法,我们在tomcat解析四中已看到过该方法,该方法会在digester.parse之前调用,参数为已实例化的Catalina对象。
该类又有end方法如下:
- /**
- * Process the end of this element.
- */
- public void end() throws Exception {
- Object top = digester.pop();
- if (digester.log.isDebugEnabled()) {
- digester.log.debug("[ObjectCreateRule]{" + digester.match +
- "} Pop " + top.getClass().getName());
- }
- }
可以看到在解析Server的开始标签时将产生一个org.apache.catalina.core.StandardServer实例,并将之放到栈中,在结束标签时又将它从栈中取,之所以放于栈中是因为后续还要对该对象进行很多处理,如下面将讲到的
2.addSetProperties
这个方法将加入的Rule实现类为SetPropertiesRule,这里先不细讲该类啦
3.addSetNext,如
该方法将添加的实现类为SetNextRule类,该类没有begin方法,因此在解析开始标签的时候没有对应的动作,但该类有end()方法,该方法是在解析结束标签的事件方法endElement()里调用的,SetNextRule类的end()方法如下:
- /**
- * Process the end of this element.
- */
- public void end() throws Exception {
- // Identify the objects to be used
- Object child = digester.peek(0);
- Object parent = digester.peek(1);
- if (digester.log.isDebugEnabled()) {
- if (parent == null) {
- digester.log.debug("[SetNextRule]{" + digester.match +
- "} Call [NULL PARENT]." +
- methodName + "(" + child + ")");
- } else {
- digester.log.debug("[SetNextRule]{" + digester.match +
- "} Call " + parent.getClass().getName() + "." +
- methodName + "(" + child + ")");
- }
- }
- // Call the specified method
- IntrospectionUtils.callMethod1(parent, methodName,
- child, paramType, digester.getClassLoader());
- }
该方法内容为:取出stack顶部的对象,再取出其前一个对象,然后调用前一个对象的方法,方法名为methodName,比如有如下的调用(事实是有的)
- digester.addSetNext("Server",
- "setServer",
- "org.apache.catalina.Server");
这段代码将在解析Server标签的时候触发,此时在栈中的对象有两个,一个是最先放入的Catalina对象,而另一个是在addSetNext调用前加入的ObjectCreateRule类所实例化的org.apache.catalina.core.StandardServer实例,因此此时调用Catalina.setServer方法,以StandardServer实例为参数。这里是一个非常巧妙的设计,通过相邻的标签来生成对应的对象,并且让上一个对象作为父对象持有另一对象,因此在加入每一个RULE的时候都必须精妙地设计加入的顺序.
4.addRule,比如:
这个直接对某一个pattern加入对应的Rule,无也讲
5.addRuleSet,如
- digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
addRuleSet代码如下:
- /**
- * Register a set of Rule instances defined in a RuleSet.
- *
- * @param ruleSet The RuleSet instance to configure from
- */
- public void addRuleSet(RuleSet ruleSet) {
- String oldNamespaceURI = getRuleNamespaceURI();
- String newNamespaceURI = ruleSet.getNamespaceURI();
- if (log.isDebugEnabled()) {
- if (newNamespaceURI == null) {
- log.debug("addRuleSet() with no namespace URI");
- } else {
- log.debug("addRuleSet() with namespace URI " + newNamespaceURI);
- }
- }
- setRuleNamespaceURI(newNamespaceURI);
- ruleSet.addRuleInstances(this);
- setRuleNamespaceURI(oldNamespaceURI);
- }
可以看到又调用了RuleSet实现类的addRuleInstances方法,以EngineRuleSet为例,其addRuleInstances方法如下:
- /**
- * <p>Add the set of Rule instances defined in this RuleSet to the
- * specified <code>Digester</code> instance, associating them with
- * our namespace URI (if any). This method should only be called
- * by a Digester instance.</p>
- *
- * @param digester Digester instance to which the new Rule instances
- * should be added.
- */
- public void addRuleInstances(Digester digester) {
- digester.addObjectCreate(prefix + "Ejb",
- "org.apache.catalina.deploy.ContextEjb");
- digester.addRule(prefix + "Ejb", new SetAllPropertiesRule());
- digester.addRule(prefix + "Ejb",
- new SetNextNamingRule("addEjb",
- "org.apache.catalina.deploy.ContextEjb"));
- digester.addObjectCreate(prefix + "Environment",
- "org.apache.catalina.deploy.ContextEnvironment");
- digester.addSetProperties(prefix + "Environment");
- digester.addRule(prefix + "Environment",
- new SetNextNamingRule("addEnvironment",
- "org.apache.catalina.deploy.ContextEnvironment"));
- digester.addObjectCreate(prefix + "LocalEjb",
- "org.apache.catalina.deploy.ContextLocalEjb");
- digester.addRule(prefix + "LocalEjb", new SetAllPropertiesRule());
- digester.addRule(prefix + "LocalEjb",
- new SetNextNamingRule("addLocalEjb",
- "org.apache.catalina.deploy.ContextLocalEjb"));
- digester.addObjectCreate(prefix + "Resource",
- "org.apache.catalina.deploy.ContextResource");
- digester.addRule(prefix + "Resource", new SetAllPropertiesRule());
- digester.addRule(prefix + "Resource",
- new SetNextNamingRule("addResource",
- "org.apache.catalina.deploy.ContextResource"));
- digester.addObjectCreate(prefix + "ResourceEnvRef",
- "org.apache.catalina.deploy.ContextResourceEnvRef");
- digester.addRule(prefix + "ResourceEnvRef", new SetAllPropertiesRule());
- digester.addRule(prefix + "ResourceEnvRef",
- new SetNextNamingRule("addResourceEnvRef",
- "org.apache.catalina.deploy.ContextResourceEnvRef"));
- digester.addObjectCreate(prefix + "ServiceRef",
- "org.apache.catalina.deploy.ContextService");
- digester.addRule(prefix + "ServiceRef", new SetAllPropertiesRule());
- digester.addRule(prefix + "ServiceRef",
- new SetNextNamingRule("addService",
- "org.apache.catalina.deploy.ContextService"));
- digester.addObjectCreate(prefix + "Transaction",
- "org.apache.catalina.deploy.ContextTransaction");
- digester.addRule(prefix + "Transaction", new SetAllPropertiesRule());
- digester.addRule(prefix + "Transaction",
- new SetNextNamingRule("setTransaction",
- "org.apache.catalina.deploy.ContextTransaction"));
- }
可以得知addRuleSet方法主要是用于为某一想同前辍的标签加入一批的Rule实现类
我们没有篇幅来说明tomcat做的每一件事,因此这里我将影响到后续服务器启动的一些Rule拿出来讲一下。
1.对<Server>标签
- // Configure the actions we will be using
- digester.addObjectCreate("Server",
- "org.apache.catalina.core.StandardServer",
- "className");
- digester.addSetProperties("Server");
- digester.addSetNext("Server",
- "setServer",
- "org.apache.catalina.Server");
实例化了一个org.apache.catalina.core.StandardServer,并以该对象为参数调用Catalina类的setServer方法,在这里我们可以回想一下在tomcat解析四里我们说的server变量所引用的对象是如何得到的呢?该对象有一个设置方法,而该方法即是setServer,因此后续整个服务器的启动重任将落于刚刚新生的org.apache.catalina.core.StandardServer对象
2.对Service标签
- digester.addObjectCreate("Server/Service",
- "org.apache.catalina.core.StandardService",
- "className");
- digester.addSetProperties("Server/Service");
- digester.addSetNext("Server/Service",
- "addService",
- "org.apache.catalina.Service");
实例化了一个org.apache.catalina.core.StandardService对象,放入栈里,并以自身为参数调用栈里上一个对象的addService方法,通过server.xml我们可以了解到<service>标签是位于<server>标签之内的,因此此时StandardService的上一个对象为StandardServer对象(该对象要到结束标签时才会被取出来,这又是一个很巧妙的设计,让xml文件里有上下级关系的两个对象实例化后又存在上下级关系,或者是互持有的关系)
3.对Engine标签有:
- digester.addRuleSet(new EngineRuleSet("Server/Service/"));
将会加下以下动作:
- /**
- * <p>Add the set of Rule instances defined in this RuleSet to the
- * specified <code>Digester</code> instance, associating them with
- * our namespace URI (if any). This method should only be called
- * by a Digester instance.</p>
- *
- * @param digester Digester instance to which the new Rule instances
- * should be added.
- */
- public void addRuleInstances(Digester digester) {
- digester.addObjectCreate(prefix + "Engine",
- "org.apache.catalina.core.StandardEngine",
- "className");
- digester.addSetProperties(prefix + "Engine");
- digester.addRule(prefix + "Engine",
- new LifecycleListenerRule
- ("org.apache.catalina.startup.EngineConfig",
- "engineConfigClass"));
- digester.addSetNext(prefix + "Engine",
- "setContainer",
- "org.apache.catalina.Container");
- //Cluster configuration start
- digester.addObjectCreate(prefix + "Engine/Cluster",
- null, // MUST be specified in the element
- "className");
- digester.addSetProperties(prefix + "Engine/Cluster");
- digester.addSetNext(prefix + "Engine/Cluster",
- "setCluster",
- "org.apache.catalina.Cluster");
- //Cluster configuration end
- digester.addObjectCreate(prefix + "Engine/Listener",
- null, // MUST be specified in the element
- "className");
- digester.addSetProperties(prefix + "Engine/Listener");
- digester.addSetNext(prefix + "Engine/Listener",
- "addLifecycleListener",
- "org.apache.catalina.LifecycleListener");
- digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));
- digester.addObjectCreate(prefix + "Engine/Valve",
- null, // MUST be specified in the element
- "className");
- digester.addSetProperties(prefix + "Engine/Valve");
- digester.addSetNext(prefix + "Engine/Valve",
- "addValve",
- "org.apache.catalina.Valve");
- }
内容有:实例化org.apache.catalina.core.StandardEngine对象,实例化org.apache.catalina.startup.EngineConfig对象,以该对象为参数,调用StandardEngine的addLifecycleListener方法,以StandardEngine对象为参数,调用StandardService.setContainer方法
4.对Host标签
内容有:内容有:实例化org.apache.catalina.core.StandardHost对象,实例化org.apache.catalina.startup.HostConfig对象,以该对象为参数,调用StandardHost的addLifecycleListener方法,以StandardHost对象为参数,调用StandardEngine.addChild方法
5.对Context标签
内容有:内容有:实例化org.apache.catalina.core.StandardContext对象,实例化org.apache.catalina.startup.ContextConfig对象,以该对象为参数,调用StandardContext的addLifecycleListener方法,以StandardContext对象为参数,调用StandardHost.addChild方法
方法总结:自上而下地实例了一些tomcat启动及处理客户请求的处理类,其中有StandardHost(表示着一个http访问的主机)、HostConfig、StandardContext(java web中的虚拟目录)及ContextConfig等,我们后续将会介绍各个主要的启动类
转载:http://blog.csdn.net/holly2k/article/details/5258849
- Tomcat源码阅读(三)Catalina.createStartDegester
- Tomcat源码阅读(三)Catalina
- Tomcat 源码阅读(三)Catalina.start
- Tomcat 源码阅读(二)Catalina.load
- Tomcat源码阅读系列(五)Catalina容器
- tomcat源码阅读(二)——ClassLoader及catalina启动
- tomcat源码阅读步骤三
- Tomcat生命周期(Tomcat源码阅读系列之三)
- tomcat源码Catalina
- tomcat源码阅读(三)——ClassLoader背景知识
- Tomcat源码阅读系列(三)启动和关闭过程
- 源码阅读(三)
- Tomcat源码阅读三:过滤器实现
- Tomcat源码阅读之Server.xml文件的处理与Catalina启动流程
- Tomcat源码阅读之Server.xml文件的处理与Catalina启动流程
- WINVNC源码阅读(三)
- Spring源码阅读(三)
- mybatis源码阅读(三)
- linux shell教程(一)
- 八、通过中缀计算表达式转换成后缀计算表达式
- A除以B
- Android应用开发中如何使用隐藏的API
- mysql 怎么把查询结果作为表名继续查询
- Tomcat源码阅读(三)Catalina.createStartDegester
- HDR和bloom效果的区别和关系
- EditText属性
- HDMI 相关资料
- VFW基础细节
- Angular + Require 结合示例
- Split正则表达式的应用
- 关闭VHDL的assertion log打印
- Linux下Nagios的安装与配置-已验证