• Tomcat源码阅读(初窥)


    第一次工作的时候,公司项目就是用Tomcat在来运行项目的,用于小项目服务,启动快,使用方便。
    Tomcat是一个服务器项目,由Apache组织维护,开源代码;
    tomcat连接前端页面与后端,将一个个浏览器的请求发送给server,server接收请求处理完再放回给前端处理结果
    这一步步是怎么做到的,肯定是不一般的设计,因此就有想对Tomcat深入了解的想法。

    Tomcat的启动运行

    启动方式

    windows系统环境启动

    startup.bat  ## 运行startup.bat 文件
    

    linux系统环境启动

    netstat -tnlp # 查看网络端口占用情况
    
    startup.sh  # 运行 startup.sh脚本文件
    

    启动脚本

    1.确定java的jre环境 JAVA_HOME
    2.确定tomcat的家,默认跟路径 CATALINA_HOME
    3.用java命令运行Bootstrap.java 的main方法,设置全局变量,启动Tomcat

    启动类-BootStrap.java

    tomcat服务器的启动类,main方法中启动tomcat
    BootStrap类的main方法中主要做了:
    1.初始化类加载器
    2.设置tomcat上下文类加载器
    3.创建Catalina实例
    4.调用catalina的load方法初始化lifecycle等基础组件
    5.调用catalina的start方法启动tomcat

    tomcat的设计定位,做了很多的特别的设计,如类加载的自定义,类加载机制并非双亲委派机制,上下文加载器的自定义,生命周期类组件定义,方便服务的管理

    main方法

    get

    代码:

    /**
         * Main method and entry point when starting Tomcat via the provided
         * scripts.
         *
         * @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;
            } 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);
            }
    
        }
    

    1.判断deamon 是否为空,为空则需要初始化

    Bootstrap bootstrap = new Bootstrap();
    try {
    	bootstrap.init();
    } catch (Throwable t) {
    	handleThrowable(t);
    	t.printStackTrace();
    	return;
    }
    daemon = bootstrap;
    

    2.根据启动命令参数决定 deamon的下一步动作

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

    3.load为启动做准备,然后start开始运行
    很明显,如果启动命令没有任何参数, Tomcat直接默认启动

    else if (command.equals("start")) {
    	daemon.setAwait(true);
    	daemon.load(args);
    	daemon.start();
    	if (null == daemon.getServer()) {
    		System.exit(1);
    	}
    }
    

    init() 类加载初始设置

    /**
     * Initialize daemon.
     * @throws Exception Fatal initialization error
     */
    public void init() throws Exception {
    
    	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.getConstructor().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();
    初始化 Tomcat自己定义的三种类加载器

    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 = createClassLoader("server", commonLoader);
    		sharedLoader = createClassLoader("shared", commonLoader);
    	} catch (Throwable t) {
    		handleThrowable(t);
    		log.error("Class loader creation threw exception", t);
    		System.exit(1);
    	}
    }
    
    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<>();
    
    	String[] repositoryPaths = getPaths(value);
    
    	for (String repository : repositoryPaths) {
    		// Check for a JAR URL repository
    		try {
    			@SuppressWarnings("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 Repository(repository, RepositoryType.GLOB));
    		} else if (repository.endsWith(".jar")) {
    			repositories.add(
    					new Repository(repository, RepositoryType.JAR));
    		} else {
    			repositories.add(
    					new Repository(repository, RepositoryType.DIR));
    		}
    	}
    
    	return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
    

    在初始化的时候,
    1.创建了三个类加载器,commonLoadercatalinaLoadersharedLoader
    类加载器:加载类文件的类
    所以创建三个类加载器,就是Tomcat要实现对类文件进行一套自己的管理方案的具体方法(区别于Java默认的类文件加载)
    2.设置线程上下文类加载器Thread.currentThread().setContextClassLoader(catalinaLoader);
    上下文类加器

    配置类加载环境

    上写文类加载器
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    设置自己的安全类加载器
    SecurityClassLoad.securityClassLoad(catalinaLoader);

    创建CataLina实例(org.apache.catalina.startup.Catalina)
    // 1.反射创建CataLina对象
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();
    
    // 2.反射获取setParentClassLoader方法对象
    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);
    // 3.反射设置Catalina的父类加载器为sharedLoader
    method.invoke(startupInstance, paramValues);
    
    // 4.将Catalina赋值给CatalinaDaemon 用于后续服务启动关闭重启的关键对象
    catalinaDaemon = startupInstance;
    

    daemon.load(args) 初始化Tomcat各组件(生命周期为主)

    /**
     * Load daemon.
     */
    private void load(String[] arguments)
    	throws Exception {
    
    	// Call the load() method
    	String methodName = "load";
    	Object param[];
    	Class<?> paramTypes[];
    	if (arguments==null || arguments.length==0) {
    		paramTypes = null;
    		param = null;
    	} else {
    		paramTypes = new Class[1];
    		paramTypes[0] = arguments.getClass();
    		param = new Object[1];
    		param[0] = arguments;
    	}
    	Method method =
    		catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    	if (log.isDebugEnabled())
    		log.debug("Calling startup class " + method);
    	method.invoke(catalinaDaemon, param);
    
    }
    

    Catalina

    load() 方法初始化Tomcat组件

    BootStrap.java 类中 daemon.load(args) 方法最后通过反射调用了catalina实例对象的load方法
    主要就是通过反射执行catalina对象的load方法(org.apache.catalina.startup.Catalina),load代码有点多,稍微了解下:

    /**
     * Start a new server instance.
     */
    public void load() {
    
    	if (loaded) {
    		return;
    	}
    	loaded = true;
    
    	long t1 = System.nanoTime();
    
    	initDirs();
    
    	// Before digester - it may be needed
    	initNaming();
            // 1.创建Digester对象,用于后面的解析Server.xml 文件,创建StandardServer等生命周期对象
    	// Create and execute our Digester
    	Digester digester = createStartDigester();
    
    	InputSource inputSource = null;
    	InputStream inputStream = null;
    	File file = null;
    	try {
    		try {
    			file = configFile();
    			inputStream = new FileInputStream(file);
    			inputSource = new InputSource(file.toURI().toURL().toString());
    		} catch (Exception e) {
    			if (log.isDebugEnabled()) {
    				log.debug(sm.getString("catalina.configFail", file), e);
    			}
    		}
    		if (inputStream == null) {
    			try {
    				inputStream = getClass().getClassLoader()
    					.getResourceAsStream(getConfigFile());
    				inputSource = new InputSource
    					(getClass().getClassLoader()
    					 .getResource(getConfigFile()).toString());
    			} catch (Exception e) {
    				if (log.isDebugEnabled()) {
    					log.debug(sm.getString("catalina.configFail",
    							getConfigFile()), e);
    				}
    			}
    		}
    
    		// This should be included in catalina.jar
    		// Alternative: don't bother with xml, just create it manually.
    		if (inputStream == null) {
    			try {
    				inputStream = getClass().getClassLoader()
    						.getResourceAsStream("server-embed.xml");
    				inputSource = new InputSource
    				(getClass().getClassLoader()
    						.getResource("server-embed.xml").toString());
    			} catch (Exception e) {
    				if (log.isDebugEnabled()) {
    					log.debug(sm.getString("catalina.configFail",
    							"server-embed.xml"), e);
    				}
    			}
    		}
    
    
    		if (inputStream == null || inputSource == null) {
    			if  (file == null) {
    				log.warn(sm.getString("catalina.configFail",
    						getConfigFile() + "] or [server-embed.xml]"));
    			} else {
    				log.warn(sm.getString("catalina.configFail",
    						file.getAbsolutePath()));
    				if (file.exists() && !file.canRead()) {
    					log.warn("Permissions incorrect, read permission is not allowed on the file.");
    				}
    			}
    			return;
    		}
    
    		try {
    			inputSource.setByteStream(inputStream);
    			digester.push(this); // 2.cataLina与digester关联,设置cataLina为root
    			digester.parse(inputSource); // 3 解析文件并在这里创建server listener  Connector Executor的实例
    		} catch (SAXParseException spe) {
    			log.warn("Catalina.start using " + getConfigFile() + ": " +
    					spe.getMessage());
    			return;
    		} catch (Exception e) {
    			log.warn("Catalina.start using " + getConfigFile() + ": " , e);
    			return;
    		}
    	} finally {
    		if (inputStream != null) {
    			try {
    				inputStream.close();
    			} catch (IOException e) {
    				// Ignore
    			}
    		}
    	}
    
    	getServer().setCatalina(this); // 4.获取StandardServer对象,并与catalina关联
    	getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    	getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
    
    	// Stream redirection
    	initStreams();
    
    	// Start the new server
    	try {
    		getServer().init(); // 5.standardServer初始化,下面一行代码是LifecycleException,说明方法与生命周期初始化有关 
    	} catch (LifecycleException e) {
    		if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
    			throw new java.lang.Error(e);
    		} else {
    			log.error("Catalina.start", e);
    		}
    	}
    
    	long t2 = System.nanoTime();
    	if(log.isInfoEnabled()) {
    		log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); // 6.打印记录Tomcat启动时初始化的时间
    	}
    }
    

    daemon.start();

    /**
     * Start a new server instance.
     */
    public void start() {
    
    	if (getServer() == null) {
    		load(); //1 判断是否创建Server实例 , 没有调用load方法试图创建Server实例
    	}
    
    	if (getServer() == null) {
    		log.fatal("Cannot start server. Server instance is not configured.");
    		return;
    	}
    
    	long t1 = System.nanoTime();
    
    	// Start the new server
    	try {
    		getServer().start(); // 2.调用server的start()方法,这里是Server接口的实现类:StandardServer的实例
    	} catch (LifecycleException e) {
    		log.fatal(sm.getString("catalina.serverStartFail"), e);
    		try {
    			getServer().destroy();
    		} catch (LifecycleException e1) {
    			log.debug("destroy() failed for failed Server ", e1);
    		}
    		return;
    	}
    
    	long t2 = System.nanoTime();
    	if(log.isInfoEnabled()) {
    		log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); //3 日志打印tomcat启动时长
    	}
    
    	// Register shutdown hook
    	if (useShutdownHook) {
    		if (shutdownHook == null) {
    			shutdownHook = new CatalinaShutdownHook();
    		}
    		Runtime.getRuntime().addShutdownHook(shutdownHook);
    
    		// If JULI is being used, disable JULI's shutdown hook since
    		// shutdown hooks run in parallel and log messages may be lost
    		// if JULI's hook completes before the CatalinaShutdownHook()
    		LogManager logManager = LogManager.getLogManager();
    		if (logManager instanceof ClassLoaderLogManager) {
    			((ClassLoaderLogManager) logManager).setUseShutdownHook(
    					false);
    		}
    	}
    
    	if (await) {
    		await();
    		stop();
    	}
    }
    

    Tomcat类

  • 相关阅读:
    对象属性键值[key]属性问题
    理解 JavaScript 中的 for…of 循环
    vue初学篇----过滤器(filters)
    CSS变量
    SCSS !default默认变量
    vue 集成 NEditor 富文本
    如何在Github上面精准搜索开源项目?
    OSS介绍
    键盘各键对应的编码值(key code)
    网易云音乐歌单生成外链播放器
  • 原文地址:https://www.cnblogs.com/Narule/p/11979365.html
Copyright © 2020-2023  润新知