设为首页 收藏本站
查看: 529|回复: 0

[经验分享] Tomcat学习之二,认识Bootstrap类

[复制链接]

尚未签到

发表于 2017-1-27 11:54:38 | 显示全部楼层 |阅读模式
Bootstrap类全称org.apache.catalina.startup.Bootstrap
整个类加上注释和空白也就559行.代码写得很规整.到底人家是世界级的代码嘛.或者java的代码格式很容易写清楚.
整个类中有22个方法,六个成员变量 ,还有一个日志成员变量 .可见平均下来类中的方法也就10多行代码这个样子.我喜欢类中方法分类得当的.太长方法说明设计不当或者实在是逻辑很复杂.
这个类注释如下:

/**
* Bootstrap loader for Catalina.  This application constructs a class loader
* for use in loading the Catalina internal classes (by accumulating all of the
* JAR files found in the "server" directory under "catalina.home"), and
* starts the regular execution of the container.  The purpose of this
* roundabout approach is to keep the Catalina internal classes (and any
* other classes they depend on, such as an XML parser) out of the system
* class path and therefore not visible to application level classes.
* -------------------------我的翻译如下:-----------------------------------------
* Catalina的Bootstrap加载器(loader).此程序构造了一个类加载器(class loader)用来在加载Catalina的内部类(internal classes)(即汇集在"catalina.home"下"server"目录中的所有JAR文件,和启动容器的常规执行(regular execution).这样绕着弯子的做法是为了保持Catalina的内部类(和它依赖的任何类,例如XMP解析器)不在系统中类路径(system class path)中,从而对应用程序的类就不可见了.
*/

我们来看下Bootstrap的main方法(不知道为什么,对于程序自从C开始,我就首先去找main函数,找不到我就有点不太爽.)主要就是初始为类加载器并启动此类.如下:

/**
* Main method, used for testing only.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
handleThrowable(t);
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
t.printStackTrace();
System.exit(1);
}
}

(#q1)上面那句注释中说的仅用于测试,难道正确的启动就不用了吗?
还有对于下面的代码:

String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}

(#q2) 为什么判断了args.length之后,是用args[args.length - 1]而不是用args[0]呢?
从此代码中,我还了解到了一点,tomcat7源代码中的try{}catch(Throwable t){}
catch子句中,catch的都是Throwable.之前我一直觉得Exception是根本,思考了之下.
我看了下jdk.原来Exception还有一个Throwable超类.同时Throwable也是Errors的超类.
引用

Throwable 类是 Java 语言中所有错误或异常的超类,只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。


我们看下main中调用的初始化方法即bootstrap.init().
看下初始化方法都做了哪些事件先:

/**
* Initialize daemon.
*/
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}

上面的方法也比较清晰易懂:
先是设置Catalina的路径.
(吐槽下啊.为什么叫catalina这个名字呢?我google了下,没有找到什么好的解释:
不过google给出的第一个网站的说明是:
If you're planning a trip to Catalina Island, this should be your first stop. The Catalina Island Guide includes packages, activities, lodging, transportation, local ...
是不是因为tomcat的开发者喜欢这个Catalina Island啊?
)
设置路径之后,调用初始化类加载器的方法即initClassLoaders()
如下:

