【0】README
0.1)本文部分文字描述转自 “how tomcat works”,旨在学习 “tomcat(15)Digester库” 的基础知识;
2)problem+solution:
2.1)problem:如下面的代码,Bootstrap类实例化连接器,servlet容器,Wrapper容器和其它组件,如设置连接器的关联顶层容器,通过set方法将它们联系起来;如监听器组件通过addLifecycleListener来设置等等。这种配置应用程序的方法有一个明显的缺点:即所有的配置都必须硬编码。调整组件配置或属性值都必须要重新编译Bootstrap类。
2.2)solution:Tomcat使用了一种更加优雅的配置方式,即使用一个名为server.xml 的XML 文档来对应用程序进行配置。server.xml文件中的每个元素都会转换为一个java 对象,元素的属性会用于设置java对象 的属性。这样,就可以通过简单地编辑 server.xml文件来修改tomcat的配置了;
看个荔枝)如server.xml文件中的 Context元素表示一个Context实例:<context/>;若要为 Context实例设置path属性和 docBase属性,使用这样的配置:
<context docBase="myApp" path="/myApp" />
Attention)
A1)tomcat使用了开源库Digester来将XML 文档中的元素转换成 java 对象;(干货——开源库Digester的作用)
A2)用来配置web 应用程序的XML 文件的名称是 web.xml,该文件位于web 应用程序的WEB-INF 目录下;(干货——引入了大家熟悉的web.xml)
public final class Bootstrap1 {public static void main(String[] args) {//invoke: http://localhost:8080/app1/Primitive or http://localhost:8080/app1/ModernSystem.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector();Wrapper wrapper1 = new StandardWrapper();wrapper1.setName("Primitive");//wrapper1.setServletClass("servlet.PrimitiveServlet");wrapper1.setServletClass("PrimitiveServlet");Wrapper wrapper2 = new StandardWrapper();wrapper2.setName("Modern");//wrapper2.setServletClass("servlet.ModernServlet");wrapper2.setServletClass("ModernServlet");Context context = new StandardContext();// StandardContext's start method adds a default mappercontext.setPath("/app1");context.setDocBase("app1");context.addChild(wrapper1);context.addChild(wrapper2);LifecycleListener listener = new SimpleContextConfig();((Lifecycle) context).addLifecycleListener(listener);Host host = new StandardHost();host.addChild(context);host.setName("localhost");host.setAppBase("webapps");Loader loader = new WebappLoader();context.setLoader(loader);// context.addServletMapping(pattern, name);context.addServletMapping("/Primitive", "Primitive");context.addServletMapping("/Modern", "Modern");connector.setContainer(host);try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) host).start(); // 与以往的Bootstrap.java不同的是,这里是host.start() 而不是 context.start()// make the application wait until we press a key.System.in.read();((Lifecycle) host).stop();}catch (Exception e) {e.printStackTrace();}}
}
【1】Digester库
1)intro:Digester是 Apache 下Jakarta项目下的子项目Commons项目下的一个开源项目;
2)Digester API包含3个包:三者都被打包到 commons-digester.jar 文件中;(package list)
package1)org.apache.commons.digester:该包提供了基于规则的,可处理任意XML 文档的类;
package2)org.apache.commons.digester.rss:该包包含一些可以用来解析与很多新闻源使用的RSS(Rich Site Summary,富站点摘要)格式兼容的XML文档的例子;
package3)org.apache.commons.digester.xmlrules:该包为 Digester库提供了一些基于XML规则的定义;
【1.1】Digester类
1)intro:Digester类可用于解析XML 文档;对于XMl 文档中的每个元素,Digester对象都会检查它是否要做事先预定义的事件。在调用Digester.parse()方法之前,需要先定义好Digester对象执行哪些动作;
2)如何定义在Digester对象遇到某个XMl 元素时它应该执行什么动作呢?—— 程序员先定义好模式,然后将每个模式与一条或多条规则相关联。XML 文档中根元素的模式与元素的名字相同。(干货——引入了模式,且XML 文档中根元素的模式与元素的名字相同
3)看个荔枝:考虑下面的 XML文档:(example.xml)
<?xml version=1.0" encoding="ISO-8859-1">
<employee firstName="pacoson" lastName="xiao"><office><address streetName="Wellington Street" streetNumber="110" /></office>
</employee>
对上述代码的分析(Analysis):
A1)该XML文档中的根元素是 employee,employee元素有一个模式, 名为 employee;
A2)office元素是 employee元素的子元素,子元素的模式是由该元素的父元素的模式再加上 “/” 符号,以及该元素名称拼接而成的,所以office元素的模式是  employee/office;(干货——我们这就了解了如何从XML 文档中推导出元素的模式)
4)下面讨论一下规则(rules):(干货——规则的定义,非常重要)
rule1)一条规则指明了当Digester 对象遇到了某个特殊的模式时要执行的一个或多个动作;规则是 org.apache.commons.digester.Rule 类;Digester类可以包含0个或多个对象;
rule2)Rule类有begin()方法 和 end() 方法。在开始标签调用start()方法,结束标签调用 end() 方法;
5)自定义自己的规则:包括创建对象和设置属性值等的规则;(干货——自定义规则包括创建对象+设置属性+调用方法+创建对象间的关系+验证 XML 文档)
5.1)创建对象:若想要Digester对象在遇到某个特殊字符时创建对象,则需要调用其 addObjectCreate()方法,该方法有4个重载版本;(干货——引入addObjectCreate()方法)
public void addObjectCreate(String pattern, String className) {addRule(pattern,  new ObjectCreateRule(className));
}
public void addObjectCreate(String pattern, Class clazz) {addRule(pattern, new ObjectCreateRule(clazz));
}
public void addObjectCreate(String pattern, String className,  String attributeName) { addRule(pattern,  new ObjectCreateRule(className, attributeName));
}
public void addObjectCreate(String pattern,  String attributeName, Class clazz) { addRule(pattern,  new ObjectCreateRule(attributeName, clazz));
}
对上述代码的分析(Analysis):
A1)需要传入一个模式和一个Class对象或类名来调用该方法;

看个荔枝) 如我们想让Digester对象在遇到模式employee 时,创建一个 mydiy.Employee 对象,则使用下面的代码来调用 addObjectCreate()方法:

digester.addObjectCreate("employee", "mydiy.Employee.class");
或者
digester.addObjectCreate("employee", "mydiy.Employee");

