精华内容
下载资源
问答
  • Tomcat类加载机制探析

    2020-12-30 02:07:26
    title: Tomcat类加载机制探析 comments: false toc: true categories: Web服务器 ...首先咱们还是老调重弹,看一下网上已经泛滥的一张Tomcat类加载关系图 属于JavaSE的BootstrapClassLoader、ExtClassL.

    title: Tomcat类加载机制探析
    comments: false
    toc: true
    categories:

    • Web服务器
      tags:
    • Tomcat
    • 类加载
    • 双亲委派
      date: 2020-12-23 23:03:58

    Java类加载机制,双亲委托模型相必大家已经熟的不能再熟了。本文就从Tomcat的源码级别上来探究一下类加载机制的秘密。

    首先咱们还是老调重弹,看一下网上已经泛滥的一张Tomcat类加载关系图 Tomcat类加载器关系图

    属于JavaSE的BootstrapClassLoader、ExtClassLoader、AppClassLoader(这里主要加载Tomcat启动的Bootstrap类)在本文不再赘述;

    • CommonClassLoader加载common.loader属性下的jar;一般是CATALINA_HOME/lib目录下

    • CatalinaClassLoader加载server.loader属性下的jar;默认未配置路径,返回其父加载器即CommonClassLoader

    • SharedClassloader加载share.loader属性下的jar;默认未配置路径,返回其父加载器即CommonClassLoader

      由于WebAppClassLoader需要等Tomcat的各个组件初始化完成之后才加载对应的server.xml配置文件,解析对应Host下的docBase目录寻找WEB-INF下的类文件,并且会将该根目录下的每个直接子目录当作一个web项目加载,为了确保各个项目之间的相互独立,每个项目都是单独的WebAppClassLoader加载的,咱们后文再讲。

    CommonClassLoader、CatalinaClassLoader、SharedClassloader

    先从源码角度来看看CommonClassLoader、CatalinaClassLoader、SharedClassloader这三者是如何绑定关系的?

    org.apache.catalina.startup.Bootstrap#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);
        }
    }
    

    调用了createClassLoader方法来创建对象,第二个参数是指定它的父级类加载器。可以看到catalinaLoader、sharedLoader均指明commonLoader为它的父级类加载器,这说明catalinaLoader、sharedLoader是同级类加载器,印证了上图。

    再看看createClassLoader方法怎么做的:

    // 创建基础类型类加载器
    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
    
        // 根据“name+.loader”从系统配置中读取需要加载的jar路径
        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
            }
    
            // 封装路径,指定其jar的类型:jar包、目录等
            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));
            }
        }
        // 通过类加载工厂创建,repositories的值为当前Tomcat所在目录下配置的jar子目录,比如 Tomcat_Home/lib/
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
    

    ClassLoaderFactory.createClassLoader是一个典型的工厂模式,屏蔽了类加载器对象初始化的细节:

    public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {
    
        if (log.isDebugEnabled())
            log.debug("Creating new class loader");
    
        // Construct the "class path" for this class loader
        Set<URL> set = new LinkedHashSet<>();
    
        if (repositories != null) {
            // 根据jar包类型解析其Url,并添加到Set<URL> set
            for (Repository repository : repositories)  {
                if (repository.getType() == RepositoryType.URL) {
                    URL url = buildClassLoaderUrl(repository.getLocation());
                    set.add(url);
                } else if (repository.getType() == RepositoryType.DIR) {
                    File directory = new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    if (!validateFile(directory, RepositoryType.DIR)) {
                        continue;
                    }
                    URL url = buildClassLoaderUrl(directory);
                   
                    set.add(url);
                } else if (repository.getType() == RepositoryType.JAR) {
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    if (!validateFile(file, RepositoryType.JAR)) {
                        continue;
                    }
                    URL url = buildClassLoaderUrl(file);
                    
                    set.add(url);
                } else if (repository.getType() == RepositoryType.GLOB) {
                    File directory=new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    if (!validateFile(directory, RepositoryType.GLOB)) {
                        continue;
                    }
                   
                    String filenames[] = directory.list();
                    if (filenames == null) {
                        continue;
                    }
                    for (int j = 0; j < filenames.length; j++) {
                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                        if (!filename.endsWith(".jar"))
                            continue;
                        File file = new File(directory, filenames[j]);
                        file = file.getCanonicalFile();
                        if (!validateFile(file, RepositoryType.JAR)) {
                            continue;
                        }
                        if (log.isDebugEnabled())
                            log.debug("    Including glob jar file "
                                + file.getAbsolutePath());
                        URL url = buildClassLoaderUrl(file);
                        set.add(url);
                    }
                }
            }
        }
    
        // Construct the class loader itself
        final URL[] array = set.toArray(new URL[set.size()]);
    
        // 直接通过URLClassLoader创建ClassLoader
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
    }
    

    可以看到通过ClassLoaderFactory.createClassLoader(List repositories, final ClassLoader parent)此方法创建的ClassLoader其本身是一个URLClassLoader,通过人为手工的方式分别指定了其父级类加载。

    上文提到catalinaLoader会加载Tomcat本身的类,其主要目的是进行类加载隔离,与SharedClassloader区分开,这样咱们开发人员编写的web项目就不能直接访问到Tomcat的类,造成安全问题了。 那么又是怎么实现的呢?咱们接着看Tomcat的启动前的初始方法org.apache.catalina.startup.Bootstrap#init()

    public void init() throws Exception {
    	// 初始化commonLoader、catalinaLoader、sharedLoader3个类加载器
        initClassLoaders();
    	// 直接设置当前线程的类加载器为catalinaLoader
        Thread.currentThread().setContextClassLoader(catalinaLoader);
    
        SecurityClassLoad.securityClassLoad(catalinaLoader);
    
        // 使用当前线程的catalinaLoader来加载Catalina类
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
    
        // 以下整段表示:Catalina初始化后得到的对象startupInstance调用setParentClassLoader方法,将sharedLoader设置为父加载器
        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;
    }
    

    从源码中可以看到步骤:

    • 用catalinaLoader加载了"org.apache.catalina.startup.Catalina"类;
    • 立即通过setParentClassLoader方法指定其父加载器为sharedLoader,这样Catalina对象也可访问sharedLoader下的类了;

    如何为每一个webApp项目定义一个独立的WebAppClassLoader?

    咱们从源码角度来分析一下。

    Tomcat的各容器调用流程不是本文关注的重点,直接查看webApp的类加载器初始化的代码位置org.apache.catalina.loader.WebappLoader#startInternal()

    // 每一个项目context均会执行此方法
    protected void startInternal() throws LifecycleException {
    
        // 省略部分代码
    
        // Construct a class loader based on our current repositories list
        try {
    		// 创建WebApp的类加载器
            classLoader = createClassLoader();
            // 获取一个webapp项目的类加载路径:WEB-INF/classes、WEB-INF/lib/
            classLoader.setResources(context.getResources());
            // delegate是否遵循类加载的双亲委派模型,默认为false
            classLoader.setDelegate(this.delegate);
    
    		// 执行类加载
            ((Lifecycle) classLoader).start();
    
            // 省略
    
        } catch (Throwable t) {
            // 省略
        }
    
        // 省略
    }
    
    
    private String loaderClass = ParallelWebappClassLoader.class.getName();
    /**
      * Create associated classLoader.
      */
    private WebappClassLoaderBase createClassLoader()
        throws Exception {
    
        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoaderBase classLoader = null;
    
        if (parentClassLoader == null) {
            parentClassLoader = context.getParentClassLoader();
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);
    
        return classLoader;
    }
    

    当每一个HOST执行org.apache.catalina.startup.HostConfig#deployApps()方法发布所有的项目时,其工作目录下的每个webapp项目均会执行org.apache.catalina.loader.WebappLoader#startInternal()方法,并在其中使用createClassLoader()创建类加载器。可以从createClassLoader()方法看到 WebappClassLoaderBase的真正实现类为ParallelWebappClassLoader。

    ParallelWebappClassLoader类继承于WebappClassLoaderBase,类结构如下:

    ParallelWebappClassLoader

    WebappClassLoaderBase继承于URLClassLoader,由URLClassLoader可指定加载位置的Path来进行类加载。

    还记得上面的 this.delegate 字段吗?默认为false,表示不使用双亲委派机制,我们再看看是如何破坏双亲委派机制的。方法位置:org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)

    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
        synchronized (getClassLoadingLock(name)) {
            
            Class<?> clazz = null;
    
            // 省略一些检查代码
    
            // (0) 从本地字节码缓存中查找
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
    
            // (0.1) 从已经加载过的类中查找
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
    
            // (0.2) 尝试使用系统类加载器加载该类,以防止webapp覆盖Java SE类。This implements SRV.10.7.2
            String resourceName = binaryNameToPath(name, false);
    
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                //使用getResource,因为如果Java SE类加载器无法提供该资源,它将不会触发昂贵的ClassNotFoundException。
                //但是,在极少数情况下在安全管理器下运行时(有关详细信息,请参见https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 //),此调用可能触发ClassCircularityError。
                URL url;
                if (securityManager != null) {
                    PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                    url = AccessController.doPrivileged(dp);
                } else {
                    url = javaseLoader.getResource(resourceName);
                }
                tryLoadingFromJavaseLoader = (url != null);
            } catch (Throwable t) {
                // 系统类加载器不适合加载此类,捕捉异常不再往外抛出
                ExceptionUtils.handleThrowable(t);
                tryLoadingFromJavaseLoader = true;
            }
    		// 尝试通过JavaSE类加载器加载
            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
    
            // (0.5) 使用SecurityManager时访问此类的权限
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }
    		
            //默认为不使用双亲委派,调用filter进行过滤是否可以委派加载此方法,如果是JSP,EL表达式,Tomcat等一些类则可以委派
            boolean delegateLoad = delegate || filter(name, true);
    
            // (1) 如果需要,委托给我们的父加载器
            if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader1 " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
    
            // (2) 从本地搜索查找类即自我加载
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
    
            // (3) 实在找不到,无条件委托给父加载器
            if (!delegateLoad) {
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }
    	// 所有方法均加载失败,返回ClassNotFoundException
        throw new ClassNotFoundException(name);
    }
    

    从源码中,可以提取为以下步骤:

    • (0) 从本地字节码缓存中查找
    • (0.1) 从已经加载过的类中查找
    • (0.2) 尝试使用系统类加载器加载该类,以防止webapp覆盖Java SE类。
    • (0.3) 系统类加载器不适合加载此类
    • (0.4) 尝试通过JavaSE类加载器加载
    • (0.5) 使用SecurityManager时访问此类的权限
    • (1) 判断是否双亲委派(一般为false),如果需要,委托给我们的父加载器
    • (2) 使用自己加载器进行类加载,注意此步骤如果执行则破坏了双亲委派原则,因为没有让父类加载器进行加载。
    • (3) 实在找不到,无条件委托给父加载器
    • 所有方法均加载失败,返回ClassNotFoundException

    总结

    Java的双亲委派机制无疑是一个非常优秀的设计,它有以下优点:

    1. 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
    2. 增加系统安全,java核心API中定义类型不会被随意替换。

    但是在Tomcat这种需要多项目部署时,其反而可能会有一些弊端,比如刚好部署了A,B两个字节码完全相同的系统,如果是传统的双亲委派机制,则后加载的系统的类不会加载成功,如果有类静态变量则会被2个系统共享,引起系统异常。

    展开全文
  • 一、tomcat类加载器关系 tomcat类加载器设计结构如上所示,上面三个Bootstrap加载器、Ext加载器、Application加载器是JVM加载器,下半部分的加载器才是tomcat自定义的加载器。学习tomcat类加载器之前先回顾下之前...

    一、tomcat类加载器关系

    49b0b72d2b34f0fb967f9fcd835a0619.png

        tomcat类加载器设计结构如上图所示,上面三个Bootstrap加载器、Ext加载器、Application加载器是JVM加载器,下半部分的加载器才是tomcat自定义的加载器。

        学习tomcat类加载器之前先回顾下之前写过的jvm类加载器的知识点。

    ·  类加载机制

        类加载机制的本质是将经过虚拟机编译后的.class文件转化为二进制,并将二进制加载成Class对象的加载过程。

        因此判断类是否完全相同,需要以下两个条件即:类的全路径名是否相同、加载类的类加载器是否为同一个,这两个条件决定类是否完全相同。

    ·  双亲委派机制

        双亲委派机制的存在,保证类是安全的,他的存在保证了类的加载顺序会优先委派父类加载器进行加载,如果父类加载器没有加载才会有子类进行加载。比如在JVM类加载器的继承关系(从子到父)为Application ClassLoader—>Extension ClassLoader—>Bootstrap ClassLoader。因此当自己在代码内部中编写恶意java代码时,比如HashMap文件,如果路径名相同,则会委派给父类Bootstrap ClassLoader进行加载,发现在Bootstrap加载的lib目录下存在HashMap文件则加载应该由Bootstrap加载的HashMap文件。从而保证自己编写的恶意java代码不会替换应由Bootstrap加载的源文件。

    ·  打破双亲委派机制

        由于虚拟机中的加载规则是按需加载的,即需要用到什么类的时候才会去加载那个类。并且在加载该类时用的是什么加载器,那么加载该类引用的类也需要用到对应的加载器,在java中的SPI机制,加载jdbc时由于Driver类不在rt.jar中因此不能被Bootstrap加载器进行加载,因此使用了线程上下文类加载器委派子类进行加载。所以打破了双亲委派机制,并且在tomcat类加载器中也存在打破双亲委派机制的情况。

    二、tomcat类加载器

    ·  Common ClassLoader

        Common ClassLoader是tomcat最基本的类加载器,被此加载器加载的类即可以被tomcat所访问,也可以被应用war包中的程序所访问。

    ·  Catalina ClassLoader

        Catalina ClassLoader是tomcat私有的类加载器,被此加载器加载的类,只能被tomcat所访问。

    ·  Shared ClassLoader

        Shared ClassLoader是各个war包共享的类加载器,被此加载器加载的类,只能被应用war包中的程序所访问。

        当tomcat中存在多个war包并同时使用了相同版本的jar包时,为了减少资源的浪费,可以使用该加载器,抽出这些相同版本的jar包,使用Shared ClassLoader加载一次被共享的jar即可,来代替每个war包中都需要加载的过程。

    ·  WebApp ClassLoader

        WebApp ClassLoader是多个war包的类加载器,即tomcat中的一个war包由一个WebApp ClassLoader加载。

        Shared ClassLoader和WebApp ClassLoader的存在可以让共享的jar包由Shared ClassLoader加载,其余不一样或需要独立部署的由WebApp ClassLoader进行加载,达成共享与隔离的统一。

    ·  JSP ClassLoader

        JSP ClassLoader是一个jsp文件的类加载器,即tomcat中存在一个jsp就会new出一个JSP ClassLoader进行jsp文件的加载。

        JSP ClassLoader是一个特殊的类加载器,他的特点也保证了jsp文件可以进行热部署,因为当对jsp文件进行修改,重新加载该jsp文件时,又会new出一个JSP ClassLoader来加载jsp文件,替换修改前的jsp文件(此时需要注意的是jsp文件的类的唯一性也变了,类的唯一性由类的全路径名+类加载器决定,此时的类加载器是重新new出来的所以和修改前的加载器是完全不一样的)。而Controller、service等文件的修改前和修改后是由相同的WebApp ClassLoader加载的,因此不能在这种情况下和jsp一样实现实现修改后的热部署。

    ·  源码中的加载器关系

    private void initClassLoaders() {try {//common加载器,父类为null        commonLoader = createClassLoader("common", null);        if (commonLoader == null) {
    commonLoader = this.getClass().getClassLoader(); }//catalina加载器,父类为commonLoader catalinaLoader = createClassLoader("server", commonLoader); //shared加载器,父类为commonLoader sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) {
    handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); }
    }

        但是创建common加载器时,传入的是null,如何判断common加载器的父类是ApplicationClassLoader呢。

    81012f8f909013d13ee16bf1df95c45e.png

        跟到底层会发现获取系统类加载器为父类加载器,即AppClassLoader。其余源码的分析在类加载详解中有进行过分析,这里不再做详细阐述。

    三、tomcat类加载流程

    b14c0b323dbff3bd01338d3f691558b1.png

    ·  Bootstrap

        主要加载JVM启动所需要的类。

    ·  System

        主要加载tomcat启动的类,比如bootstrap.jar。位于bin/bootstrap.jar。

    c373d1f05fde96e57df362d856813c66.png

    ·  Common

        主要加载tomcat中常用的类,位于lib中。

    402c36872a440dde243a715937944480.png

    ·  WebApp

        主要加载应用的类文件,即位于WEB-INF/lib下的jar文件和WEB-INF/classes下的class文件。

    四、tomcat类加载源码分析

    public Class> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}//重写了loadClass方法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; //校验程序是否启动,如果已启动抛出异常 checkStateForClassLoading(name); //校验当前对象缓存是否加载过该类 clazz = findLoadedClass0(name); //如果已经加载过该类 则直接返回 if (clazz != null) {if (log.isDebugEnabled())
    log.debug(" Returning class from cache"); if (resolve)
    resolveClass(clazz); return clazz; }//校验是否加载过该类,会调用native方法 clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name); if (clazz != null) {if (log.isDebugEnabled())
    log.debug(" Returning class from cache"); if (resolve)
    resolveClass(clazz); return clazz; }
    String resourceName = binaryNameToPath(name, false); ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; try {//使用javaseLoader加载 // 即-使用ExtClassLoader/BootstrapClassLoader判断是否需要加载这个calss // 防止先被webAppClassLoader加载了脏的类 URL url; if (securityManager != null) {
    PrivilegedAction dp = new PrivilegedJavaseGetResource(resourceName); url = AccessController.doPrivileged(dp); } else {
    url = javaseLoader.getResource(resourceName); }
    tryLoadingFromJavaseLoader = (url != null); } catch (Throwable t) {
    ExceptionUtils.handleThrowable(t); tryLoadingFromJavaseLoader = true; }if (tryLoadingFromJavaseLoader) {try {
    clazz = javaseLoader.loadClass(name); if (clazz != null) {if (resolve)
    resolveClass(clazz); return clazz; }
    } catch (ClassNotFoundException e) {
    }
    }//安全策略校验 if (securityManager != null) {int i = name.lastIndexOf('.'); if (i >= 0) {try {
    securityManager.checkPackageAccess(name.substring(0, i)); } catch (SecurityException se) {
    String error = sm.getString("webappClassLoader.restrictedPackage", name); log.info(error, se); throw new ClassNotFoundException(error, se); }
    }
    }//判断是否需要被WebAppClassLoader进行加载,是否设置了delegate boolean delegateLoad = delegate || filter(name, true); //委托给父类 CommonClassLoader加载 if (delegateLoad) {if (log.isDebugEnabled())
    log.debug(" Delegating to parent classloader1 " + parent); try {
    clazz = Class.forName(name, false, parent); if (clazz != null) {if (log.isDebugEnabled())
    log.debug(" Loading class from parent"); if (resolve)//加载成功则返回 resolveClass(clazz); return clazz; }
    } catch (ClassNotFoundException e) {
    }
    }//使用WebAppClassLoader进行加载 if (log.isDebugEnabled())
    log.debug(" Searching local repositories"); try {
    clazz = findClass(name); if (clazz != null) {if (log.isDebugEnabled())
    log.debug(" Loading class from local repository"); if (resolve)
    resolveClass(clazz); return clazz; }
    } catch (ClassNotFoundException e) {
    }//WebAppClassLoader加载失败,则委托WebAppClassLoader的父类进行加载 if (!delegateLoad) {if (log.isDebugEnabled())
    log.debug(" Delegating to parent classloader at end: " + parent); try {
    clazz = Class.forName(name, false, parent); if (clazz != null) {if (log.isDebugEnabled())
    log.debug(" Loading class from parent"); if (resolve)
    resolveClass(clazz); return clazz; }
    } catch (ClassNotFoundException e) {// Ignore }
    }
    }//加载不到 抛出异常 throw new ClassNotFoundException(name);}

        上述源码分析中,tomcat的类加载步骤为(delegate设置为true的情况):

    (1)判断当前被加载的类对象是否存在缓存中,如果存在则直接返回该类。

    (2)判断是否加载过该类,如果加载过则直接返回该类。

    (3)委派jvm中的父类ExtClassLoader、BootStrapClassLoader是否加载过该类。

    (4)委派tomcat中的父类CommonClassLoader是否加载过该类。

    (5)当前WebAppClassLoader自己加载该类。

    (6)如果类仍然没有加载成功,则抛出异常。

    五、tomcat打破双亲委派加载机制

        不论加载的delegate配置设置为true或者false,tomcat加载类的顺序都为先查询当前WebAppClassLoader类加载器是否加载过该类,而不是预先委托父类进行加载。

        第二点是当delegate设置为false时,上述的第四步和第五步可能也会打破双亲委派,优先使用WebAppClassLoader加载器进行加载,加载不到才委托父类进行加载。

    展开全文
  • tomcat加载机制

    2020-04-21 18:12:39
    java的双亲委派模型如下所示(组合方式实现继承关系): Bootstrap加载器:负责加载jre/lib下的jar,例如rt.jar是java的核心类库,Object、String、System等常用都存在于rt.jar中。这个加载器存在于虚拟机...

    tomcat的类加载机制遵循了java类加载机制中经典的双亲委派模型。所以要了解tomcat的类加载机制需要先了解双亲委派模型。

    双亲委派模型

    java的双亲委派模型如下图所示(组合方式实现继承关系):
    在这里插入图片描述
    Bootstrap类加载器:负责加载jre/lib下的jar,例如rt.jar是java的核心类库,Object、String、System等常用类都存在于rt.jar中。这个加载器存在于虚拟机中,如果是HotSpot虚拟机,这个类加载器是C++语言编写,如果虚拟机本身就是由纯java语言实现,那就是由java编写。如果想要使用这个加载器,可以使用参数-Xbootclasspath指定加载路径,但boostrap类加载器只会加载虚拟机识别的类库,例如rt.jar,否则即使名字不符合,放在lib中也不会被加载。

    Extension类加载器:扩展类加载器,负责加载jre/lib/ext中的jar,或者java.ext.dirs系统变量指定路径的类库。

    Application类加载器:负责加载用户类路径上指定的类库,由于ClassLoader的getSystemClassLoader方法的返回值,所以也叫作系统类加载器。

    tomcat的类加载器

    为什么tomcat要实现自己的类加载器?
    tomcat作为一个服务器,在它上面可以部署多个应用。默认情况下是使用1中的应用加载器加载类的,但往往部署的应用都会有多个jar依赖,所以第一点为了解决依赖问题,必须保证每个应用的类库独立。 为什么要保证每个应用的类库相互独立呢? 打个比方,我们都知道连接数据库需要数据库驱动,而数据库驱动的版本要与连接的数据库相对应,否则无法获取连接。 那假设部署在tomcat上的两个应用依赖的数据库驱动版本有很大差异,这样直接使用java的系统类加载器会导致这两个应用有一个无法启动。

    a)、要保证部署在tomcat上的每个应用依赖的类库相互独立,不受影响。
    b)、由于tomcat是采用java语言编写的,它自身也有类库依赖,为了安全考虑,tomcat使用的类库要与部署的应用的类库相互独立。
    c)、有些类库tomcat与部署的应用可以共享,比如说servlet-api,使用maven编写web程序时,servlet-api的范围是provided,表示打包时不打包这个依赖,因为我们都知道服务器已经有这个依赖了。
    d)、部署的应用之间的类库可以共享。这听起来好像与第一点相互矛盾,但其实这很合理,类被类加载器加载到虚拟机后,会生成代表该类的class对象存放在永久代区域,这时候如果有大量的应用使用spring来管理,如果spring类库不能共享,那每个应用的spring类库都会被加载一次,将会是很大的资源浪费。

    于存在上述问题,tomcat实现了自己的类加载器,不仅仅是tomcat,所有的服务器基本都有或多或少上述所说的问题。

    tomcat的类加载器设计

    tomcat的类加载器设计如下图所示:
    在这里插入图片描述
    ps: 其中蓝色的类加载器为tomcat自己实现的类加载器。

    Common类加载器:负责加载/common目录的类库,这儿存放的类库可被tomcat以及所有的应用使用。

    Catalina类加载器:负责加载/server目录的类库,只能被tomcat使用。

    Shared类加载器:负载加载/shared目录的类库,可被所有的web应用使用,但tomcat不可使用。

    WebApp类加载器:负载加载单个Web应用下classes目录以及lib目录的类库,只能当前应用使用。

    Jsp类加载器:负责加载Jsp,每一个Jsp文件都对应一个Jsp加载器。

    Tomcat运行期间,Webapp类加载器与Jsp类加载器个数为复数。通过上图的设计,可以解决掉2.1中的问题。

    看到这儿你可以翻出tomcat目录结构查看一下,然后你会发现根本没有common、shared、server目录。这是因为只有在conf目录下的catalina.properties指定了server.loader 以及 share.loader两个属性tomcat才会建立CatalinaClassLoader和SharedClassLoader实例,而默认情况下都没指定,所以CatalinaClassLoader以及SharedClassLoader都会使用CommonClassLoader来代替,所以tomcat6.x以上顺理成章地把上述三个目录合并成了一个lib目录。这个目录相当于/common目录的作用。

    展开全文
  • 一. 什么是加载器? “通过一个的全限定名来获取描述此类的二进制字节流”...中展示的加载器之间的这种层次关系,称为加载器的双亲委派模型(Parents Delegation Model)   2.1 双亲委派模型的工作...

    一. 什么是类加载器?

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

     

    二. 什么是双亲委派模型?

    类加载器的双亲委派模型

    图中展示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)

     

    2.1 双亲委派模型的工作过程

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

     

    2.2 双亲委派模型优点

    Java类随着它的类加载器一起具备了一种带有优先级的层次关系,保证"JAVA类"在程序的各种类加载器环境中都是同一个类。

    例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的Class Path中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。

     

    三. 为什么打破双亲委派模型?

          如果基础类又要调用回用户的代码,那该怎么办?

          这并非是不可能的事情,一个典型的例子便是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代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

          双亲委派模型的另一次“被破坏”是由于用户对程序动态性的追求而导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换(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的实现,就可以算是掌握了类加载器的精髓。

     

     4. 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自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,其关系如下图所示。

    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了。

     

     5. Spring Boot 程序中ClassLoader 对比图

     

    展开全文
  • Tomcat和servlet的关系

    2016-09-28 14:14:21
    1、以下都是一个菜菜鸟的理解;...3、做了一个测试,由下面1,2,可知,可以把tomcat理解为一个写了main函数,然后调用开发者写的servlet这些1 4、实际上这个过程是(结合conf/server.x
  • tomcat源码思维导图

    2020-11-10 11:10:02
    tomcat和nginx深度剖析内部调优 tomcat启动流程源码 tomcat完整版解析【重要】 tomcat视图和优化 tomcat 架构 tomcat xml 内容解析 Tomcat handler类关系 tomcat pipeline
  • 加载器--Tomcat--ParallelWebappClassLoader

    千次阅读 2018-11-23 15:22:23
    首先是jvm自带的三个加载器的关系图: 系统加载器在加载一个时,会先查找已经加载的,如果没找到,再委托父加载器(父加载器不是父类,这是2个概念),父加载器没找到就继续委托父加载器,直到所有的父...
  • 1 项目整体结构的设计为了让新...思路模型:项目结构:web.xml映射文件中的内容,用于定义URL请求映射路径和java之间的对应关系logincom.bjsxt.servlet.LoginServletlogin/login/login22 Entity实体的处理/*...
  • 介绍容器初始化过程之前,让我们先来看下tomcat容器的继承关系图。 Lifecycle:顶级接口:定义了添加,删除,查找生命周期监听器的方法,以及init,stop等。 MBeanRegistration:顶级接口,定义一些注册jmx其后的...
  • 1 生命周期 1.1 观察者模型 tomcat生命周期采用了观察者模式,所以在介绍生命周期的时候不得不介绍观察者... 根据UML可以看出所有被观察的对象Observer的实现(可以有多个具体实现)被添加到观察者Subject的实
  • tomcat源码学习

    2015-06-25 11:07:29
    读源码时首先要大致理解各个核心模块之间的关系,核心架构如下,一图胜千言 下面是tomcat的运行流程的概括,需要编译tomcat源码后debug一步步配合源码来看。 org.apache.catalina.startup.Bootstrap是...
  • 本文将从手写一个微型服务器,模拟实现...反射及容器集合框架的相关知识第一步:我们先要按上的包类关系,把整个服务器的项目框架搭建好,然后编写XML文档,最后写IOCloseUtil.java工具类。以下是XML文档和IOClos...
  • tomcat学习笔记

    2018-04-26 19:51:59
    初学tomcat,做下笔记...2.tomcat代码结构,抄的大神的,很形象。主要是层级关系,service包含多个connector和一个container3.生命周期管理,主要是以下几个,摘抄如下Lifecycle:定义了容器生命周期、容器状态...
  • Processor是一个接口,...然后起一个中间作用转发到 Adater,下面是其关系图 其实现中我们常用的http协议,所以一般是左边的部分,用红线标注1.1 循环队列protected static class RecycledProcessors<P extends P
  • 1 项目整体结构的设计为了让新...思路模型:项目结构:web.xml映射文件中的内容,用于定义URL请求映射路径和java之间的对应关系logincom.bjsxt.servlet.LoginServletlogin/login/login22 Entity实体的处理/*...
  • Tomcat 初始化及启动过程

    千次阅读 2018-04-23 13:50:27
    Tomcat中很多组件具有生命周期,如初始化、启动、关闭,这些组件的生命周期具有共性,因此Tomcat中将其抽象...继承关系图Tomcat加载器架构 先是Bootstrap.initClassLoaders()设置加载器:CommonClas...
  • Tomcat工作原理

    2018-12-17 14:07:32
    Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图如下: 它的标准实现是 StandardEngine,这个注意一点就是 Engine 没有父容器了,如果调用 setParent 方法时将会报错。添加子容器也只能是 Host...
  • Web 前端学习 之Tomcat

    2020-08-13 09:40:01
    Web 前端学习 之Tomcat ...Tomcat 服务器和Servlet 版本的对应关系 Tomcat 的使用 常见的启动失败的情况有,双击startup.bat 文件,就会出现一个小黑窗口一闪而来。 这个时候,失败的原因基本上都是因为
  • 容器核心框架Tomcat的核心类图Tomcat 请求流程 前言 刚入手JavaWeb项目时,还是通过手动配置Tomcat来部署项目,除此之外,还需要理清各个Jar包之间的关系,稍有不慎就会出现Jar包丢失或者应用版本冲突导致的服务...
  • tomcat的load过程是从Bootstrap的load方法开始的。load方法通过反射调用Catalina的load方法。...容器组件等级请查看tomcat组成。 那么首先阅读Digester实例化容器组件部分的源码。 protected Digest
  • 本篇文章较长 重要的是思路!!!之后你看Tomcat的任何一个的源码都可以根据这种思路去定位和...从上我们可以看到server.xml文件中的标签与右边的架构的组件具有相同的相对位置 而在源码中他们更是具有相同的关系
  • how-tomcat-works

    2011-06-30 13:09:09
    catalina结构 1 tomcat的版本4和版本5 2 章节简介 2 第1章 一个简单的Web服务器 3 1.1 The Hypertext Transfer Protocol (HTTP) 3 1.2 HTTP Request 3 1.3 HTTP Response 4 1.4 Socket 4 1.5 ServerSocket 5 ...
  • Tomcat之JVM调优

    2019-11-13 09:38:39
    其中持久代主要存放的是Java信息,与垃圾收集器需要收集的Java对象关系不大,而年轻代和年老代的划分是对垃圾收集影响比较大的。 年轻代 所有新生成的对象首先都是放在年轻代,年轻代的目标是尽可能快速的...
  • 以上是默认连接器UML。 Connector和Container是一对一的关系,从箭头的方向可以看出Connector是知道Container的,但反过来就不成立了。别外需要注意的是和上一章不同,HttpConnector和HttpProcessor是一对多的...
  • 内嵌的Tomcat流程解析 一.@Import({ ...BeanPostProcessorsRegistrar是一个静态内部,继承以及实现关系如下: 简而言之,ImportBeanDefinitionRegistrar昰进行导入预定义bean注册的...
  • 其实,这并不奇怪,这幅本身只是描述的Tomcat容器模型,而这个模型与Servlet的关系又是如何建立的呢。 首先,一个Container包含一个Host,在这个Host中又包含了Servlet容器,而这个Servlet容器中就包含了许多应用...
  • 1 项目整体结构的设计为了让新同学深入...思路模型:项目结构:web.xml映射文件中的内容,用于定义URL请求映射路径和java之间的对应关系<?xml version="1.0" encoding="UTF-8"?> <web-app> <!--...
  • 前言 这篇文章探索一下springboot的tomcat是如何实现的 一、起步依赖 1.首先我们了解一下起步依赖 SpringBoot结合Maven管理,无需关注不同的依赖,不同版本是否存在冲突的问题。...2.上直观的看依赖关系 我们平

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 141
精华内容 56
关键字:

tomcat类关系图