private void initClassLoader(){
try{
commonLoader = createClassLoader("common",null);
if( commonLoader == null){
//no config file,default to this  loader -we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server",commonLoader);
sharedLoader = createClassLoader("shared",commonLoader);
}catch(Throwable t){
handleThrowable(t);
log.error("Class loader creation threw exception",t);
System.exit(1);
}
}


初始化类加载器,主要也是三个步骤.即创建一个公共类加载器commonLoader.
然后以commonLoader为父类加载器.创标签名为"server"的catalinaLoader类加载器,和标签名为"shared"的sharedLoader类加载器.
createClassLoader()方法如下:

private ClassLoader createClassLoader(String name,ClassLoader parent)
throws Exception{
String value = CatalinaProperties.getProperty(name+".loader");
if((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<Repository>();
StringTokenizer tokenizer = new StringTokenizer(value,",");
while(tokenizer.hasMoreElements()){
String repository = tokenizer.nextToken().trim();
if(repository.length() == 0){
continue;
}
//check for a jar URL repository
try{
@SupperssWarning("unused");
URL url = new URL(repository);
repositories.add(new Repository(repository,RepositoryType.URL);
continue;
}catch(MalformedURLException e){
// Ignore
}
// Local repository
if(repository.endsWith("*.jar")){
repository = repository.substring(0,repository.length() - "*.jar".length());
repositories.add(new Reposiotry(repository,RepositoryType.GLOB);
}else if(repository.endsWith(".jar")){
repositories.add(new Reposiotry(repository,RepositoryType.JAR);
}else {
repositories.add(new Reposiotry(repository,RepositoryType.DIR);
}
}
ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories,parent);
// Retrieving MBean server
MBeanServer mBeanServer = null;
if(MBeanServerFactory.findMBeanServer(null).size() > 0) {
mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
}else{
mBeanServer = ManagementFactory.getPlatformMBeanServer();
}
// Register the server classLoader
ObjectName objectName = new ObjectName("Catalina:type=ServerClassLoader,name="+name);
mBeanServer.registerMBean(classLoader,objectName);
return classLoader;
}

上面的方法中第一句代码:
String value = CatalinaProperties.getProperty(name+".loader");
CatalinaProperties就是指tomcat/conf目录下的catalina.properties文件的配置.
common.loader的value值为:
引用

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar


value = replace(value)
上面这一句代码执行过后,value值,在我的系统中变成了如下:
引用

"/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib,/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/*.jar,/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib,/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/*.jar"


上面出现的Repository是ClassLoaderFactory的静态内部类,
RepositoryType是ClassLoaderFactory的静态内部枚举.
代码如下:

public static enum RepositoryType {
DIR,
GLOB,
JAR,
URL
}
public static class Repository {
private String location;
private RepositoryType type;
public Repository(String location, RepositoryType type) {
this.location = location;
this.type = type;
}
public String getLocation() {
return location;
}
public RepositoryType getType() {
return type;
}
}

在确定的类资源的仓库之后,接下来就到了tomcat加载类最主要的部分了.

ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories,parent);

在ClassLoaderFactory的createClassLoader方法中,先是将repositories中路径,变成一个
包含仓库中jar包文件的URL路径的数组:
然后以此数组为参数返回一个标准的类加载器(StandardClassLoader).
此方法中的关键代码如下:

// Construct the class loader itself
final URL[] array = set.toArray(new URL[set.size()]);
if (log.isDebugEnabled())
for (int i = 0; i < array.length; i++) {
log.debug("  location " + i + " is " + array);
}
return AccessController.doPrivileged(
new PrivilegedAction<StandardClassLoader>() {
@Override
public StandardClassLoader run() {
if (parent == null)
return new StandardClassLoader(array);
else
return new StandardClassLoader(array, parent);
}
});

接我上面环境.此代码执行过程,array数组中的内容如下所示:
引用

[file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/jasper-el.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-jdbc.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-i18n-ja.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina-ha.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-dbcp.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/ecj-3.7.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-coyote.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-i18n-fr.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/jsp-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-i18n-es.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/el-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/jasper.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina-tribes.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/servlet-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/tomcat-util.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/annotations-api.jar, file:/home/banxi1988/work/tomcat7/tomcat-7.0.x/output/build/lib/catalina-ant.jar]


现在我们来仔细看下上面那个复杂的返回语句:
首先了解下AccessController类.它在java.security包下:
jdk_api_1.6对此类的使用目的说明如下:
引用

AccessController 类用于与访问控制相关的操作和决定。
更确切地说,AccessController 类用于以下三个目的:
    基于当前生效的安全策略决定是允许还是拒绝对关键系统资源的访问
    将代码标记为享有“特权”,从而影响后续访问决定,以及
    获取当前调用上下文的“快照”,这样便可以相对于已保存的上下文作出其他上下文的访问控制决定。


如类名所示,AccessController是用来做访问权限控制的.
引用

可以将调用方标记为享有“特权”(请参阅 doPrivileged 及下文)。在做访问控制决定时,如果遇到通过调用不带上下文参数(请参阅下文,以获取关于上下文参数的信息)的 doPrivileged 标记为“特权”的调用方,则 checkPermission 方法将停止检查。如果该调用方的域具有指定的权限,则不进行进一步检查,并且 checkPermission 正常返回,指示允许所请求的访问。如果该域不具有指定的权限,则通常抛出异常。
“特权”功能的标准用法如下所示。如果不需要从“特权”块返回值,则使用以下代码:



somemethod() {
...normal code here...
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// privileged code goes here, for example:
System.loadLibrary("awt");
return null; // nothing to return
}
});
...normal code here...
}

关于AccessController的具体详细使用的话,在此我也暂时无法分析清楚.
下面再分析下返回的标签类加载器.
return new StandardClassLoader(array);
这个类是在org.apache.catalina.loader包下.
整个类如下:

public class StandardClassLoader extends URLClassLoader implements StandardClassLoaderMBean{
public StandardClassLoader(URL repositories[]){
super(repositories);
}
public StandardClassLoader(URL repositories[],ClassLoader parent){
super(repositories,parent);
}
}

上面的类中,继承的类URLClassLoader在java.net包中.
而StandardClassLoaderMBean则是一个标记类如下:

package org.apache.catalina.loader;
/**
* MBean interface for StandardClassLoader, to allow JMX remote management.
*
* @author Remy Maucherat
* @version $Id: StandardClassLoaderMBean.java 988225 2010-08-23 17:38:41Z markt $
*/
public interface StandardClassLoaderMBean {
// Marker interface
}

下面我们来重点了解下URLClassLoader类.
下面是JDK_API_1.6的文档说明:
引用

public class URLClassLoader
extends SecureClassLoader
该类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要打开的 JAR 文件。
创建 URLClassLoader 实例的 AccessControlContext 线程将在后续加载类和资源时使用。
为加载的类默认授予只能访问 URLClassLoader 创建时指定的 URL 的权限。


下面是关于参数为URL[] urls的构造器的文档说明:
引用

URLClassLoader
public URLClassLoader(URL[] urls)
    使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader。首先在父类加载器中搜索 URL,然后按照为类和资源指定的顺序搜索 URL。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要下载和打开的 JAR 文件。
    如果有安全管理器,该方法首先调用安全管理器的 checkCreateClassLoader 方法以确保允许创建类加载器。
    参数:
        urls - 从其位置加载类和资源的 URL
    抛出:
        SecurityException - 如果安全管理器存在并且其 checkCreateClassLoader 方法不允许创建类加载器。
    另请参见:
        SecurityManager.checkCreateClassLoader()


查看源代码如下:

public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
acc = AccessController.getContext();
}

关于这个类加载器的创建过程就暂时分析到这里,下一往篇博文中将介绍下面提到的东西 .
上面main方法中关于command的if else 一堆.我们先来关注,正常情况下的启动过程:

else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-334072-1-1.html 上篇帖子: TomCat 出现 OutOfMemoryError : PermGen space 解决方法 下篇帖子: 绝对难题,tomcat警告 spring jdbcTemplate查询
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表