A2)addObjecdtCreate()方法的最后两个重载版本允许在xml 文档中定义类的名字,而无须将其作为参数传入,这使得类名可以在运行时决定;在上述最后两个重载方法中,参数 attributeName参数指明了 XML 元素的属性的名字,该属性包含了将要实例化的类的名字;
看个荔枝)
step1)添加创建对象的一条规则:digester.addObjectCreate("employee",null,"className");(属性名是 className);
step2)传入XML 元素中的类名: <employee firstName="pacoson" lastName="xiao" className="mydiy.employee">;如果employee元素包含 className属性,那么该属性指定的值会用来作为待实例化的类的名字,如果没有包含 className属性,则会使用默认的类名;(干货——显然意思是说Emplyee类需要依赖 名为className的类对象)
Attention)addObjectCreate()方法创建的对象会被压入到一个内部栈中;
5.2)设置属性:addSetProperties()方法,该方法可以使用Digester对象为创建的对象设置属性。该方法的重载版本有:(干货——引入addSetProperties()方法)
 public void addSetProperties(String pattern) {addRule(pattern,  new SetPropertiesRule()); }public void addSetProperties( String pattern, String attributeName, String propertyName) { addRule(pattern, new SetPropertiesRule(attributeName, propertyName)); }public void addSetProperties(String pattern, String [] attributeNames, String [] propertyNames) { addRule(pattern,  new SetPropertiesRule(attributeNames, propertyNames)); }
看个荔枝)考虑下面的代码:
digester.addObjectCreate("employee", "mydiy.Employee");
digester.addSetProperties("employee");
对以上代码的分析(Analysis):
A1)上面的Digester有两个Rule 对象,分别用来创建对象和设置属性,他们都是通过employee模式触发的。而Rule对象按照其添加到Digester实例中的顺序逐个执行。
A2)对于下面XMl 文档中的employee 元素(该元素匹配 employee模式): <employee firstName="pacoson", lastName="Xiao">;依据Digester的第一条rule,会创建 diy.Employee类的一个实例,依据第二条Rule,调用已经实例化的Employee.setFirstName() and Employee.setLastName(),分别传入pacoson 和 Xiao 来设置属性;
5.3)调用方法:Digetser类允许通过添加一条Rule,使Digester 在遇到与该规则相关联的模式时调用内部栈最顶端对象的 某个方法。这需要用到 addCallMethod()方法,重载版本如下:(干货——引入addCallMethod()方法)
public void addCallMethod(String pattern, String methodName) {addRule( pattern,  new CallMethodRule(methodName)); }public void addCallMethod(String pattern, String methodName, int paramCount) { addRule(pattern, new CallMethodRule(methodName, paramCount)); }public void addCallMethod(String pattern, String methodName, int paramCount, String paramTypes[]) { addRule(pattern, new CallMethodRule( methodName,paramCount,paramTypes));}public void addCallMethod(String pattern, String methodName,int paramCount, Class paramTypes[]) {addRule(pattern,new CallMethodRule(methodName,paramCount,paramTypes));}
5.4)创建对象之间的关系(干货——Digester实例有一个内部栈,用于临时存储创建的对象)(干货——引入addSetNext()方法)
5.4.1)addSetNext()方法:若栈中有两个对象,那么该方法会调用第1个对象的指定方法并将第2个对象作为参数传入该方法来创建第1个对象和第2个对象的关系;
public void addSetNext(String pattern, String methodName) {addRule(pattern,new SetNextRule(methodName));}public void addSetNext(String pattern, String methodName,String paramType) {addRule(pattern,new SetNextRule(methodName, paramType));}

对以上代码的分析(Analysis):参数pattern 指明了触发该规则的具体模式,参数methodName 是将要调用的第1个对象的方法的名称。模式是如下格式:firstObject/secondObject;
看个荔枝)如何创建对象间的关系:
step1)创建两个对象;
digester.addObjectCreate("employee", "mydiy.Employee");
digester.addObjectCreate("employee/office", "mydiy.Office");

step2)创建对象间的关系:需要另外定义一条规则,使用 addSetNext()方法来建立关系(调用addOffice()方法建立关系):
digetster.addSetNext("employee/office", 'addOffice');

