Tomcat源码学习--WebAppClassLoader类加载机制

上一篇博客JVM-类加载机制 中我们已经对JVM的类加载机制有所了解, 这篇博客我们了解一下Tomcat的类加载机制。

Tomcat的类加载器可以分为两部分,第一个是Tomcat自身所使用的类加载器,会加载jre的lib包及tomcat的lib包的类,遵循类加载的双亲委派机制;第二个是每个Web应用程序用的,每个web应用程序都有自己专用的WebappClassLoader,优先加载/web-inf/lib下的jar中的class文件,这样就隔离了每个web应用程序的影响,但是webappClassLoader没有遵循类加载的双亲委派机制,处理的方法就是在使用webappClassLoader的load加载类会进行过滤,如果有些类被过滤掉还是通过双亲委派机制优先从父加载器中加载类。

一、Tomcat类加载器初始化

在tomcat调用Bootstrap进行启动时会调用initClassLoaders创建3个ClassLoader,它们分别是commonLoader,catalinaLoader,sharedLoader,遵循双亲委派机制。commonLoader会根据tomcat的conf/catalina.properties中的配置加载tomcat自身的jar,然后将这个类加载器作为整个tomcat容器的父类加载器。
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
	ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;
    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中会加载tomcat的lib/*.jar下的所有jar中的class文件。
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);
    }

接下来我们看看这3个类加载器都使用了在什么地方,commonLoader除了在initClassLoader处使用外并没有在其他地方使用,它是作为catalinaLoader和sharedLoader的父类加载器;catalinaLoader在init方法中被设置为当前线程的类加载器。
 public void init() throws Exception {

	//.....省略部分代码
	//将catalinaLoader设置为当前线程的类加载器
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    SecurityClassLoad.securityClassLoad(catalinaLoader);
	
	//.....省略部分代码
	
 }
sharedLoader类加载器作为参数调用了Catalina的setParentClassLoader方法,成为了整个Catalina容器的父类加载器,当然也是WebAppClassLoader的父类加载器。
	//Catalina类的setParentClassLoader方法
	String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
		//sharedLoader作为参数调用setParentClassLoader方法
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
		//调用Catalina类的setParentClassLoader方法参数值为sharedLoader
        method.invoke(startupInstance, paramValues);
 

二、WebAppClassLoader应用类加载器

在tomcat中对每个应用都有一个WebAppClassLoader用来隔绝不同应用之前的class文件,因此WebAppClassLoader的创建工作也是在Context中进行的,在StandardContext的startInternal方法开启一个web应用时会创建类加载器。
 if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());//添加父类加载器
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }
同时会启动类加载器
 Loader loader = getLoader();
                if (loader instanceof Lifecycle) {
                    ((Lifecycle) loader).start();
                }
在WebappClassLoader的父类WebappClassLoaderBase中实现了start方法
@Override
    public void start() throws LifecycleException {

        state = LifecycleState.STARTING_PREP;
		//加载web应用的所有class文件
        WebResource classes = resources.getResource("/WEB-INF/classes");
        if (classes.isDirectory() && classes.canRead()) {
            localRepositories.add(classes.getURL());
        }
		//加载web应用lib目录下在jar文件
        WebResource[] jars = resources.listResources("/WEB-INF/lib");
        for (WebResource jar : jars) {
            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
                localRepositories.add(jar.getURL());
                jarModificationTimes.put(
                        jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }

        state = LifecycleState.STARTED;
    }

在WebappClassLoaderBase中重写了ClassLoader的loadClass方法,在这个实现方法中我们可以一窥tomcat真正的类加载机制,简单来说web应用首先还是去尝试加载jre下面的类这个流程是不可变的,接下来web应用就可以根据设置首先是加载自己应用下的class文件还是tomcat的lib目录下的class文件了,实现逻辑看loadClass的实现机制还是比较简单的,所有通过设置web应用可以遵循类加载的双亲委派机制或者不遵循双亲委派机制了。
@Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;

            // Log access to stopped class loader
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
			//从当前ClassLoader的本地缓存中加载类,如果找到则返回
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.1) Check our previously loaded class cache
			// 本地缓存没有的情况下,调用ClassLoader的findLoadedClass方法查看jvm是否已经加载过此类,如果已经加载则直接返回。
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // (0.2) Try loading the class with the system class loader, to prevent
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
			//通过系统的来加载器加载此类,这里防止应用写的类覆盖了J2SE的类,这句代码非常关键,如果不写的话,
			//就会造成你自己写的类有可能会把J2SE的类给替换调,另外假如你写了一个javax.servlet.Servlet类,放在当前应用的WEB-INF/class中,
			//如果没有此句代码的保证,那么你自己写的类就会替换到Tomcat容器Lib中包含的类。
            String resourceName = binaryNameToPath(name, false);

            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
            } catch (ClassCircularityError cce) {
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }

            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            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);
                    }
                }
            }
			//判断是否需要委托给父类加载器进行加载,delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值了,filter中是优先加载tomcat的lib下的class文件
			//filter方法中根据包名来判断是否需要进行委托加载,默认情况下会返回false.因此delegatedLoad为false
            boolean delegateLoad = delegate || filter(name, true);

            // (1) Delegate to our parent if requested
			 //因为delegatedLoad为false,那么此时不会委托父加载器去加载,这里其实是没有遵循parent-first的加载机制。
            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) Search local repositories
			//调用findClass方法在webapp级别进行加载
            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) {
                // Ignore
            }

            // (3) Delegate to parent unconditionally
			//如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载。
            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);
    }

 

 

 
  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
java.lang.SecurityException: class "org.apache.commons.collections.SequencedHashMap"'s signer information does not match signer information of other classes in the same package at java.lang.ClassLoader.checkCerts(Unknown Source) at java.lang.ClassLoader.preDefineClass(Unknown Source) at java.lang.ClassLoader.defineClass(Unknown Source) at java.security.SecureClassLoader.defineClass(Unknown Source) at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:1817) at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:872) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1325) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1204) at java.lang.ClassLoader.loadClassInternal(Unknown Source) at org.hibernate.mapping.Table.(Table.java:32) at org.hibernate.cfg.Mappings.addTable(Mappings.java:120) at org.hibernate.cfg.HbmBinder.bindRootPersistentClassCommonValues(HbmBinder.java:251) at org.hibernate.cfg.HbmBinder.bindRootClass(HbmBinder.java:236) at org.hibernate.cfg.HbmBinder.bindRoot(HbmBinder.java:152) at org.hibernate.cfg.Configuration.add(Configuration.java:362) at org.hibernate.cfg.Configuration.addInputStream(Configuration.java:400) at org.hibernate.cfg.Configuration.addResource(Configuration.java:449) at org.hibernate.cfg.Configuration.parseMappingElement(Configuration.java:1263) at org.hibernate.cfg.Configuration.parseSessionFactory(Configuration.java:1235) at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:1217) at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:1184) at org.hibernate.cfg.Configuration.configure(Configuration.java:1112) at com.chinafi.hibernate.SessionFactory.(SessionFactory.java:30) at com.chinafi.hibernate.BaseD

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值