欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

tomcat7源码阅读(一)

程序员文章站 2022-06-15 10:23:33
...

先从tomcat启动脚本开始,我们可以使用startup.sh启动tomcat

startup.sh脚本分析

  1. 先判断操作系统(os400是 IBM的AIX、darwin是MacOSX 操作环境的操作系统成份、Darwin是windows平台上运行的类UNIX模拟环境)

  2. 获取catalina.sh的真实路径,并判断是否有可执行权限。调用catalina.sh脚本

PRG="$0"

while [ -h "$PRG" ] ; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
done

PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh

# Check that target executable exists
if $os400; then
  # -x will Only work on the os400 if the files are:
  # 1. owned by the user
  # 2. owned by the PRIMARY group of the user
  # this will not work if the user belongs in secondary groups
  eval
else
  if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
    echo "Cannot find $PRGDIR/$EXECUTABLE"
    echo "The file is absent or does not have execute permission"
    echo "This file is needed to run this program"
    exit 1
  fi
fi

exec "$PRGDIR"/"$EXECUTABLE" start "aaa@qq.com"

 catalina.sh

  1. 和start.sh类似,先检测操作系统

  2. 获取catalina.sh真实路径,并设置环境变量CATALINA_HOME、CATALINA_BASE。一般情况下两者都是tomcat根目录

  3. 调用setenv.sh设置classpath环境变量,但是这个文件是不存在的,如果想要额外的classpath,可以新建,感觉这么设计为了扩展性

  4. 在clsspath后追加Bootstrap.jar、Tomcat-juli.jar

  5. 解析脚本参数,执行java类Bootsrap的main方法,将start作为参数传入,也就是tomcat的入口

Bootstarp.java类

main方法

1.加锁新建一个Bootstrap对象,调用init初始化并且赋值给了私有静态对象daemon

2.调用了daemon对象load和start方法,而load和start方法里面利用了反射调用了catalinaDeamon的load和start方法

可以看到该类有一个Object作为锁,新建了一个为null的私有静态Bootstrap对象。

/**
 * Daemon object used by main.
 */
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;

public static void main(String args[]) {

        synchronized (daemonLock) {
            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;
            } else {
                // When running as a service the call to stop will be on a new
                // thread so make sure the correct class loader is used to
                // prevent a range of class not found exceptions.
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        }

        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();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } 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) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

 init方法

1.设置了catelina.home和catalina.base

2.使用initClassLoaders()创建了commonLoader、catalinaLoader和sharedLoader

3.利用反射新建一个org.apache.catalina.startup.Catalina类对象,赋值给了catalinaDeamon。

private Object catalinaDaemon = null;

ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null; 
public void init() throws Exception {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();
        //tomcat 违背了双亲委派原则
        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;
    }

initClassLoaders创建了3个类加载器

private void initClassLoaders() {
        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、sharedLoader 的父类加载器都为commonLoader
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

看到里边其实会有很多疑问:

为啥tomcat要破坏双亲委派原则?

先简单说说双亲委派原则,所有的加载器加载类的时候都会有父类加载器先去加载,父类加载不了才子类加载。因为jvm中的类的唯一性是由类加载器和类的全限定名来标识的,这种模型保证了类的唯一性。

但是tomcat是web服务器,需要解决几个问题:

  1. 一个web容器可能会部署两个应用程序,不同应用程序会依赖不同版本的类库,因此要保证每个应用程序的类库是独立的、互相隔离的
  2. web容器也有依赖的类库,不能和应用程序类混淆,要让容器类库和程序的类库隔离开来

很明显双亲委派模型解决不了问题,看看tomcat的加载器模型设计:

由WebappClassLoader去加载Webapp/WEB-INF/*的java类库,每个应用程序都有不同的WebAppClassLoader加载,这样实现了隔离。那么很显然,tomcat破坏了双亲违背原则,每个webappClassloade加载各自项目下的class文件,不会传递给父类去加载。

tomcat7源码阅读(一)

 

为啥catalinaDeam要用反射加载,调用它的start和load方法为什么还要用反射?

大家都知道,反射是会降低性能的。