5.5)验证XML文档:Digester.validating属性指明了是否要对 XML 文档进行有效性验证。默认case下,其为false;setValidating()方法可以设置其值;(干货——引入setValidating()方法)
【1.2】Digester库荔枝1(如何使用 Digester库动态地创建对象,并设置相应的属性)
1)源代码
public class Test01 {public static void main(String[] args) {String path = System.getProperty("user.dir") + File.separator + "src";File file = new File(path, "employee1.xml");Digester digester = new Digester();// add rules (为模式 employee 添加3条规则)digester.addObjectCreate("employee","com.tomcat.chapter15.digestertest.Employee");digester.addSetProperties("employee");digester.addCallMethod("employee", "printName");try {Employee employee = (Employee) digester.parse(file);System.out.println("First name : " + employee.getFirstName());System.out.println("Last name : " + employee.getLastName());} catch (Exception e) {e.printStackTrace();}}
}
<?xml version="1.0" encoding="ISO-8859-1"?>
<employee firstName="Brian" lastName="May">
</employee>
2)console info
Creating Employee
Setting firstName : Brian
Setting lastName : May
My name is Brian May
First name : Brian
Last name : May
3)info analysis:当调用parse()方法时,它会打开指定的xml 文档,开始解析它;(干货——当调用parse()方法时,它会打开指定的xml 文档,开始解析它,只需要parse方法就可以创建根元素(模式)的相应对象和其关联对象)
step1)Digester类查看 employee 元素的开始标签,这会触发与 employee模式关联的3条规则,按照其被添加到 Digester 对象中的顺序逐个执行;
step2)第一条规则用于创建Employee对象,调用构造函数,打印Creating Employee;
step3)第二条规则设置 Employee对象的属性,在employee 元素中包含两个属性:分别是 firstName 和 lastName, 这会调用调用的set方法,打印 Setting firstName : Brian  Setting lastName : May;
step4第三条规则调用 Employee.printName()方法,打印M y name is Brian May;
【1.3】Digester库荔枝2(如何创建两个对象,并建立他们的关系)
1)源代码
public class Test02 {public static void main(String[] args) {String path = System.getProperty("user.dir") + File.separator + "src";File file = new File(path, "employee2.xml");Digester digester = new Digester();// add rules,添加规则(key)digester.addObjectCreate("employee","com.tomcat.chapter15.digestertest.Employee");digester.addSetProperties("employee");digester.addObjectCreate("employee/office","com.tomcat.chapter15.digestertest.Office");digester.addSetProperties("employee/office");digester.addSetNext("employee/office", "addOffice");digester.addObjectCreate("employee/office/address","com.tomcat.chapter15.digestertest.Address");digester.addSetProperties("employee/office/address");digester.addSetNext("employee/office/address", "setAddress");try {Employee employee = (Employee) digester.parse(file);ArrayList offices = employee.getOffices();Iterator iterator = offices.iterator();System.out.println("-------------------------------------------------");while (iterator.hasNext()) {Office office = (Office) iterator.next();Address address = office.getAddress();System.out.println(office.getDescription());System.out.println("Address : " + address.getStreetNumber()+ " " + address.getStreetName());System.out.println("--------------------------------");}} catch (Exception e) {e.printStackTrace();}}
}
<?xml version="1.0" encoding="ISO-8859-1"?>
<employee firstName="Freddie" lastName="Mercury"><office description="Headquarters"><address streetName="Wellington Avenue" streetNumber="223"/></office><office description="Client site"><address streetName="Downing Street" streetNumber="10"/></office>
</employee>
2)console info
Creating Employee
Setting firstName : Freddie
Setting lastName : Mercury
..Creating Office
..Setting office description : Headquarters
....Creating Address
....Setting streetName : Wellington Avenue
....Setting streetNumber : 223
..Setting office address : ....223 Wellington Avenue
Adding Office to this employee
..Creating Office
..Setting office description : Client site
....Creating Address
....Setting streetName : Downing Street
....Setting streetNumber : 10
..Setting office address : ....10 Downing Street
Adding Office to this employee
-------------------------------------------------
Headquarters
Address : 223 Wellington Avenue
--------------------------------
Client site
Address : 10 Downing Street
--------------------------------
Attention)本文不对 荔枝2的实例程序进行分析了,结合荔枝1的分析,理解这个不难;
【1.4】org.apache.commons.digester.Rule类(最重要的方法start() + end())
1)intro to begin() :当Digester实例 遇到某个XML 元素的开始标签时,会调用它所包含的匹配Rule 对象的begin()方法: 
public void begin(Attributes attributes) throws Exception {;  // The default implementation does nothing}  public void begin(String namespace, String name, Attributes attributes)throws Exception {begin(attributes);}
2)intro to end():当Digester实例 遇到某个XML 元素的结束标签时,会调用它所包含的匹配Rule 对象的end()方法:
public void end() throws Exception {;  // The default implementation does nothing}  public void end(String namespace, String name)throws Exception {end();}
3)Digester对象是如何完成这些工作的? 当调用Digester.addObjectCreate()方法,addCallMethod()方法,addSetNext()方法或其他方法时,都会间接地调用 Digester.addRule()方法;
 public void addRule(String pattern, Rule rule) {rule.setDigester(this);getRules().add(pattern, rule);}

4)再次review Digester.addObjectCreate()方法的重载version:

public void addObjectCreate(String pattern, String className) {addRule(pattern,  new ObjectCreateRule(className));
}
public void addObjectCreate(String pattern, Class clazz) {addRule(pattern, new ObjectCreateRule(clazz));
}
public void addObjectCreate(String pattern, String className,  String attributeName) { addRule(pattern,  new ObjectCreateRule(className, attributeName));
}
public void addObjectCreate(String pattern,  String attributeName, Class clazz) { addRule(pattern,  new ObjectCreateRule(attributeName, clazz));
}

对以上代码的分析(Analysis):

A1)这4个重载方法都调用了addRule()方法,ObjectCreateRule类 是 Rule 类的子类,该类的实例都作为 addRule()方法的参数;
A2)ObjectCreateRule.start()方法 和 ObjectCreateRule.end()方法的实现如下:
 public void begin(Attributes attributes) throws Exception { //org.apache.commons.digester.ObjectCreateRule.begin().// Identify the name of the class to instantiateString 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 stackClass clazz = digester.getClassLoader().loadClass(realClassName);Object instance = clazz.newInstance();digester.push(instance); //highlight line.}public void end() throws Exception {Object top = digester.pop(); //highlight line.if (digester.log.isDebugEnabled()) {digester.log.debug("[ObjectCreateRule]{" + digester.match +"} Pop " + top.getClass().getName());}}

对以上代码的分析(Analysis):begin()方法的最后三行会创建Digester对象的一个实例,并将其压入到 Digester对象的内部栈中。end()方法 会将内部站的栈顶元素弹出;
【1.5】Digester库荔枝3:使用RuleSet(org.apache.commons.digester.RuleSet)
1)要向Digester实例添加 Rule对象,还可以调用其 addRuleSet()方法;
2)Rule对象集合是 org.apache.commons.digester.RuleSet接口的实例,该接口定义了两个方法,分别是 addRuleInstance()方法 和 getNamespaceURI()方法;
2.1)addRuleInstance方法:将在当前RuleSet 中的Rule对象的集合作为该方法的参数添加到 Digester实例中;
2.2)getNamespaceURI方法:返回将要应用在 RuleSet中所有Rule 对象的命名空间的URI;
3)RuleSetBase implements RuleSet。RuleSetBase 是一个抽象类,提供了getNamespaceURI的实现,你只需要提供addRuleInstances()方法的实现就可以了;
public abstract class RuleSetBase implements RuleSet {   protected String namespaceURI = null;    public String getNamespaceURI() {return (this.namespaceURI);}    public abstract void addRuleInstances(Digester digester);
}

4)测试用例

