精华内容
下载资源
问答
  • Tomcat类加载方式

    一、Tomcat类加载方式

    1、jdk定义的类加载

    双亲委派模型
    从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
    从Java开发人员的角度来看, 类加载器还可以划分得更细致一些, 绝大部分Java程序都会使用到以下3种系统提供的类加载器。

    1)启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。

    2)扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher.ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

    3)应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher.AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Class Path)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    4)我们的应用程序都是由这3种类加载器互相配合进行加载的,如果有必要,还可以加入自己定义的类加载器。这些类加载器之间的关系一般如下图所示。

    在这里插入图片描述
    双亲委托类加载源码参考如下:

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    	//首先, 检查请求的类是否已经被加载过了
    	Class c=findLoadedClass(name);
    	if( c== null ){
    		try{
    			if( parent != null ){
    				c = parent.loadClass(name,false);
    			} else {
    				c = findBootstrapClassOrNull(name);
    			}
    		} catch (ClassNotFoundException e) {
    			//如果父类加载器抛出ClassNotFoundException
    			//说明父类加载器无法完成加载请求
    		}
    		if( c == null ) {
    			//在父类加载器无法加载的时候
    			//再调用本身的findClass方法来进行类加载
    			c = findClass(name);
    		}
    	} 
    	if(resolve){
    		resolveClass(c);
    	}
    	return c;
    }
    

    2、Tomcat类加载方式

    打破双亲委派模型
    上文提到过双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型,但也有例外。
    Tomcat有自己定义的类加载器,因为一个功能健全的Web容器,要解决如下几个问题:
    **1)部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以实现相互隔离。**这是最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相独立使用。
    **2)部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以互相共享。**这个需求也很常见,例如,用户可能有10个使用Spring组织的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到Web容器的内存,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。
    **3)Web容器需要尽可能地保证自身的安全不受部署的Web应用程序影响。**目前,有许多主流的Java Web容器自身也是使用Java语言来实现的。因此,Web容器本身也有类库依赖的问题,一般来说,基于安全考虑,容器所使用的类库应该与应用程序的类库互相独立。
    **4)支持JSP应用的Web容器,大多数都需要支持HotSwap功能。**我们知道,JSP文件最终要编译成Java Class才能由虚拟机执行,但JSP文件由于其纯文本存储的特性,运行时修改的概率远远大于第三方类库或程序自身的Class文件。而且ASP、PHP和JSP这些网页应用也把修改后无须重启作为一个很大的“优势”来看待,因此“主流”的Web容器都会支持JSP生成类的热替换,当然也有“非主流”的,如运行在生产模式(Production Mode)下的WebLogic服务器默认就不会处理JSP文件的变化。

    在Tomcat目录结构中,有3组目录(“/common/”、“/server/”和“/shared/” tomcat6以后移到lib目录下)可以存放Java类库,另外还可以加上Web应用程序自身的目录“/WEB-INF/”,一共4组,把Java类库放置在这些目录中的含义分别如下:
    ①放置在/common目录中:类库可被Tomcat和所有的Web应用程序共同使用。
    ②放置在/server目录中:类库可被Tomcat使用,对所有的Web应用程序都不可见。
    ③放置在/shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
    ④放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。
    为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,其关系如下图所示。
    在这里插入图片描述

    前面3个类加载和默认的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/、/server/、/shared/*(在tomcat 6之后已经合并到根目录下的lib目录下)和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。

    根据不通的加载器的隔离性可见性分析如下:
    commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
    catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
    sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
    WebappClassLoaderJasperClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

    二、Springboot启动的嵌入式tomcat类加载

    Springboot启动嵌入式tomcat过程源码(参考我上一篇博文:SprintBoot 1.4.3 启动tomcat源码分析,与Tomcat启动war包的不同)如下:

        @Override
    	public EmbeddedServletContainer getEmbeddedServletContainer(
    			ServletContextInitializer... initializers) {
    		// 实例化一个tomcat
    		Tomcat tomcat = new Tomcat();
    		// 设置tomcat目录
    		File baseDir = (this.baseDirectory != null ? this.baseDirectory
    				: createTempDir("tomcat"));
    		tomcat.setBaseDir(baseDir.getAbsolutePath());
    		// 创建一个连接器
    		Connector connector = new Connector(this.protocol);
    		// service 添加连接器,一个service可以添加多个连接器
    		tomcat.getService().addConnector(connector);
    		// 设置连接器的相关参数
    		customizeConnector(connector);
    		// tomcat设置该连接器
    		tomcat.setConnector(connector);
    		tomcat.getHost().setAutoDeploy(false);
    		// 配置tomcat的engine
    		configureEngine(tomcat.getEngine());
    		for (Connector additionalConnector : this.additionalTomcatConnectors) {
    			tomcat.getService().addConnector(additionalConnector);
    		}
    		// 准备context
    		prepareContext(tomcat.getHost(), initializers);
    		// 创建Container
    		return getTomcatEmbeddedServletContainer(tomcat);
    	}
    

    今天我们进入prepareContext方法继续分析逻辑,就可以看出嵌入式tomcat的类加载方式,看源码:

    protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    		File docBase = getValidDocumentRoot();
    		docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
    		TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    		context.setName(getContextPath());
    		context.setDisplayName(getDisplayName());
    		context.setPath(getContextPath());
    		context.setDocBase(docBase.getAbsolutePath());
    		context.addLifecycleListener(new FixContextListener());
    		// 设置父类加载器,可以为空
    		context.setParentClassLoader(
    				this.resourceLoader != null ? this.resourceLoader.getClassLoader()
    						: ClassUtils.getDefaultClassLoader());
    		resetDefaultLocaleMapping(context);
    		addLocaleMappings(context);
    		try {
    			context.setUseRelativeRedirects(false);
    		}
    		catch (NoSuchMethodError ex) {
    			// Tomcat is < 8.0.30. Continue
    		}
    		SkipPatternJarScanner.apply(context, this.tldSkip);
    		// 使用WebApp类加载器加载应用信息
    		WebappLoader loader = new WebappLoader(context.getParentClassLoader());
    		// 嵌入式tomcat的webapp类加载器TomcatEmbeddedWebappClassLoader
    		loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
    		loader.setDelegate(true);
    		context.setLoader(loader);
    		if (isRegisterDefaultServlet()) {
    			addDefaultServlet(context);
    		}
    		if (shouldRegisterJspServlet()) {
    			addJspServlet(context);
    			addJasperInitializer(context);
    			context.addLifecycleListener(new StoreMergedWebXmlListener());
    		}
    		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    		configureContext(context, initializersToUse);
    		host.addChild(context);
    		postProcessContext(context);
    	}
    

    我们进入TomcatEmbeddedWebappClassLoader类继续分析:

    **
     * Extension of Tomcat's {@link WebappClassLoader} that does not consider the
     * {@link ClassLoader#getSystemClassLoader() system classloader}. This is required to to
     * ensure that any custom context classloader is always used (as is the case with some
     * executable archives).
     *
     * @author Phillip Webb
     */
    public class TomcatEmbeddedWebappClassLoader extends WebappClassLoader {
    
    	private static final Log logger = LogFactory
    			.getLog(TomcatEmbeddedWebappClassLoader.class);
    
    	public TomcatEmbeddedWebappClassLoader() {
    		super();
    	}
    
    	public TomcatEmbeddedWebappClassLoader(ClassLoader parent) {
    		super(parent);
    	}
    
    	// 类加载过程
    	@Override
    	public synchronized Class<?> loadClass(String name, boolean resolve)
    			throws ClassNotFoundException {
    			Class<?> resultClass = null;
    
    		// 验证当前class缓存,即类是否被加载过
    		resultClass = (resultClass == null ? findLoadedClass0(name) : resultClass);
    		resultClass = (resultClass == null ? findLoadedClass(name) : resultClass);
    		if (resultClass != null) {
    			return resolveIfNecessary(resultClass, resolve);
    		}
    
    		// 校验权限
    		checkPackageAccess(name);
    
    		// 执行具体加载
    		boolean delegateLoad = (this.delegate || filter(name, true));
    
    		if (delegateLoad) {
    			resultClass = (resultClass == null ? loadFromParent(name) : resultClass);
    		}
    		resultClass = (resultClass == null ? findClassIgnoringNotFound(name)
    				: resultClass);
    		if (!delegateLoad) {
    			resultClass = (resultClass == null ? loadFromParent(name) : resultClass);
    		}
    
    		if (resultClass == null) {
    			throw new ClassNotFoundException(name);
    		}
    
    		return resolveIfNecessary(resultClass, resolve);
    	}
    

    SpringBoot嵌入式的Tomcat的启动过程中的类加载只需要WebApp的类加载和Jsper类加载。
    而tomcat启动war要经历所有的类加载过程。

    本文分析若有不对的地方还请给位指正。

    展开全文
  • Servlet与Tomcat关系

    千次阅读 2019-08-22 16:55:03
    Servlet与Tomcat关系概述 在web项目时,多数需要http协议,也就是基于请求和响应,比如你在百度输入一行内容搜索, 那么百度服务器如何处理这个请求呢,他需要创建servlet来处理,servlet其实就是java程序,只是...

    Servlet与Tomcat的关系概述

    在web项目访问中,多数需要http协议,也就是基于请求和响应,比如你在百度输入一行内容搜索,

    那么百度服务器如何处理这个请求呢,他需要创建servlet来处理,servlet其实就是java程序,只是在服务器端的java程序,

    servlet通过配置文件拦截你的请求,并进行相应处理,然后展示给你相应界面,那么servlet如何创建? 这时候tomcat用到了,

    它就是帮助你创建servlet的东西,所以也称web容器,没有它,没法运行web项目。相对应的web容器有很多,比如Tomcat、Jboss、Weblogic等




    Servlet

    1、Servlet是什么?

    (1)Servlet是一个Java应用程序,运行在服务器端,用来处理客户端请求并作出响应的程序。
    (2)Servlet多线程体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器(如Tomcat)负责的。
    (3)Servlet的作用:主要功能在于交互式地浏览和修改数据,生成动态Web内容。
    (4)Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。 它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器响应(HTTP服务器上的数据库或应用程序)的中间层。

    1.1 Servlet 与 Servlet 容器的关系

    tomcat容器模型
    Context 容器
    Tomcat 的容器等级中,Context 容器是直接管理 Servlet 在容器中的包装类 Wrapper,所以 Context 容器如何运行将直接影响 Servlet 的工作方式。

    真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程,在 Tomcat 的配置文件中可以很容易发现这一点,如下:

    <Context path="/projectOne " docBase="D:\projects\projectOne"  
    reloadable="true" />
    
    1.2 Servlet 如何工作?

    当用户从浏览器向服务器发起一个请求,请求信息:

    http://hostname: port /contextpath/servletpath 
    
            ①:hostname 和 port 是用来与服务器建立 TCP 连接。
            ②:【/contextpath/servletpath】即【 URL 】才是用来选择服务器中的哪个子容器来服务用户的请求。
            特别说明:
            Tomcat7.0 中有个映射类【org.apache.tomcat.util.http.mapper】,这个类保存了 Tomcat 的 Container 容器中的所有子容器的信息,
            mapper 将会根据这次请求的 hostnane 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中。


    Tomcat

    Tomcat 是什么?

      Tomcat是应用(java)服务器,它只是一个servlet容器,是Apache的扩展,处理动态网页部分。
      Tomcat是运行java的网络服务器,底层是一个socket的程序,同时Tomcat也是JSP和Serlvet的容器

    那么为什么我们要用Tomcat呢?

      一般情况下,如果我们使用HTML/CSS/JS编写了一个网页,这个网页只能由我们本地打开,别的人无法通过远程访问这个页面。那么Tomcat就解决了这个问题,让其他人也可以访问我们写的页面。

    在这里插入图片描述




    扩 展 知 识

    Session

    (1)Session的三种工作方式
        1.基于 URL Path Parameter,默认就支持;
        2.基于 Cookie,如果你没有修改 Context 容器个 cookies 标识的话,默认也是支持的;
        3.基于 SSL,默认不支持,只有 connector.getAttribute(“SSLEnabled”) 为 TRUE 时才支持。
    (2)SSL(Secure Sockets Layer 安全套接层)协议,及其继任者TLS(Transport Layer Security传输层安全)协议,是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密,用于保障网络数据传输安全,利用数据加密技术,确保数据在网络传输过程中不会被截取及窃听。SSL协议已成为全球化标准,所有主要的浏览器和WEB服务器程序都支持SSL协议,可通过安装SSL证书激活SSL协议。
    SSL 证书就是遵守 SSL协议的服务器数字证书,由受信任的证书颁发机构(CA机构),验证服务器身份后颁发,部署在服务器上,具有网站身份验证和加密传输双重功能。
    如果是第三种情况的话将会根据 javax.servlet.request.ssl_session 属性值设置 Session ID。
    (3)有了 Session ID 服务器端就可以创建 HttpSession 对象了,第一次触发是通过 request. getSession() 方法,如果当前的 Session ID 还没有对应的 HttpSession 对象那么就创建一个新的,并将这个对象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 类将管理所有 Session 的生命周期,Session 过期将被回收,服务器关闭,Session 将被序列化到磁盘等。只要这个 HttpSession 对象存在,用户就可以根据 Session ID 来获取到这个对象,也就达到了状态的保持。
    (4)Session 的致命弱点是不容易在多台服务器之间共享。

    Servlet 中的 Listener

    (1)整个 Tomcat 服务器中 Listener 使用的非常广泛,它是基于【观察者模式】设计的。
    Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段,能够方便的从另一个【纵向维度控制程序和数据】。
    (2)目前 Servlet 中提供了 6 种两类事件的观察者接口,它们分别是:
    4 个 EventListeners 类型的,ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionAttributeListener 和 2 个 LifecycleListeners 类型的,ServletContextListener、HttpSessionListener。
    这些 Listener 的实现类可以配置在 web.xml 中的 标签中。当然也可以在应用程序中动态添加 Listener,需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所监听的事件已经不会再出现。掌握这些 Listener 的使用,能够让我们的程序设计的更加灵活。

    展开全文
  • Java类加载机制与Tomcat类加载器架构

    万次阅读 热门讨论 2017-02-26 10:58:11
    Java加载机制 加载器 虚拟机设计团队把加载阶段中的“通过一个的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的。实现这个动作的...

    Java类加载机制

    类加载器

            虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为类加载器

          类加载器可以说是Java语言的一项创新,也是Java语言流行的重要原因之一,它最初是为了满足Java Applet的需求而开发出来的。虽然目前Java Applet技术基本上已经“死掉”,但类加载器却在类层次划分、OSGi、热部署、代码加密等领域大放异彩,成为了Java技术体系中一块重要的基石,可谓是失之桑榆,收之东隅。

          类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等

    双亲委派模型

          从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

          从Java开发人员的角度来看, 类加载器还可以划分得更细致一些, 绝大部分Java程序都会使用到以下3种系统提供的类加载器。

    1)启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用

    2)扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher.ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器

    3)应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher.AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Class Path)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器

          我们的应用程序都是由这3种类加载器互相配合进行加载的,如果有必要,还可以加入自己定义的类加载器。这些类加载器之间的关系一般如下图所示。

     

          图中展示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。类加载器的双亲委派模型在JDK 1.2期间被引入并被广泛应用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。

          双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载

          使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的Class Path中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。如果读者有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但永远无法被加载运行。

          双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如以下代码所示,逻辑清晰易懂:

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    	//首先检查请求的类是否已经被加载过了
    	Class c = findLoadedClass(name);
    	if (c== null) {
    		try {
    			if(parent !=null) {
    				c = parent.loadClass(name,false);
    			} else {
    				c = findBootstrapClassOrNull(name);
    			}
    		} catch (ClassNotFoundException e) {
    		//如果父类加载器抛出ClassNotFoundException,说明父类加载器无法完成加载请求
    		}
    		if(c == null) {
    			//在父类加载器无法加载的时候,再调用本身的findClass方法来进行类加载
    			c = findClass(name);
    		}
    	} 
    	if (resolve) {
    		resolveClass(c);
    	}
    	return c;
    }
    

           先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。双亲委派的具体逻辑就实现在这个loadClass()方法之中,JDK 1.2之后已不提倡用户再去覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里如果父类加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派规则的。

    自定义一个java.lang.Object类

            尝试在工程中创建自己的java.lang.Object类

    package java.lang;
     
    /**
     * 自己创建的java.lang.Object
     */
    public class Object {
        static {
            System.out.println("hello");
        }
        public static void main(String[] args) {
            Object o = new Object();
        }
    }

    运行main方法,发现打印报错:

          我们知道,类加载的过程会遵循双亲委派原则,当一个类首次被加载时,会依次向parent类加载器委托, 直到最顶层的BootstrapClassLoader。java.lang.Object属于系统类,会由BootstrapClassLoader优先加载,最终加载的还是系统原生的java.lang.Object类,因此会报找不到main方法的错误。

           有了这一前提,我们需要通过自定义类加载器绕过双亲委派机制,实现自定义类的加载。MyClassLoader为自定义类加载器,它继承于ClassLoader,覆写了loadClass方法,实现自定义加载。

    public class MyClassLoader extends ClassLoader {
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name == null || "".equals(name)) {
                throw new ClassNotFoundException();
            }
            InputStream is = null;
            try {
                String className = "/" + name.replace('.', '/') + ".class";
                System.out.println(className);
                // 在classpath路径下加载java/lang/Object.class文件
                is = getClass().getResourceAsStream(className);
                System.out.println(is);
                if (is == null) {
                    throw new ClassNotFoundException();
                }
                byte[] bytes = new byte[is.available()];
                is.read(bytes);
                // 调用父类classLoader的defineClass方法
                // 将字节数组转换为Class实例
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
     
        public static void main(String[] args) {
            MyClassLoader myClassLoader = new MyClassLoader();
            try {
                Class<?> clazz = Class.forName("java.lang.Object", true, myClassLoader);
                System.out.println("自定义类加载器:" + clazz.newInstance().getClass().getClassLoader());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    运行main函数:

    提示:禁止使用包名:“java.lang”。跟进defineClass的源码:

    protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {
        return defineClass(name, b, off, len, null);
    }
    
    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain) throws ClassFormatError {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }
    
    private ProtectionDomain preDefineClass(String name,ProtectionDomain pd) {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);
        // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }
        if (name != null) checkCerts(name, pd.getCodeSource());
        return pd;
    }

          在preDefineClass()这个方法的逻辑中是禁止包以“java.”开头命名的,并且该方法为final方法,禁止覆写,说明了我们无法通过自定义类加载器加载以“java.”开头的类。将自定义java.lang.Object修改成myjava.lang.Object后,直接运行。

    package myjava.lang;
    
    /**
     * @description 自定义的Object类,路径不能以java.开始
     */
    public class Object {
    
        static {
            System.out.println(Object.class.getName());
        }
    
        public static void main(String[] args) {
            Object object = new Object();
    
        }
    }
    

    结果如下:

    打破双亲委派模型

          上文提到过双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型,但也有例外。

          SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中,在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。 有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

          Java 提供了很多服务SPI,允许第三方为这些接口提供实现。这些SPI的接口由Java 核心库来提供,而这些 SPI 的实现则是由各供应商来完成。终端只需要将所需的实现作为Java应用所依赖的jar包包含进类路径(CLASSPATH)就可以了。问题在于SPI接口中的代码经常需要加载具体的实现类:SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;而SPI的实现类是由系统类加载器来加载的。启动类加载器是无法找到 SPI的实现类的(因为它只加载 Java 的核心库),按照双亲委派模型,启动类加载器无法委派系统类加载器去加载类。也就是说,类加载器的双亲委派模式无法解决这个问题
     

          双亲委派模型的一次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载)并防止类的重复加载基础类之所以称为基础,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美,如果基础类又要调用回用户的代码,那该怎么办?这并非是不可能的事情,一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的Class Path下的JNDI接口实现(SPI,Service Provider Interface)代码,但启动类加载器不可能“认识”这些代码,因为启动类加载器的搜索范围中找不到用户应用程序类,那该怎么办?

          为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个线程上下文加载器默认就是应用程序类加载器(Application ClassLoader)。

          有了线程上下文类加载器,就可以做一些“舞弊”的事情了,有了有线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,去加载所需要的SPI代码,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

    SPI打破双亲委派的示例

           Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要在代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。JDBC SPI Mysql的实现如下所示:

    来看下Mysql的驱动是如何被加载的,代码如下

    // 1.加载数据访问驱动
    Class.forName("com.mysql.jdbc.Driver");
     //2.连接到数据库上去
    Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "admin");


           Class.forName()触发了Mysql驱动的加载,我们看下com.mysql.jdbc.Driver这个类,它实现了java.sql.Driver接口,加载该类时肯定先执行静态代码块,向DriverManager中注册了一个mysql的Driver实现。

    package com.mysql.jdbc;
    
    import java.sql.DriverManager;
    import java.sql.SQLException;
    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    
        public Driver() throws SQLException {
    
        }
    
        static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    }

           现在问题来了,Class.forName()加载类用的是调用者的Classloader,这个调用者DriverManager是在rt.jar中的,ClassLoader是启动类加载器,而com.mysql.jdbc.Driver肯定不在<JAVA_HOME>/lib下,所以肯定是无法加载com.mysql.jdbc.Driver这个类的。这就是双亲委派模型的局限性了,父级加载器无法加载子级类加载器路径中的类。那么,这个问题如何解决呢?按照之前的分析,com.mysql.jdbc.Driver类只有应用类加载器能加载,那么我们只要在DriverManager类中有方法获取应用程序类加载器,然后通过它去加载就可以了。来看java.sql.DriverManager类的loadInitialDrivers()方法,关键源码如下:

    private static void loadInitialDrivers() {
       
       	//...省略代码
    
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
    
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
    
        //...省略代码
    }

         看英文注释就知道ServiceLoader.load()方法将各个厂商实现的驱动类都加载进来,ServiceLoader.load()方法的源码如下,看到这里就很明显了,用的就是线程上下文加载器。这样我们就可以成功的在rt.jar包中的DriverManager中成功的加载了放在第三方应用程序包中的类了。很明显,mysql驱动采用的这种SPI服务确确实实是破坏了双亲委派模型的,毕竟做到了父级类加载器加载了子级路径中的类。

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    模块化热部署打破双亲委派

          双亲委派模型的另一次“被破坏”是由于用户对程序动态性的追求而导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换(HotSwap)、模块热部署(HotDeployment)等,说白了就是希望应用程序能像我们的计算机外设那样,接上鼠标、U盘,不用重启机器就能立即使用,鼠标有问题或要升级就换个鼠标,不用停机也不用重启。对于个人计算机来说,重启一次其实没有什么大不了的,但对于一些生产系统来说,关机重启一次可能就要被列为生产事故,这种情况下热部署就对软件开发者,尤其是企业级软件开发者具有很大的吸引力。Sun公司所提出的JSR-294、JSR-277规范在与JCP组织的模块化规范之争中落败给JSR-291(即OSGi R4.2),虽然Sun不甘失去Java模块化的主导权,独立在发展Jigsaw项目,但目前OSGi已经成为了业界“事实上”的Java模块化标准,而OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。

          在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi将按照下面的顺序进行类搜索:

    1)将以java.*开头的类委派给父类加载器加载。

    2)否则,将委派列表名单内的类委派给父类加载器加载。

    3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。

    4)否则,查找当前Bundle的Class Path,使用自己的类加载器加载。

    5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。

    6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。

    7)否则,类查找失败。

          上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级的类加载器中进行的。

          只要有足够意义和理由,突破已有的原则就可认为是一种创新。正如OSGi中的类加载器并不符合传统的双亲委派的类加载器,并且业界对其为了实现热部署而带来的额外的高复杂度还存在不少争议,但在Java程序员中基本有一个共识:OSGi中对类加载器的使用是很值得学习的,弄懂了OSGi的实现,就可以算是掌握了类加载器的精髓。

    Tomcat的类加载器架构

          主流的Java Web服务器(也就是Web容器),如Tomcat、Jetty、WebLogic、WebSphere或其他笔者没有列举的服务器,都实现了自己定义的类加载器(一般都不止一个)。因为一个功能健全的Web容器,要解决如下几个问题:

          1)部署在同一个Web容器上两个Web应用程序使用的Java类库可以实现相互隔离。这是最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库可以互相独立使用。

          2)部署在同一个Web容器上两个Web应用程序所使用的Java类库可以互相共享。这个需求也很常见,例如,用户可能有10个使用Spring组织的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到Web容器的内存,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险

          3)Web容器需要尽可能地保证自身的安全不受部署的Web应用程序影响。目前,有许多主流的Java Web容器自身也是使用Java语言来实现的。因此,Web容器本身也有类库依赖的问题,一般来说,基于安全考虑,容器所使用的类库应该与应用程序的类库互相独立。

          4)支持JSP应用的Web容器,大多数都需要支持HotSwap功能。我们知道,JSP文件最终要编译成Java Class才能由虚拟机执行,但JSP文件由于其纯文本存储的特性,运行时修改的概率远远大于第三方类库或程序自身的Class文件。而且ASP、PHP和JSP这些网页应用也把修改后无须重启作为一个很大的“优势”来看待,因此“主流”的Web容器都会支持JSP生成类的热替换,当然也有“非主流”的,如运行在生产模式(Production Mode)下的WebLogic服务器默认就不会处理JSP文件的变化。

          由于存在上述问题,在部署Web应用时,单独的一个Class Path就无法满足需求了,所以各种Web容都“不约而同”地提供了好几个Class Path路径供用户存放第三方类库,这些路径一般都以“lib”或“classes”命名。被放置到不同路径中的类库,具备不同的访问范围和服务对象,通常,每一个目录都会有一个相应的自定义类加载器去加载放置在里面的Java类库。现在,就以Tomcat容器为例,看一看Tomcat具体是如何规划用户类库结构和类加载器的。

          在Tomcat目录结构中,有3组目录(“/common/*”、“/server/*”和“/shared/*”)可以存放Java类库,另外还可以加上Web应用程序自身的目录“/WEB-INF/*”,一共4组,把Java类库放置在这些目录中的含义分别如下:

          ①放置在/common目录中:类库可被Tomcat和所有的Web应用程序共同使用

          ②放置在/server目录中:类库可被Tomcat使用,对所有的Web应用程序都不可见。

          ③放置在/shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。

          ④放置在/WebApp/WEB-INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。

          为了支持这套目录结构,并对目录里面的类库进行加载和隔离,Tomcat自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,其关系如下图所示。

     
     

          上图中灰色背景的3个类加载器是JDK默认提供的类加载器,这3个加载器的作用已经介绍过了。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/*、/server/*、/shared/*和/WebApp/WEB-INF/*中的Java类库。其中WebApp类加载器和Jsp类加载器通常会存在多个实例每一个Web应用程序对应一个WebApp类加载器每一个JSP文件对应一个Jsp类加载器

           从图中的委派关系中可以看出,CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,而CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。

          对于Tomcat的6.x版本,只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader项后才会真正建立Catalina ClassLoader和Shared ClassLoader的实例,否则在用到这两个类加载器的地方都会用Common ClassLoader的实例代替,而默认的配置文件中没有设置这两个loader项,所以Tomcat 6.x顺理成章地把/common、/server和/shared三个目录默认合并到一起变成一个/lib目录,这个目录里的类库相当于以前/common目录中类库的作用。这是Tomcat设计团队为了简化大多数的部署场景所做的一项改进,如果默认设置不能满足需要,用户可以通过修改配置文件指定server.loader和share.loader的方式重新启用Tomcat 5.x的加载器架构。

          Tomcat加载器的实现清晰易懂,并且采用了官方推荐的“正统”的使用类加载器的方式。如果读者阅读完上面的案例后,能完全理解Tomcat设计团队这样布置加载器架构的用意,那说明已经大致掌握了类加载器“主流”的使用方式,那么笔者不妨再提一个问题让读者思考一下:前面曾经提到过一个场景,如果有10个Web应用程序都是用Spring来进行组织和管理的话,可以把Spring放到Common或Shared目录下让这些程序共享。Spring要对用户程序的类进行管理,自然要能访问到用户程序的类,而用户的程序显然是放在/WebApp/WEB-INF目录中的,那么被CommonClassLoader或SharedClassLoader加载的Spring如何访问并不在其加载范围内的用户程序呢?如果研究过虚拟机类加载器机制中的双亲委派模型,相信读者可以很容易地回答这个问题。

          分析:如果按主流的双亲委派机制,显然无法做到让父类加载器加载的类访问子类加载器加载的类,上面在类加载器一节中提到过通过线程上下文方式传播类加载器。

          答案是使用线程上下文类加载器来实现的,使用线程上下文加载器,可以让父类加载器请求子类加载器去完成类加载的动作。看spring源码发现,spring加载类所用的Classloader是通过Thread.currentThread().getContextClassLoader()来获取的,而当线程创建时会默认setContextClassLoader(AppClassLoader),即线程上下文类加载器被设置为AppClassLoader,spring中始终可以获取到这个AppClassLoader(在Tomcat里就是WebAppClassLoader)子类加载器来加载bean,以后任何一个线程都可以通过getContextClassLoader()获取到WebAppClassLoader来getbean了。

          本篇博文内容取材自《深入理解Java虚拟机:JVM高级特性与最佳实践》

     

    展开全文
  • 本篇文章我们来探索一下Tomcat的类加载机制,如果我们搜Tomcat的类加载机制,绝大多数结果都会给出答案——打破...1. Tomcat类加载机制要考虑的问题 Tomcat作为Servlet容器,它负责加载我们的Servlet类,此外它还负.

    本篇文章我们来探索一下Tomcat的类加载机制,如果我们搜Tomcat的类加载机制,绝大多数结果都会给出答案——打破双亲委托机制。但是感觉很多文章介绍的都不是很清楚,所以本篇文章就从我的理解上来分析一下Tomcat的类加载机制,希望能讲的明白。

    关于JVM类加载机制,我们在之前的文章Java编程拾遗『Java ClassLoader工作机制』 已经介绍过,有兴趣的可以去了解一下。

    1. Tomcat类加载机制要考虑的问题

    Tomcat作为Servlet容器,它负责加载我们的Servlet类,此外它还负责加载Servlet所依赖的 JAR 包。并且Tomcat本身也是也是一个Java程序,因此它需要加载自己的类和依赖的JAR包,所以可能要考虑这几个问题:

    1. 假如在Tomcat中运行了两个Web应用程序,两个Web应用中有同名的 Servlet,但是功能不同,Tomcat需要同时加载和管理这两个同名的Servlet 类,保证它们不会冲突,也就是说,Web应用之间的类需要隔离
    2. 假如两个Web应用都依赖同一个第三方的JAR包,比如Spring,那Spring的JAR包被加载到内存后,Tomcat要保证这两个Web应用能够共享,也就是说Spring的JAR包只被加载一次,否则随着依赖的第三方JAR包增多,JVM 的内存会膨胀
    3. Tomcat自身也是一个Java程序,需要隔离Tomcat本身的类和Web应用的类,避免相互影响,比如Web应用中定义了一个同名类导致Tomcat本身的类无法加载

    所以,Tomcat是如何来解决这些问题的?答案是通过设计多层次的类加载器。

    1.1 WebAppClassLoader

    首先来看第一个问题,假如我们使用JVM默认AppClassLoader来加载Web应用,AppClassLoader只能加载一个 Servlet 类,在加载第二个同名 Servlet 类时,AppClassLoader会返回第一个Servlet类的Class实例,这是因为在 AppClassLoader看来,同名的Servlet类只被加载一次。

    因此Tomcat的解决方案是自定义一个类加载器WebAppClassLoader,并且给每个Web应用创建一个类加载器实例。我们知道,Context容器组件对应一个Web应用,因此,每个Context容器负责创建和维护一个WebAppClassLoader加载器实例,这背后的原理是,不同的加载器实例加载的类被认为是不同的类,即使它们的类名相同。这就相当于在Java虚拟机内部创建了一个个相互隔离的Java类空间,每一个Web应用都有自己的类空间,Web应用之间通过各自的类加载器互相隔离。

    1.2 SharedClassLoader

    再来看第二个问题,本质需求是两个Web应用之间怎么共享库类,并且不能重复加载相同的类。我们知道,在双亲委托机制里,各个子加载器都可以通过父加载器去加载类,那么把需要共享的类放到父加载器的加载路径下应该就可以了,应用程序也正是通过这种方式共享JRE的核心类。因此Tomcat的设计者又加了一个类加载器SharedClassLoader,作为WebAppClassLoader的父加载器,专门来加载Web应用之间共享的类。如果WebAppClassLoader自己没有加载到某个类,就会委托父加载器SharedClassLoader去加载这个类,SharedClassLoader会在指定目录下加载共享类,之后返回给WebAppClassLoader,这样共享的问题就解决了。

    1.3 CatalinaClassloader

    第三个问题,如何隔离Tomcat本身的类和Web应用的类。我们知道,要共享可以通过父子关系,要隔离那就需要兄弟关系了。兄弟关系就是指两个类加载器是平行的,它们可能拥有同一个父加载器,但是两个兄弟类加载器加载的类是隔离的。基于此Tomcat又设计一个类加载器CatalinaClassloader,专门来加载Tomcat自身的类。

    如果Tomcat和各Web应用之间需要共享一些类时该怎么办?

    1.4 CommonClassLoader

    老办法,还是再增加一个CommonClassLoader,作为CatalinaClassloader和SharedClassLoader的父加载器。CommonClassLoader能加载的类都可以CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和 SharedClassLoader能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。

    1.5 线程类加载器

    在JVM的实现中有一条隐含的规则,默认情况下,如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载。比如 Spring 作为一个Bean工厂,它需要创建业务类的实例,并且在创建业务类实例之前需要加载这些类。Spring是通过调用Class.forName来加载业务类的,我们来看一下forName的源码:

    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

    可以看到在forName的函数里,会用调用者也就是Spring的加载器去加载业务类。

    前面提到,Web应用之间共享的JAR包可以交给SharedClassLoader来加载,从而避免重复加载。Spring作为共享的第三方JAR包,它本身是由SharedClassLoader 来加载的,Spring又要去加载业务类,按照前面那条规则,加载Spring的类加载器也会用来加载业务类,但是业务类在Web应用目录下,不在SharedClassLoader的加载路径下,这该怎么办呢?

    于是线程上下文加载器登场了,它其实是一种类加载器传递机制。为什么叫作“线程上下文加载器”呢,因为这个类加载器保存在线程私有数据里,只要是同一个线程,一旦设置了线程上下文加载器,在线程后续执行过程中就能把这个类加载器取出来用。因此Tomcat为每个Web应用创建一个WebAppClassLoarder类加载器,并在启动Web应用的线程里设置线程上下文加载器,这样Spring在启动时就将线程上下文加载器取出来,用来加载Bean。Spring取线程上下文加载的代码如下:

    ClassLoader cl = Thread.currentThread().getContextClassLoader();

    2. Tomcat类加载机制默认实现

    上面我们介绍了几种Tomcat的自定义类加载器,但其实除了上述几种自定义类加载器,Tomcat自身也是Java应用,肯定也需要JDK类加载器来加载。Tomcat相关的类加载器的加载范围如下:

    2.1 BootstrapClassLoader

    BootstrapClassLoader负责加载JVM提供的基础的运行时类(即rt.jar)以及${JAVA\_HOME}/jre/lib/ext下的类,按照之前JVM类加载器的介绍,其实这是BootstrapExtension这两个类加载器的功能。到底是一个类加载器还是两个类加载器,是由具体的JVM决定的。

    2.2 SystemClassLoader

    这里说的SystemClassLoader,其实就是之前介绍的AppClassLoader,不同的是在Tomcat的运行环境下,它不再去加载CLASSPATH中的类,而去加载$CATALINA_HOME/bin目录下的3个jar(这里是启动脚本catalina.sh中写死的),主要包括以下三个jar:

    • bootstrap.jar
    • tomcat-juli.jar(如果$CATALINA_BASE/bin目录下也有这个包,会使用$CATALINA_BASE/bin目录下的)
    • commons-daemon.jar

    2.3 CommonClassLoader

    CommonClassLoader是Tomcat自定义的类加载器,用于加载Tomcat容器自身和Web应用之间需要共享的类。所以该类加载器加载的类,对Tomcat自身和所有Web应用都可见。通常情况下,应用的类文件不应该放在Common ClassLoader中。Common会扫描 $CATALINA_BASE/conf/catalina.properties中common.loader属性指定的路径中的类文件。默认情况下,它会按顺序去下列路径中去加载:

    1. $CATALINA_BASE/lib中未打包的classes和resources
    2. $CATALINA_BASE/lib中的jar文件
    3. $CATALINA_HOME/lib中未打包的classes和resources
    4. $CATALINA_HOME/lib中的jar文件

    默认情况下,这些路径下的jar主要有以下这些:

    • annotations-api.jar — JavaEE annotations classes.
    • catalina.jar — Implementation of the Catalina servlet container portion of Tomcat.
    • catalina-ant.jar — Tomcat Catalina Ant tasks.
    • catalina-ha.jar — High availability package.
    • catalina-storeconfig.jar — Generation of XML configuration files from current state
    • catalina-tribes.jar — Group communication package.
    • ecj-.jar — Eclipse JDT Java compiler. el-api.jar — EL 3.0 API. jasper.jar — Tomcat Jasper JSP Compiler and Runtime. jasper-el.jar — Tomcat Jasper EL implementation. jsp-api.jar — JSP 2.3 API. servlet-api.jar — Servlet 3.1 API. tomcat-api.jar — Several interfaces defined by Tomcat. tomcat-coyote.jar — Tomcat connectors and utility classes. tomcat-dbcp.jar — Database connection pool implementation based on package-renamed copy of Apache Commons Pool and Apache Commons DBCP. tomcat-i18n-*.jar — Optional JARs containing resource bundles for other languages. As default bundles are also included in each individual JAR, they can be safely removed if no internationalization of messages is needed.
    • tomcat-jdbc.jar — An alternative database connection pool implementation, known as Tomcat JDBC pool. See documentation for more details.
    • tomcat-util.jar — Common classes used by various components of Apache Tomcat.
    • tomcat-websocket.jar — WebSocket 1.1 implementation
    • websocket-api.jar — WebSocket 1.1 API

    2.4 WebappClassLoader

    每一个部署在Tomcat中的web应用,Tomcat都会为其创建一个WebappClassloader,它会去加载应用WEB-INF/classes目录下所有未打包的classes和resources,然后再去加载WEB-INF/lib目录下的所有jar文件。每个应用的WebappClassloader都不同,因此,它加载的类只对本应用可见,其他应用不可见(这是实现web应用隔离的关键)。

    这里我们来介绍一个重要的概念——Tomcat类加载机制打破了双亲委托机制。为什么Tomcat要打破双亲委托机制?

    上面我们说了使用JDK默认类加载机制无法解决多个web应用加载同名类的问题,所以自定义了WebAppClassLoader用于解决该问题。其实说到底Tomcat打破JDK自定义类加载器的原因是Servlet规范,优先加载Web应用目录下的类,只要该类不覆盖JRE核心类

    从Web应用的视角来看,当有类加载的请求时,class或者resource的查找顺序是这样的:

    1. JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
    2. 应用的/WEB-INF/classes目录
    3. 应用的/WEB-INF/lib/*.jar
    4. SystemClassloader加载的类(如上所述)
    5. CommonClassloader加载的类(如上所述)

    如果你的应用配置了<loader delegate="true"/>那么查找顺序就会变为:

    1. JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
    2. SystemClassloader加载的类(如上所述)
    3. CommonClassloader加载的类(如上所述)
    4. 应用的/WEB-INF/classes目录
    5. 应用的/WEB-INF/lib/*.jar

    可以发现,如果配置了delegate = true,其实WebAppClassLoader的加载机制就是标准的双亲委托机制了。

    讲到我们发现,好像少了两个类加载器,CatalinaClassLoader和SharedClassLoader。是因为在Tomcat默认实现中直接使用的是CommonClassLoader作为CatalinaClassLoader和SharedClassLoader。

    3. 源码分析

    3.1 类加载器构建

    org.apache.catalina.startup.Bootstrap#main方法中在创建Bootstrap后会调用bootstrap.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方法初始化类加载器,然后通过catalinaLoader加载一些类,然后反射实例化Catalina对象,并反射setParentClassLoader方法,将Catalina的parentClassLoader成员变量设置为sharedLoader。

    3.2 initClassLoaders

    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 {
    
        // 1. 从catalina.properties中获取”name.loader“属性
        // 默认实现中common.loader = ${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
        // server.loader = 
        // shared.loader = 
        String value = CatalinaProperties.getProperty(name + ".loader");
        // 2. 如果在catalina.properties找不到value,直接返回参数的parent
        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));
            }
        }
    
        // 3. 创建类加载器
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

    结合这两个方法,我们可以得知,默认实现中,CommonClassLoader、CatalinaClassLoader、SharedClassLoader其实是一种类加载器,都是CommonClassLoader(也就是说默认Tomcat容器跟容器的web应用之间未隔离)。

    另外需要注意的是,创建CommonClassLoader时,createClassLoader方法parent传参为null,那么CommonClassLoader的父类加载器(类加载器的parent成员)是SystemClassLoader,这部分逻辑在ClassLoaderFactory.createClassLoader中。

    到这里我们发现,上述Tomcat自定义类加载还少了一个WebAppClassLoader没有创建,WebAppClassLoader是在Context的启动方法中创建的。

    3.3 WebAppClassLoader

    StandardContext类的startInterl方法中有如下一段逻辑:

    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader();
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }

    这里创创建的是WebappLoader,并不是我们所说的WebAppClassLoader,那么WebAppLoader和WebAppClassLoader之间有什么关系?WebAppLoader实现了LifeCycle接口,并且内部有一个成员变量classLoader(类型为WebappClassLoaderBase),用于存储真正用于Web服务类加载的类加载器。也就是说,我们可以理解为WebAppLoader是WebAppClassLoader的管理组件。

    那么接下来我们来看一下Tomcat如何通过上述这段代码,管理WebAppClassLoader的。首先调用了WebAppLoader的无参构造函数:

    /**
     * Construct a new WebappLoader. The parent class loader will be defined by
     * {@link Context#getParentClassLoader()}.
     */
    public WebappLoader() {
        this(null);
    }
    
    /**
     * Construct a new WebappLoader with the specified class loader
     * to be defined as the parent of the ClassLoader we ultimately create.
     *
     * @param parent The parent class loader
     *
     * @deprecated Use {@link Context#setParentClassLoader(ClassLoader)} to
     *             specify the required class loader. This method will be
     *             removed in Tomcat 10 onwards.
     */
    @Deprecated
    public WebappLoader(ClassLoader parent) {
        super();
        this.parentClassLoader = parent;
    }

    从注释中我们也可以知道,这里仅仅是构建了一个WebAppLoader对象,其成员变量parentClassLoader设置为null。但是注释中介绍到,parentClassLoader将被赋值为Context#getParentClassLoader(),那么肯定在其它地方,实现了该赋值逻辑。另外这里再着重介绍一下Context#getParentClassLoader(),该方法返回的其实就是我们上面介绍的在Bootstrap类中反射Catalina的setParentClassLoader设置进去的sharedLoader,同时上面也介绍到sharedLoader初始化时,其实跟commonLoader是一个对象,所以这里最终Context#getParentClassLoader()返回的其实是commonLoader

    接下来我们接着看StandardContext#setLoader方法:

    public void setLoader(Loader loader) {
    
        Lock writeLock = loaderLock.writeLock();
        writeLock.lock();
        Loader oldLoader = null;
        try {
            // Change components if necessary
            oldLoader = this.loader;
            if (oldLoader == loader)
                return;
            this.loader = loader;
    
            // Stop the old component if necessary
            if (getState().isAvailable() && (oldLoader != null) &&
                (oldLoader instanceof Lifecycle)) {
                try {
                    ((Lifecycle) oldLoader).stop();
                } catch (LifecycleException e) {
                    log.error(sm.getString("standardContext.setLoader.stop"), e);
                }
            }
    
            // Start the new component if necessary
            if (loader != null)
                loader.setContext(this);
            if (getState().isAvailable() && (loader != null) &&
                (loader instanceof Lifecycle)) {
                try {
                    ((Lifecycle) loader).start();
                } catch (LifecycleException e) {
                    log.error(sm.getString("standardContext.setLoader.start"), e);
                }
            }
        } finally {
            writeLock.unlock();
        }
    
        // Report this property change to interested listeners
        support.firePropertyChange("loader", oldLoader, loader);
    }

    这里Tomcat启动时调用该方法,肯定会调用到loader的start方法,根据我们之前介绍的LifeCycle机制,这里最终会调用到WebAppLoader的startInternal方法:

    protected void startInternal() throws LifecycleException {
    
        if (log.isDebugEnabled())
            log.debug(sm.getString("webappLoader.starting"));
    
        if (context.getResources() == null) {
            log.info(sm.getString("webappLoader.noResources", context));
            setState(LifecycleState.STARTING);
            return;
        }
    
        // Construct a class loader based on our current repositories list
        try {
    
            classLoader = createClassLoader();
            classLoader.setResources(context.getResources());
            classLoader.setDelegate(this.delegate);
    
            // Configure our repositories
            setClassPath();
    
            setPermissions();
    
            classLoader.start();
    
            String contextName = context.getName();
            if (!contextName.startsWith("/")) {
                contextName = "/" + contextName;
            }
            ObjectName cloname = new ObjectName(context.getDomain() + ":type=" +
                    classLoader.getClass().getSimpleName() + ",host=" +
                    context.getParent().getName() + ",context=" + contextName);
            Registry.getRegistry(null, null)
                .registerComponent(classLoader, cloname, null);
    
        } catch (Throwable t) {
            t = ExceptionUtils.unwrapInvocationTargetException(t);
            ExceptionUtils.handleThrowable(t);
            throw new LifecycleException(sm.getString("webappLoader.startError"), t);
        }
    
        setState(LifecycleState.STARTING);
    }

    可以看到,核心逻辑就是classLoader的初始化,通过createClassLoader()方法创建类加载器:

    private WebappClassLoaderBase createClassLoader()
        throws Exception {
    
        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoaderBase classLoader = null;
    
        if (parentClassLoader == null) {
            parentClassLoader = context.getParentClassLoader();
        } else {
            context.setParentClassLoader(parentClassLoader);
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);
    
        return classLoader;
    }
    /**
     * The Java class name of the ClassLoader implementation to be used.
     * This class should extend WebappClassLoaderBase, otherwise, a different
     * loader implementation must be used.
     */
    private String loaderClass = ParallelWebappClassLoader.class.getName();

    我们这里知道,WebAppLoader默认关联的类加载器类型为ParallelWebappClassLoader。然后就是我们上面介绍的,为什么WebAppLoader的成员变量为null,那么将被设置为context#getParentClassLoader(),在这里也给出了答案,我们再createClassLoader()方法中实现了对parentClassLoader的赋值。最后就是调用了ParallelWebappClassLoader的有参构造函数,实现了WebAppLoader关联的类加载器的创建。

    public ParallelWebappClassLoader(ClassLoader parent) {
        super(parent);
    }

    3.3.1 Tomcat是如何打破双亲委托机制的

    其实我们经常说的Tomcat打破了双亲委托机制,其实是指自定义类加载器ParallelWebappClassLoader(WebAppClassLoader)打破了双亲委托机制。按照我们之前对类加载器的理解,ParallelWebappClassLoader肯定自定义实现了loadClass方法。但ParallelWebappClassLoader除了重写了loadClass方法,其实还重写了findClass方法,接下来我们来看一下这两个方法。

    首先来看一下findClass方法(org.apache.catalina.loader.WebappClassLoaderBase#findClass),为了方便阅读,这里省略了一些catch异常以及日志的一些细节。

    public Class<?> findClass(String name) throws ClassNotFoundException {
    
        // (1) Permission to define this class when using a SecurityManager
        // ……
    
        // Ask our superclass to locate this class, if possible
        // (throws ClassNotFoundException if it is not found)
        Class<?> clazz = null;
        try {
            try {
                // 1. 从Web应用目录下查找类
                clazz = findClassInternal(name);
            } catch(AccessControlException ace) {
                // throw exception
            } catch (RuntimeException e) {
                // throw exception
            }
    
            // 2. 如果Web应用目录下为找到类,并且WebAppClassLoader指定了额外加载的路径,
            // 则交给父类去指定的额外加载路径去查找类
            if ((clazz == null) && hasExternalRepositories) {
                try {
                    clazz = super.findClass(name);
                } catch(AccessControlException ace) {
                    // throw exception
                } catch (RuntimeException e) {
                    // throw exception
                }
            }
            if (clazz == null) {
                // throw exception
            }
        } catch (ClassNotFoundException e) {
            if (log.isTraceEnabled())
                log.trace("    --> Passing on ClassNotFoundException");
            throw e;
        }
    
       // log
        return clazz;
    
    }

    从这里可以知道,WebAppClassLoader中默认的是直接在Tomcat本地查找Class,但是提供了参数可以控制从父类,或者指定的目录去查找Class。

    接下来我们来看一下loadClass方法,如下:

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
        synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;
    
            // 1. 先从本地缓存中查找类是否已经加载过
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                // ……
                return clazz;
            }
    
            // 2. 从系统类加载器缓存中查找是否已经加载过
            clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
            if (clazz != null) {
                // ……
                return clazz;
            }
    
            // ……
    
            if (tryLoadingFromJavaseLoader) {
                try {
                    // 3. 尝试使用javaSE classLoader来加载,避免web应用覆盖核心jre类
                    // 这里的javaSE classLoader是ExtClassLoader还是BootstrapClassLoader,要看具体的jvm实现
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        // ……
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
    
            // ……
    
            boolean delegateLoad = delegate || filter(name, true);
    
            // 3. 如果delegateLoad为true,则先使用parent(sharedLoader\commonLoader)加载
            if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader1 " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        // ……
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
    
            // 4. 在Web应用目录中加载类
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
    
            // 5. 如果delegateLoad为false,则在web应用目录中加载后,再使用parent(sharedLoader\commonLoader)加载
            if (!delegateLoad) {
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        // ……
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }
    
        // 6. 如果上述步骤都未加载到Class,抛ClassNotFoundException
        throw new ClassNotFoundException(name);
    }

    Tomcat默认未配置<loader delegate="true"/>,所以默认的类加载顺序为:

    1. JVM中的类库,如rt.jar和$JAVA_HOME/jre/lib/ext目录下的jar
    2. 应用的/WEB-INF/classes目录
    3. 应用的/WEB-INF/lib/*.jar
    4. SystemClassloader加载的类
    5. CommonClassloader(sharedClassLoader)加载的类

    跟JVM默认的双亲委托机制不同,Tomcat会优先加载Web应用目录下的类, 只要该类不覆盖JRE核心类。

    参考链接:

    1. Tomcat源码

    2. 《深入了解Tomcat&Jetty》

    3. Tomcat类加载机制分析

    展开全文
  • Tomcat类加载机制

    千次阅读 2018-10-14 19:26:42
    tomcat组成 tomcat主要由以上组建组成. 整个的http请求是通过tomcat以上组件协调完成的,server和service完成请求的... tomcat类加载 下简述tomcat类加载过程: tomcat的启动方式都是通过在org.apache.catalina.st...
  • 应用程序在启动的时候需要启动虚拟机...也许你会说jvm找不到。但是jvm它是怎么进行加载的呢?加载的机制是怎么样的? jvm采用委托上级的加载机制加载。那么jvm的加载器自上而下四个: BootStraptClassLoader:...
  • tomcat学习之四:tomcat加载机制

    千次阅读 2017-10-27 17:21:13
    tomcat加载机制继承了java加载机制中经典的双亲委派模型。所以要了解tomcat加载机制需要先了解双亲委派模型。  在程序中用到的需要由加载器将的class文件加载到内存中,然后经由JVM验证、解析、...
  • Web容器和Tomcat关系

    2019-08-29 00:07:22
    Servlet实质就是一个有特殊继承关系要求的Java,理解Servlet就基本上完全理解Java Web技术了。 四、Web模块   在Java EE技术体系中,除了Java Web以外,还包括EJB等其他技术。每个技术体系会运行在各自的...
  • servlet(3.0以上版本) ... servlet为主启动时 servlet容器启动时,会通过SPI,在一个文件中找实现了ServletContainerInitializer接口的, 该被容器自动加载并执行其onStart方法。 当spring需要被...
  • servlet与tomcat关系

    万次阅读 多人点赞 2018-03-21 16:51:29
    1.Tomcat 一接受到请求首先将会创建 【org.apache.coyote.Request】 和 【org.apache.coyote.Response】,这两个Tomcat 内部使用的描述一次请求和相应的信息它们是一个轻量级的,它们作用就是在服务器接收...
  • Tomcat加载机制是违反了双亲委托原则的,对于一些未加载的非基础(Object,String等),各个web应用自己的加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。 对于JVM来说...
  • Tomcat类加载器

    2020-04-21 18:27:40
    Tomcat类加载器 Tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托...
  • 图解Tomcat类加载机制

    2019-11-21 17:17:52
    Tomcat加载机制是违反了双亲委托原则的,对于一些未加载的非基础(Object,String等),各个web应用自己的加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。 对于JVM来说...
  • tomcat9加载器

    2019-06-10 16:49:37
    我们都知道,tomcat9加载器结构如下: BoostrapClassLoader:启动加载器,它是用本地代码实现的装载器,负责将 JDK 中 jre/lib 下的 类库 或者 XBootClassPath指定的类库加载到内存中,开发者无法直接获取...
  • JVM加载 1.根加载器:BootstrapClassLoader 由C++编写,没有父加载器,加载JVM核心类库(jdk/jre/lib目录下),没有继承java.lang.ClassLoader 2.扩展加载器:ExtensionClassLoader 由Java实现,父...
  • 上一篇讲完了spring cloud,Spring boot,Tomcat容器之间关系以及执行顺序,这次再继续讲当一个请求到达服务时,执行的流程: 首先我们得把Tomcat讲清楚: 综上所述,一个tomcat只包含一个Server,一个Server可以...
  • 一个tomcat中部署的多个app,虽然同处一个JVM里,但是由于无法相互调用(因为被加载器隔离开的),所以也可以认为是分布式的 扩展:Tomcat和JVM的内存配置/性能优化 JVM内存分配设置的参数有四个: (1)...
  • Tomcat和Servlet的关系

    千次阅读 2020-08-18 18:32:03
    Tomcat Tomcat是一个Web应用服务器,是Apache开源软件组织下的一个软件项目。Tomcat作为Web服务器,可以通过Http协议与客户端(浏览器)进行数据交互。同时也可以来发布Web应用。tomcat与Web应用是由不同的开发商...
  • Tomcat加载机制

    2019-01-30 23:20:17
    关于Java的加载机制推荐这...Tomcat加载机制是违反了双亲委托原则的,对于一些未加载的非基础(Object,String等),各个web应用自己的加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoa...
  • tomcat加载器

    2020-03-21 23:06:12
    一、tomcat是个web容器,要解决以下问题 1. 一个web容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离的。 2. 部署...
  • tomcat其实是一个用java语言开发的免费开源的web服务器(因为是java语言开发,这就是为什么使用tomcat前要配置好jdk,因为jdk里面有jvm,而运行java应用需要jvm)。此时再次查看任务管理器会发现多了一个javaw.exe ...
  • 这是我们研究Tomcat的第四篇文章,前三篇文章我们搭建了源码...按照我们最初订的计划,今天,我们要开始研究tomcat的几个主要组件(组件太多,无法一一解析,解析几个核心),包括核心的加载器,连接器和容器,还...
  • tomcat 与 jvm的关系

    万次阅读 多人点赞 2018-07-22 19:29:29
    tomcat跑起来,就得有jdk,而jdk自带了JVM,安装完了jdk,把安装jkd的bin目录告诉tomca,就可以了。 2,有的web服务器不必安装jdk,因为自带了,如weblogic。 3,tomcat web容器,主要和有jsp和servlet有关, ...
  • 深入剖析Tomcat类加载机制+图解

    千次阅读 2017-06-11 11:32:59
    1JVM加载机制JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构。其中引导、扩展、系统三个加载器是JVM内置的。 它们的作用分别是: 1)引导加载器:使用native代码实现,在rt.jar等包中...
  • tomcat和jvm的关系,总结几句话: ▲ 运行一个Java程序(一个main方法),就会产生一个jvm实例,(我猜)此jvm实例的内存大小为默认大小或者如果给jvm设置了大小就按照设置的大小。main方法执行过程中产生的对象、...
  • 图解Tomcat类加载机制(阿里面试题) Tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给...
  •  今天因为把Tomcat的版本从8升级到了9,而Tomcat9版本需要java8及其以上的版本,于是又开始了各种环境变量,Tomcat服务,开发工具的jre路径修改。记得初学java的时候,老师告诉我们需要配置java运行环境,其大致的...
  • 附1 内嵌式tomcat配置 1-1 使用Spring原生容器AnnotationConfigApplicationContext 注解加载配置方式 public class Application { ... Tomcat tomcat = new Tomcat(); tomcat.setPort(8080); tomca.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 110,321
精华内容 44,128
关键字:

tomcat类关系图