public class Test03 {public static void main(String[] args) {String path = System.getProperty("user.dir") + File.separator + "src";File file = new File(path, "employee2.xml");Digester digester = new Digester();digester.addRuleSet(new EmployeeRuleSet());try {Employee employee = (Employee) digester.parse(file);ArrayList offices = employee.getOffices();Iterator iterator = offices.iterator();System.out.println("-------------------------------------------------");while (iterator.hasNext()) {Office office = (Office) iterator.next();Address address = office.getAddress();System.out.println(office.getDescription());System.out.println("Address : " + address.getStreetNumber()+ " " + address.getStreetName());System.out.println("--------------------------------");}} catch (Exception e) {e.printStackTrace();}}
}
public class EmployeeRuleSet extends RuleSetBase {public void addRuleInstances(Digester digester) {// add rulesdigester.addObjectCreate("employee","com.tomcat.chapter15.digestertest.Employee");digester.addSetProperties("employee");digester.addObjectCreate("employee/office","com.tomcat.chapter15.digestertest.Office");digester.addSetProperties("employee/office");digester.addSetNext("employee/office", "addOffice");digester.addObjectCreate("employee/office/address","com.tomcat.chapter15.digestertest.Address");digester.addSetProperties("employee/office/address");digester.addSetNext("employee/office/address", "setAddress");}
}
Attention)参考前面做的分析,应该不难;
【2】ContextConfig类
1)在前面章节中,我们使用了 SimpleContextConfig 作为 StandardContext的监听器:其唯一用途是设置configure变量,这样StandardContext.start()方法才能继续执行;
public class SimpleContextConfig implements LifecycleListener {public void lifecycleEvent(LifecycleEvent event) {if (Lifecycle.START_EVENT.equals(event.getType())) {Context context = (Context) event.getLifecycle();context.setConfigured(true);}}
}
2)而tomcat 的标准监听器:是 org.apache.catalina.startup.ContextConfig类的实例;(干货——Tomcat标准监听器)
3)ContextConfig会执行很多对 StandardContext实例来说必不可少的任务。如,与某个 StandardContext实例关联的 ContextConfig 实例会安装一个验证器阀到 StandardContext的管道中。它还会添加一个许可阀(org.apache.catalina.valves.CertificateValve)到管道对象中;
4)更重要的是:ContextConfig类的实例还要读取和解析默认的 web.xml 文件和应用程序自定义的web.xml文件,并将xml 元素转换为 java 对象;(干货——引入了默认的和自定义的web.xml文件)
4.1)默认的web.xml:位于 CATALINA_HOME/conf 目录中,其中定义并映射了很多默认的 servlet,配置了很多 MIME类型文件的映射,定义了默认的session超时时间,以及定义了欢迎文件的列表;
4.2)应用程序的web.xml文件:位于 WEB-INF 目录中;
5)以上两个文件都不是必须的,即使这两个文件没有找到, ContextConfig 实例仍然会继续执行;
6)ContextConfig实例:会为每个 servlet元素创建一个 StandardWrapper类;(干货——ContextConfig的作用)
7)在BootStrap程序中,需要实例化一个 ContextConfig类,并调用 addLifecycleListener方法;
LifecycleListener listener = new ContextConfig();((Lifecycle) context).addLifecycleListener(listener);
7.1)在启动 StandardContext实例时,会触发以下事件(events):
event1)BEFORE_START_EVENT;
event2)START_EVENT;
event3)AFTER_START_EVENT;
7.2)当程序停止时,会触发以下事件:
event1)BEFORE_STOP_EVENT;
event2)STOP_EVENT;
event3)AFTER_STOP_EVENT;
8)ContextConfig 实例会对两种事情做出相应:分别是START_EVENT and STOP_EVENT。每次 StandardContext实例触发事件时,会调用 ContextConfig.lifecycleEvent()方法;
public void lifecycleEvent(LifecycleEvent event) { //org.apache.catalina.startup.ContextConfig.lifecycleEvent().// Identify the context we are associated withtry {context = (Context) event.getLifecycle();if (context instanceof StandardContext) {int contextDebug = ((StandardContext) context).getDebug();if (contextDebug > this.debug)this.debug = contextDebug;}} catch (ClassCastException e) {log(sm.getString("contextConfig.cce", event.getLifecycle()), e);return;}// Process the event that has occurredif (event.getType().equals(Lifecycle.START_EVENT))start(); //highlight line.else if (event.getType().equals(Lifecycle.STOP_EVENT))stop(); //highlight line.}

8.1)start()方法

(干货——start方法会调用 defaultConfig() and applicationConfig()方法,两者分别用于读取和解析默认的web.xml 和 应用程序自定义的 web.xml)
private synchronized void start() {  //org.apache.catalina.startup.ContextConfig.start().if (debug > 0)log(sm.getString("contextConfig.start"));context.setConfigured(false);ok = true;// Set properties based on DefaultContextContainer container = context.getParent();if( !context.getOverride() ) {if( container instanceof Host ) {((Host)container).importDefaultContext(context);container = container.getParent();}if( container instanceof Engine ) {((Engine)container).importDefaultContext(context);}}// Process the default and application web.xml filesdefaultConfig(); // highlight line.applicationConfig(); // highlight line.if (ok) {validateSecurityRoles();}// Scan tag library descriptor files for additional listener classesif (ok) {try {tldScan();} catch (Exception e) {log(e.getMessage(), e);ok = false;}}// Configure a certificates exposer valve, if requiredif (ok)certificatesConfig();// Configure an authenticator if we need oneif (ok)authenticatorConfig();// Dump the contents of this pipeline if requestedif ((debug >= 1) && (context instanceof ContainerBase)) {log("Pipline Configuration:");Pipeline pipeline = ((ContainerBase) context).getPipeline();Valve valves[] = null;if (pipeline != null)valves = pipeline.getValves();if (valves != null) {for (int i = 0; i < valves.length; i++) {log("  " + valves[i].getInfo());}}log("======================");}// Make our application available if no problems were encounteredif (ok)context.setConfigured(true);else {log(sm.getString("contextConfig.unavailable"));context.setConfigured(false);}}

【2.1】 defaultConfig()方法
1)intro: 该方法负责读取并解析位于 CATALINA_HOME/conf目录下的默认web.xml 文件;
 private void defaultConfig() {  //org.apache.catalina.startup.ContextConfig.defaultConfig().// Open the default web.xml file, if it existsFile file = new File(Constants.DefaultWebXml);// public static final String DefaultWebXml = "conf/web.xml";if (!file.isAbsolute())file = new File(System.getProperty("catalina.base"),Constants.DefaultWebXml);FileInputStream stream = null;try {stream = new FileInputStream(file.getCanonicalPath());stream.close();stream = null;} catch (FileNotFoundException e) {log(sm.getString("contextConfig.defaultMissing"));return;} catch (IOException e) {log(sm.getString("contextConfig.defaultMissing"), e);return;}// Process the default web.xml file (锁定webDigester变量,并解析默认的 web.xml 文件)synchronized (webDigester) {try {InputSource is =new InputSource("file://" + file.getAbsolutePath());stream = new FileInputStream(file);is.setByteStream(stream);webDigester.setDebug(getDebug());if (context instanceof StandardContext)((StandardContext) context).setReplaceWelcomeFiles(true);webDigester.clear();webDigester.push(context);webDigester.parse(is);webDigester.push(null); // 解析结束.} catch (SAXParseException e) {log(sm.getString("contextConfig.defaultParse"), e);log(sm.getString("contextConfig.defaultPosition","" + e.getLineNumber(),"" + e.getColumnNumber()));ok = false;} catch (Exception e) {log(sm.getString("contextConfig.defaultParse"), e);ok = false;} finally {try {if (stream != null) {stream.close();}} catch (IOException e) {log(sm.getString("contextConfig.defaultClose"), e);}}}}

【2.2】applicationConfig()方法
1)intro:该方法处理的是 应用程序自定义的web.xml,位于 WEB-INF 目录中;
private void applicationConfig() {  //org.apache.catalina.startup.ContextConfig.applicationConfig().// Open the application web.xml file, if it existsInputStream stream = null;ServletContext servletContext = context.getServletContext();if (servletContext != null)stream = servletContext.getResourceAsStream(Constants.ApplicationWebXml); //  public static final String ApplicationWebXml = "/WEB-INF/web.xml";if (stream == null) {log(sm.getString("contextConfig.applicationMissing"));return;}// Process the application web.xml filesynchronized (webDigester) {try {URL url =servletContext.getResource(Constants.ApplicationWebXml);InputSource is = new InputSource(url.toExternalForm());is.setByteStream(stream);webDigester.setDebug(getDebug());if (context instanceof StandardContext) {((StandardContext) context).setReplaceWelcomeFiles(true);}webDigester.clear();webDigester.push(context);webDigester.parse(is);webDigester.push(null);} catch (SAXParseException e) {log(sm.getString("contextConfig.applicationParse"), e);log(sm.getString("contextConfig.applicationPosition","" + e.getLineNumber(),"" + e.getColumnNumber()));ok = false;} catch (Exception e) {log(sm.getString("contextConfig.applicationParse"), e);ok = false;} finally {try {if (stream != null) {stream.close();}} catch (IOException e) {log(sm.getString("contextConfig.applicationClose"), e);}}}}

【2.3】创建 Web Digester
1)在ContextConfig 类中,使用变量 webDigester来引用一个 Digester类型的对象;
private static Digester webDigester = createWebDigester();private static Digester createWebDigester() {  //org.apache.catalina.startup.ContextConfig.createWebDigester().URL url = null;Digester webDigester = new Digester();webDigester.setValidating(true);url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_22);webDigester.register(Constants.WebDtdPublicId_22,url.toString());url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_23);webDigester.register(Constants.WebDtdPublicId_23,url.toString());webDigester.addRuleSet(new WebRuleSet()); // highlight line.return (webDigester);}
2)这个Digester对象用来解析默认的 web.xml 文件和应用程序自定义的 web.xml 文件。在调用了 createWebDigester() 方法时会添加用来处理 web.xml 文件的规则;(干货——在调用了 createWebDigester() 方法时会添加用来处理 web.xml 文件的规则,什么是规则,你懂的,前面已经详细介绍了Rule)
Attention)
A1)createWebDigester()方法调用了变量webDigester的 addRuleSet()方法,传入了一个 org.apache.catalina.startup.WebRuleSet 类型的对象作为参数;
A2)WebRuleSet 类是 org.apache.commons.digester.RuleSetBase的子类;
public class WebRuleSet extends RuleSetBase { //org.apache.catalina.startup.WebRuleSet

A3)org.apache.catalina.startup.WebRuleSet的定义代码见 文末,特别要注意其addRuleInstances()方法,其添加了很多规则集合;(干货——addRuleInstances()方法添加了很多规则集合)
【3】应用程序(本测试用例重在说明如何使用ContextConfig实例作为一个监听器来配置StandardContext对象) 
Attention)通过以下实例,你会发现,这与之前的Bootstrap 测试用例大有不同,以前是显式地创建StandardWrapper(创建具体的servlet实例),而下面的测试用例采用Digester从 xml 中读取 servlet的配置信息创建servlet实例,这就是为什么本文之前讲那么多 Digester库的原因;
1)测试用例
public final class Bootstrap {// invoke: http://localhost:8080/app1/Modern or // http://localhost:8080/app2/Primitive// note that we don't instantiate a Wrapper here,// ContextConfig reads the WEB-INF/classes dir and loads all servlets.public static void main(String[] args) {System.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector();Context context = new StandardContext();// StandardContext's start method adds a default mappercontext.setPath("/app1");context.setDocBase("app1");LifecycleListener listener = new ContextConfig();((Lifecycle) context).addLifecycleListener(listener);Host host = new StandardHost();host.addChild(context);host.setName("localhost");host.setAppBase("webapps");Loader loader = new WebappLoader();context.setLoader(loader);connector.setContainer(host);try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) host).start();Container[] c = context.findChildren();int length = c.length;for (int i=0; i<length; i++) {Container child = c[i];System.out.println(child.getName());}// make the application wait until we press a key.System.in.read();((Lifecycle) host).stop();}catch (Exception e) {e.printStackTrace();}}
}
Supplement)本文习惯性地总结了上述测试用例的调用过程,如下:


S0)上述调用过程涉及到的变量ApplicationWebXml 和 DefaultWebXml,其值为:
 public static final String ApplicationWebXml = "/WEB-INF/web.xml";public static final String DefaultWebXml = "conf/web.xml"; // both of them are defined in org.apache.catalina.startup.Constant;
S1)自定义的web.xml如下所示:该文件的文件路径为  System.getProperty("user.dir")\webapps\app1\WEB-INF;
S2)(tomcat使用了开源库Digester来将XML 文档中的元素转换成 java 对象,触发相应规则如调用设置器来配置StandardContext的子容器(StandardWrapper包装了servlet))(干货——具体的规则包含在 WebRuleSet类中,见文末)
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd"><web-app><servlet><servlet-name>Modern</servlet-name><servlet-class>ModernServlet</servlet-class></servlet><servlet><servlet-name>Primitive</servlet-name><servlet-class>PrimitiveServlet</servlet-class></servlet><servlet-mapping><servlet-name>Modern</servlet-name><url-pattern>/Modern</url-pattern></servlet-mapping><servlet-mapping><servlet-name>Primitive</servlet-name><url-pattern>/Primitive</url-pattern></servlet-mapping>
</web-app>
2)console info
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common.
jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/commons-digester.jar;lib/catalina.jar;lib/commons-logging.jar;lib/commons-beanutils.jar;E
:\bench-cluster\cloud-data-preprocess\HowTomcatWoks\webroot com.tomcat.chapter15.startup.Bootstrap
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
WebappLoader[/app1]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\localhost\app1
WebappLoader[/app1]: Deploy class files /WEB-INF/classes to E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\webapps\app1\WEB-INF\classes
ContextConfig[/app1]: Missing default web.xml, using application web.xml only
StandardManager[/app1]: Seeding random number generator class java.security.SecureRandom
StandardManager[/app1]: Seeding of random number generator has been completed
Primitive
Modern
StandardHost[localhost]: MAPPING configuration error for request URI /Modern/app1/Modern
StandardHost[localhost]: MAPPING configuration error for request URI /favicon.ico
ModernServlet -- init
StandardHost[localhost]: MAPPING configuration error for request URI /favicon.ico
3)访问结果

public class WebRuleSet extends RuleSetBase { //org.apache.catalina.startup.WebRuleSetprotected String prefix = null;  public WebRuleSet() {  this("");}  public WebRuleSet(String prefix) {super();this.namespaceURI = null;this.prefix = prefix;}  public void addRuleInstances(Digester digester) { // highlight.digester.addRule(prefix + "web-app",new SetPublicIdRule(digester, "setPublicId"));digester.addCallMethod(prefix + "web-app/context-param","addParameter", 2);digester.addCallParam(prefix + "web-app/context-param/param-name", 0);digester.addCallParam(prefix + "web-app/context-param/param-value", 1);digester.addCallMethod(prefix + "web-app/display-name","setDisplayName", 0);digester.addRule(prefix + "web-app/distributable",new SetDistributableRule(digester));digester.addObjectCreate(prefix + "web-app/ejb-local-ref","org.apache.catalina.deploy.ContextLocalEjb");digester.addSetNext(prefix + "web-app/ejb-local-ref","addLocalEjb","org.apache.catalina.deploy.ContextLocalEjb");digester.addCallMethod(prefix + "web-app/ejb-local-ref/description","setDescription", 0);digester.addCallMethod(prefix + "web-app/ejb-local-ref/ejb-link","setLink", 0);digester.addCallMethod(prefix + "web-app/ejb-local-ref/ejb-ref-name","setName", 0);digester.addCallMethod(prefix + "web-app/ejb-local-ref/ejb-ref-type","setType", 0);digester.addCallMethod(prefix + "web-app/ejb-local-ref/local","setLocal", 0);digester.addCallMethod(prefix + "web-app/ejb-local-ref/local-home","setHome", 0);digester.addObjectCreate(prefix + "web-app/ejb-ref","org.apache.catalina.deploy.ContextEjb");digester.addSetNext(prefix + "web-app/ejb-ref","addEjb","org.apache.catalina.deploy.ContextEjb");digester.addCallMethod(prefix + "web-app/ejb-ref/description","setDescription", 0);digester.addCallMethod(prefix + "web-app/ejb-ref/ejb-link","setLink", 0);digester.addCallMethod(prefix + "web-app/ejb-ref/ejb-ref-name","setName", 0);digester.addCallMethod(prefix + "web-app/ejb-ref/ejb-ref-type","setType", 0);digester.addCallMethod(prefix + "web-app/ejb-ref/home","setHome", 0);digester.addCallMethod(prefix + "web-app/ejb-ref/remote","setRemote", 0);digester.addObjectCreate(prefix + "web-app/env-entry","org.apache.catalina.deploy.ContextEnvironment");digester.addSetNext(prefix + "web-app/env-entry","addEnvironment","org.apache.catalina.deploy.ContextEnvironment");digester.addCallMethod(prefix + "web-app/env-entry/description","setDescription", 0);digester.addCallMethod(prefix + "web-app/env-entry/env-entry-name","setName", 0);digester.addCallMethod(prefix + "web-app/env-entry/env-entry-type","setType", 0);digester.addCallMethod(prefix + "web-app/env-entry/env-entry-value","setValue", 0);digester.addObjectCreate(prefix + "web-app/error-page","org.apache.catalina.deploy.ErrorPage");digester.addSetNext(prefix + "web-app/error-page","addErrorPage","org.apache.catalina.deploy.ErrorPage");digester.addCallMethod(prefix + "web-app/error-page/error-code","setErrorCode", 0);digester.addCallMethod(prefix + "web-app/error-page/exception-type","setExceptionType", 0);digester.addCallMethod(prefix + "web-app/error-page/location","setLocation", 0);digester.addObjectCreate(prefix + "web-app/filter","org.apache.catalina.deploy.FilterDef");digester.addSetNext(prefix + "web-app/filter","addFilterDef","org.apache.catalina.deploy.FilterDef");digester.addCallMethod(prefix + "web-app/filter/description","setDescription", 0);digester.addCallMethod(prefix + "web-app/filter/display-name","setDisplayName", 0);digester.addCallMethod(prefix + "web-app/filter/filter-class","setFilterClass", 0);digester.addCallMethod(prefix + "web-app/filter/filter-name","setFilterName", 0);digester.addCallMethod(prefix + "web-app/filter/large-icon","setLargeIcon", 0);digester.addCallMethod(prefix + "web-app/filter/small-icon","setSmallIcon", 0);digester.addCallMethod(prefix + "web-app/filter/init-param","addInitParameter", 2);digester.addCallParam(prefix + "web-app/filter/init-param/param-name",0);digester.addCallParam(prefix + "web-app/filter/init-param/param-value",1);digester.addObjectCreate(prefix + "web-app/filter-mapping","org.apache.catalina.deploy.FilterMap");digester.addSetNext(prefix + "web-app/filter-mapping","addFilterMap","org.apache.catalina.deploy.FilterMap");digester.addCallMethod(prefix + "web-app/filter-mapping/filter-name","setFilterName", 0);digester.addCallMethod(prefix + "web-app/filter-mapping/servlet-name","setServletName", 0);digester.addCallMethod(prefix + "web-app/filter-mapping/url-pattern","setURLPattern", 0);digester.addCallMethod(prefix + "web-app/listener/listener-class","addApplicationListener", 0);digester.addObjectCreate(prefix + "web-app/login-config","org.apache.catalina.deploy.LoginConfig");digester.addSetNext(prefix + "web-app/login-config","setLoginConfig","org.apache.catalina.deploy.LoginConfig");digester.addCallMethod(prefix + "web-app/login-config/auth-method","setAuthMethod", 0);digester.addCallMethod(prefix + "web-app/login-config/realm-name","setRealmName", 0);digester.addCallMethod(prefix + "web-app/login-config/form-login-config/form-error-page","setErrorPage", 0);digester.addCallMethod(prefix + "web-app/login-config/form-login-config/form-login-page","setLoginPage", 0);digester.addCallMethod(prefix + "web-app/mime-mapping","addMimeMapping", 2);digester.addCallParam(prefix + "web-app/mime-mapping/extension", 0);digester.addCallParam(prefix + "web-app/mime-mapping/mime-type", 1);digester.addCallMethod(prefix + "web-app/resource-env-ref","addResourceEnvRef", 2);digester.addCallParam(prefix + "web-app/resource-env-ref/resource-env-ref-name", 0);digester.addCallParam(prefix + "web-app/resource-env-ref/resource-env-ref-type", 1);digester.addObjectCreate(prefix + "web-app/resource-ref","org.apache.catalina.deploy.ContextResource");digester.addSetNext(prefix + "web-app/resource-ref","addResource","org.apache.catalina.deploy.ContextResource");digester.addCallMethod(prefix + "web-app/resource-ref/description","setDescription", 0);digester.addCallMethod(prefix + "web-app/resource-ref/res-auth","setAuth", 0);digester.addCallMethod(prefix + "web-app/resource-ref/res-ref-name","setName", 0);digester.addCallMethod(prefix + "web-app/resource-ref/res-sharing-scope","setScope", 0);digester.addCallMethod(prefix + "web-app/resource-ref/res-type","setType", 0);digester.addObjectCreate(prefix + "web-app/security-constraint","org.apache.catalina.deploy.SecurityConstraint");digester.addSetNext(prefix + "web-app/security-constraint","addConstraint","org.apache.catalina.deploy.SecurityConstraint");digester.addRule(prefix + "web-app/security-constraint/auth-constraint",new SetAuthConstraintRule(digester));digester.addCallMethod(prefix + "web-app/security-constraint/auth-constraint/role-name","addAuthRole", 0);digester.addCallMethod(prefix + "web-app/security-constraint/display-name","setDisplayName", 0);digester.addCallMethod(prefix + "web-app/security-constraint/user-data-constraint/transport-guarantee","setUserConstraint", 0);digester.addObjectCreate(prefix + "web-app/security-constraint/web-resource-collection","org.apache.catalina.deploy.SecurityCollection");digester.addSetNext(prefix + "web-app/security-constraint/web-resource-collection","addCollection","org.apache.catalina.deploy.SecurityCollection");digester.addCallMethod(prefix + "web-app/security-constraint/web-resource-collection/http-method","addMethod", 0);digester.addCallMethod(prefix + "web-app/security-constraint/web-resource-collection/url-pattern","addPattern", 0);digester.addCallMethod(prefix + "web-app/security-constraint/web-resource-collection/web-resource-name","setName", 0);digester.addCallMethod(prefix + "web-app/security-role/role-name","addSecurityRole", 0);digester.addRule(prefix + "web-app/servlet",new WrapperCreateRule(digester));digester.addSetNext(prefix + "web-app/servlet","addChild","org.apache.catalina.Container");digester.addCallMethod(prefix + "web-app/servlet/init-param","addInitParameter", 2);digester.addCallParam(prefix + "web-app/servlet/init-param/param-name",0);digester.addCallParam(prefix + "web-app/servlet/init-param/param-value",1);digester.addCallMethod(prefix + "web-app/servlet/jsp-file","setJspFile", 0);digester.addCallMethod(prefix + "web-app/servlet/load-on-startup","setLoadOnStartupString", 0);digester.addCallMethod(prefix + "web-app/servlet/run-as/role-name","setRunAs", 0);digester.addCallMethod(prefix + "web-app/servlet/security-role-ref","addSecurityReference", 2);digester.addCallParam(prefix + "web-app/servlet/security-role-ref/role-link", 1);digester.addCallParam(prefix + "web-app/servlet/security-role-ref/role-name", 0);digester.addCallMethod(prefix + "web-app/servlet/servlet-class","setServletClass", 0);digester.addCallMethod(prefix + "web-app/servlet/servlet-name","setName", 0);digester.addCallMethod(prefix + "web-app/servlet-mapping","addServletMapping", 2);digester.addCallParam(prefix + "web-app/servlet-mapping/servlet-name", 1);digester.addCallParam(prefix + "web-app/servlet-mapping/url-pattern", 0);digester.addCallMethod(prefix + "web-app/session-config/session-timeout","setSessionTimeout", 1,new Class[] { Integer.TYPE });digester.addCallParam(prefix + "web-app/session-config/session-timeout", 0);digester.addCallMethod(prefix + "web-app/taglib","addTaglib", 2);digester.addCallParam(prefix + "web-app/taglib/taglib-location", 1);digester.addCallParam(prefix + "web-app/taglib/taglib-uri", 0);digester.addCallMethod(prefix + "web-app/welcome-file-list/welcome-file","addWelcomeFile", 0);}}// ----------------------------------------------------------- Private Classes/*** A Rule that calls the <code>setAuthConstraint(true)</code> method of* the top item on the stack, which must be of type* <code>org.apache.catalina.deploy.SecurityConstraint</code>.*/final class SetAuthConstraintRule extends Rule {public SetAuthConstraintRule(Digester digester) {super(digester);}public void begin(Attributes attributes) throws Exception {SecurityConstraint securityConstraint =(SecurityConstraint) digester.peek();securityConstraint.setAuthConstraint(true);if (digester.getDebug() > 0)digester.log("Calling SecurityConstraint.setAuthConstraint(true)");}}/*** Class that calls <code>setDistributable(true)</code> for the top object* on the stack, which must be a <code>org.apache.catalina.Context</code>.*/final class SetDistributableRule extends Rule {public SetDistributableRule(Digester digester) {super(digester);}public void begin(Attributes attributes) throws Exception {Context context = (Context) digester.peek();context.setDistributable(true);if (digester.getDebug() > 0)digester.log(context.getClass().getName() +".setDistributable( true)");}}/*** Class that calls a property setter for the top object on the stack,* passing the public ID of the entity we are currently processing.*/final class SetPublicIdRule extends Rule {public SetPublicIdRule(Digester digester, String method) {super(digester);this.method = method;}private String method = null;public void begin(Attributes attributes) throws Exception {Context context = (Context) digester.peek(digester.getCount() - 1);Object top = digester.peek();Class paramClasses[] = new Class[1];paramClasses[0] = "String".getClass();String paramValues[] = new String[1];paramValues[0] = digester.getPublicId();Method m = null;try {m = top.getClass().getMethod(method, paramClasses);} catch (NoSuchMethodException e) {digester.log("Can't find method " + method + " in " + top +" CLASS " + top.getClass());return;}m.invoke(top, paramValues);if (digester.getDebug() >= 1)digester.log("" + top.getClass().getName() + "." + method +"(" + paramValues[0] + ")");}}/*** A Rule that calls the factory method on the specified Context to* create the object that is to be added to the stack.*/final class WrapperCreateRule extends Rule {public WrapperCreateRule(Digester digester) {super(digester);}public void begin(Attributes attributes) throws Exception {Context context =(Context) digester.peek(digester.getCount() - 1);Wrapper wrapper = context.createWrapper();digester.push(wrapper);if (digester.getDebug() > 0)digester.log("new " + wrapper.getClass().getName());}public void end() throws Exception {Wrapper wrapper = (Wrapper) digester.pop();if (digester.getDebug() > 0)digester.log("pop " + wrapper.getClass().getName());}
}

tomcat(15)Digester库相关推荐

  1. SystemCenter2012SP1实践(15)共享库服务器和ISO

    用过HyperV的同学都知道,HyperV调用ISO作为启动光盘的时候,必须保存在本地才行.网络共享下的一概不认.在SCVMM下,我们可以通过一些设置,让SCVMM下创建的虚拟机,支持调用不在同一台主 ...

  2. native react 常用指令_React Native 常用的 15 个库

    点赞再看,养成习惯 本文 GitHub https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料.欢迎Star和完善,大家 ...

  3. Python初学15——wordcloud库简介与使用

    目录 一.wordcloud库基本介绍 二.wordcloud库使用说明( .WordCloud(width= ,height=, min_font_size=, max_font_size=, fo ...

  4. tomcat(18)部署器

    [0]README -1)先上干货:本文重点分析了tomcat 如何部署WAR文件的项目形式 以及 普通文件夹的项目形式:不管是WAR文件 还是 普通文件夹的项目形式,在tomcat中,它们都是Con ...

  5. tomcat(17)启动tomcat

    [0]README 1)本文部分文字描述转自"how tomcat works",旨在学习"tomcat(17)启动tomcat"的相关知识: 2)本文重点关注 ...

  6. tomat(16)关闭钩子

    [0]REAMDE 0)本文部分文字描述转自:"how tomcat works",旨在学习"tomat(16)关闭钩子"的相关知识: 1)problem+so ...

  7. 15个PHP库,你值得拥有!(下)

    2019独角兽企业重金招聘Python工程师标准>>> 在PHP程序员应该知道的15个库(上)一文中,小编为大家介绍了Mink.Geocoder.Ratchet等8个有用的PHP库, ...

  8. python高效编程15个利器_15个Python库,让你学习编程更轻松!

    image 在过去的五年中,Python已成为数据科学界的一大热门 .因此,它正在慢慢接管R–"统计学术语" – 作为许多工具的首选工具.最近发布的Stack Overflow D ...

  9. “金三银四”春招指南之“性能调优”:MySQL+Tomcat+JVM,看完还怕面试官的轰炸?

    春招指南之"性能调优":MySQL+Tomcat+JVM,还怕面试官的轰炸? 01 MySQL性能调优 1.1 MySQL性能调优问题有哪些?怎么学? 关于这个,给大家看一份学习大 ...

最新文章

  1. Java常见面试题,2021年及答案汇总
  2. Python大佬抓取了招聘信息并告诉你哪种Python 程序员最赚钱
  3. 判断某个点是否在不规则图形内
  4. XGBoost核心讲解笔记(贪心学院)
  5. c语言-指针的本质和使用
  6. 黑马ee在职进阶视频_进阶– Java EE 7前端5强
  7. [vue-element]有阅读过ElementUI的源码吗?
  8. maven package install deploy区别
  9. Asp.Net项目的部署到Linux中(Linux + Jexus+Nginx )
  10. mcollective的web控制台---mcomaster搭建
  11. python回溯算法全排列_python 回溯法 子集树模板 系列 —— 11、全排列
  12. ps+背景缩放+内容缩放
  13. 怎么获取计算机用户权限,如何获取电脑的最高管理权限|细讲电脑最高管理权限的获取方式...
  14. PaddlePaddle tutorial Ⅰ——Multiple linear regression
  15. 回溯算法——我欲修仙(功法篇)
  16. Are Graph Augmentations Necessary? Simple Graph Contrastive Learning for Recommendation
  17. 国内用户访问国内服务器,国外用户访问国外服务器
  18. 中兴设备电话人工服务器,中兴刀片服务器 ATCA机柜 中兴 6008002200 网络服务器机柜...
  19. 【贝叶斯滤波与卡尔曼滤波】 第四讲 连续随机变量的贝叶斯公式
  20. php删除bom,php bom如何去掉

热门文章

  1. Keiichi Tsuchiya the Drift King
  2. P2575 高手过招
  3. 2021 CSP-S 游记
  4. CF1054D-Changing Array【贪心】
  5. bzoj1013,luogu4035-[JSOI2008]球形空间产生器【高斯消元】
  6. 【jzoj3734,Usaco2014Open银组】双导航(gpsdual)
  7. 【线段树】GSS5 - Can you answer these queries V(luogu-SPOJ 2916)
  8. 2017西安交大ACM小学期 毁灭序列[倒跑并查集]
  9. SpringBoot2.1.9 多数据源Mybatis—JDBC配置
  10. 一次堆外内存泄露的排查过程