建设网站案例,wordpress+icon+修改,做网站需要什么步骤,如何将网址提交到一些权重比较高的网站转载自 深入浅出ClassLoader 你真的了解ClassLoader吗#xff1f;
这篇文章翻译自zeroturnaround.com的 Do You Really Get Classloaders? #xff0c;融入和补充了笔者的一些实践、经验和样例。本文的例子比原文更加具有实际意义#xff0c;文字内容也更充沛一些#xf…转载自 深入浅出ClassLoader 你真的了解ClassLoader吗
这篇文章翻译自zeroturnaround.com的 Do You Really Get Classloaders? 融入和补充了笔者的一些实践、经验和样例。本文的例子比原文更加具有实际意义文字内容也更充沛一些非常感谢作者 Jevgeni Kabanov 能够共享如此优秀的文档。 1. 为什么你需要了解和敬畏ClassLoader
ClassLoader在Java语言中占据了核心地位Java应用服务器OSGi以及大量的网络框架它们大多数都用到了ClassLoader。如果在使用过程中出现了类加载错误你能解决它吗
我们将从JVM和开发者两个角度讲述ClassLoader将会选择一些典型的案例然后演示如何解决它们。NoClassDefFoundErrorLinkageError等很多错误都会有特定的表征我们分析每个例子然后进行解决。
2. 进入ClassLoader
每个ClassLoader对象都是一个java.lang.ClassLoader的实例。每个Class对象都被这些ClassLoader对象所加载通过继承java.lang.ClassLoader可以扩展出自定义ClassLoader并使用这些自定义的ClassLoader对类进行加载。
先大体了解一下ClassLoader的API
package java.lang;public abstract class ClassLoader {public Class loadClass(String name);protected Class defineClass(byte[] b);public URL getResource(String name);public Enumeration getResources(String name);public ClassLoader getParent();
} 最重要的是ClassLoader的loadClass方法它接受一个全类名然后返回一个Class类型的实例。 defineClass方法接受一组字节然后将其具体化为一个Class类型实例它一般从磁盘上加载一个文件然后将文件的字节传递给JVM通过JVMnative 方法对于Class的定义将其具体化实例化为一个Class类型实例。 getParent方法返回其parent ClassLoader。 getResource和getResources方法从给定的repository中查找URLs同时它们也具备类似loadClass一样的代理机制我们可以将loadClass视为defineClass(getResource(name).getBytes())。 Java由于其晚绑定和“解释型”的特性类型的加载是到最晚才进行一个类型直到被调用构造函数、静态方法或者在字段上使用时才会被加载。 考虑如下代码
public class A {public void doSomething() {B b new B();b.doSomethingElse();}
}
代码B b new B();等同于B b Class.forName(“B”, false, A.class.getClassLoader()).newInstance();
这代表着在类型A中使用到的类型将由加载了类型A的类加载器来进行加载。
3. ClassLoader继承体系
当启动一个JVM时bootstrap 类加载器就会加载java的核心类例如rt.jar中的类。bootstrap 类加载器是其他类加载器的parent它使唯一一个没有parent的类加载器。
接下来是extension 类加载器它以bootstrap 类加载器作为parent它用来从Java系统变量java.ext.dir中的jar包中加载类的。
第三个也是最重要的一个就是开发者使用的system classpath 类加载器 。它是extension 类加载器 的child它用来从Java系统变量java.class.path下面加载类可以通过 -classpath 来指定这个位置。
注意类加载器的体系并不是“继承”体系而是一个“委派”体系。大多数类加载器首先会到自己的parent中查找类或者资源如果找不到才会在自己的本地进行查找。事实上类加载器被定义加载哪些在parent中无法加载到的类这样在较高层级的类加载器上的类型能够被“赋值”为较低类加载器加载的类型。
类加载器的委托行为动机是为了避免相同的类被加载多次。回到1995年Java的主要方向被放在Applet上那时候网络带宽优先所以程序中的类直到用时才会被加载。但是事实上Java在服务器端展示了强劲的能力但是服务器端要求类加载器能够反转委派原则也就是先加载本地的类如果加载不到再到parent中加载。
JavaEE的 委派模型 每个方块都是一个类加载器JavaEE规范推荐每个模块的类加载器先加载本类加载的内容如果加载不到才回到parent类加载器中尝试加载。 反转委派原则的原因是应用服务器中所携带的类库并不是应用所期待的也许不适合应用开发者一个常见的例子就是log4j的依赖在容器和不同的应用中都存在但是它们的版本大都不同。 Tomcat的 类加载顺序开启了delegate模式 在Tomcat中默认的行为是先尝试在Bootstrap和Extension中进行类型加载如果加载不到则在WebappClassLoader中进行加载如果还是找不到则在Common中进行查找。在Alibaba使用的Tomcat开启了delegate模式因此加载类型时会以parent类加载器优先。
4. NoClassDefFoundError
NoClassDefFoundError是在开发JavaEE程序中常见的一种问题。该问题会随着你所使用的JavaEE中间件环境的复杂度以及应用本身的体量变得更加复杂尤其是现在的JavaEE服务器具有大量的类加载器。
在JavaDoc中对NoClassDefFoundError的产生是由于JVM或者类加载器实例尝试加载类型的定义但是该定义却没有找到影响了执行路径。换句话说在编译时这个类是能够被找到的但是在执行时却没有找到。
这一刻IDE是没有出错提醒的但是在运行时却出现了错误。
看看如下示例
/*** author weipeng2k 2015年3月27日 下午5:15:15*/
WebServlet(name NoClassDefFoundErrorServlet, urlPatterns /noClassDefFoundError.do)
public class NoClassDefFoundErrorServlet extends HttpServlet {private static final long serialVersionUID 61585757018374721L;Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().println(TestCase.class.toString());}
}
其中对于junit的依赖是provided级别的这里是为了能简化错误出现的条件。可以看到在NoClassDefFoundErrorServlet中使用了junit.jar中的TestCase但是junit.jar在WEB-INF/lib中却没有从而导致WebappClassLoader在进行加载TestCase时无法找到从而抛出NoClassDefFoundError。我们需要从最终的war包中确定是否存在这个类而不是在IDE中进行搜索。
在看pom.xml中对于依赖的定义
dependenciesdependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion3.8.1/versionscopeprovided/scope/dependencydependencygroupIdjavax.servlet/groupIdartifactIdservlet-api/artifactIdversion3.0/versionscopeprovided/scope/dependencydependencygroupIdorg.springframework/groupIdartifactIdspring/artifactIdversion2.5.6/version/dependency
/dependencies
其中对于junit的依赖是provided级别的这里是为了能简化错误出现的条件。可以看到在NoClassDefFoundErrorServlet中使用了junit.jar中的TestCase但是junit.jar在WEB-INF/lib中却没有从而导致WebappClassLoader在进行加载TestCase时无法找到从而抛出NoClassDefFoundError。我们需要从最终的war包中确定是否存在这个类而不是在IDE中进行搜索。
5. NoSuchMethodError
在另一个场景中我们可能遇到了另一个错误也就是NoSuchMethodError。
NoSuchMethodError代表这个类型确实存在但是一个不正确的版本被加载了。为了解决这个问题我们可以使用 ‘-verbose:class’ 来判断该JVM加载的到底是哪个版本。
看如下示例
import org.springframework.beans.factory.BeanFactoryUtils;/*** author weipeng2k 2015年3月31日 上午9:09:58*/
WebServlet(name NoSuchMethodErrorServlet, urlPatterns { /noSuchMethodError.do })
public class NoSuchMethodErrorServlet extends HttpServlet {private static final long serialVersionUID 1699609060417354821L;Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {BeanFactoryUtils.isGeneratedBeanName(xxx);resp.getWriter().println(done.);}
}
在doGet方法中调用了BeanFactoryUtils.isGeneratedBeanName(”xxx“);看一下项目的pom依赖。
dependenciesdependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.11/versionscopeprovided/scope/dependencydependencygroupIdjavax.servlet/groupIdartifactIdservlet-api/artifactIdversion3.0/versionscopeprovided/scope/dependencydependencygroupIdorg.springframework/groupIdartifactIdorg.springframework.context/artifactIdversion3.0.5.RELEASE/versionscopeprovided/scope/dependencydependencygroupIdorg.apache.mina/groupIdartifactIdmina-core/artifactIdversion2.0.7/version/dependencydependencygroupIdcom.alibaba.external/groupIdartifactIdsourceforge.spring/artifactIdversion2.0.7/version/dependency
/dependencies
这里为了方便观察到结果将org.springframework.context的 scope 改为了 provided 目的是不将其打包入war包而只是使用了sourceforge.spring中定义的2.0.7版本这个版本肯定没有isGeneratedBeanName(String name)方法但是在IDE中由于应用依赖到了高版本的spring从而能够编译通过但是在运行时却没有那么好运了。这种错误常见于 Maven坐标 的变动使得应用依赖了多个 相同内容不同版本 的jar包以致在运行时选择了非期望的版本。
6. ClassCastException
NoClassDefFoundError和NoSuchMethodError是两个在 JavaEE 环境中经常出现的问题这些问题需要 开发人员了解问题的本质才能够被 从容 的处理。
下面我们看一下ClassCastException在一个类加载器的情况下一般出现这种错误都会是在转型操作时比如A a (A) method();很容易判断出来method()方法返回的类型不是类型A但是在 JavaEE 多个类加载器的环境下就会出现一些难以定位的情况。
看如下示例
package com.murdock.classloader.servlet;import java.io.File;
import java.io.IOException;
import java.net.URL;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.mina.proxy.utils.MD4;import com.murdock.classloader.CachedClassLoader;/*** author weipeng2k 2015年4月4日 下午6:00:54*/
WebServlet(name ClassCastExceptionServlet, urlPatterns /classCastException.do)
public class ClassCastExceptionServlet extends HttpServlet {private static final long serialVersionUID -8959000121057369987L;Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String localFirst req.getParameter(localFirst);CachedClassLoader cl null;cl new CachedClassLoader(new URL[] { new File(/Users/weipeng2k/.m2/repository/org/apache/mina/mina-core/2.0.7/mina-core-2.0.7.jar).toURI().toURL() }, this.getClass().getClassLoader());if (false.equals(localFirst)) {cl.setLocalFirst(false);}try {Class? klass cl.loadClass(org.apache.mina.proxy.utils.MD4);MD4 md4 (MD4) klass.newInstance();resp.getWriter().println(md4);} catch (Exception ex) {throw new RuntimeException(ex);} finally {cl.close();}}
}
在ClassCastExceptionServlet中构建了一个CachedClassLoader利用这个ClassLoader加载org.apache.mina.proxy.utils.MD4然后反射调用构造该类的实例将其赋给MD4最后将其打印到浏览器。
请求URLhttp://localhost:8080/classCastException.do
响应页面出现错误
java.lang.RuntimeException: java.lang.ClassCastException: org.apache.mina.proxy.utils.MD4 cannot be cast to org.apache.mina.proxy.utils.MD4
com.murdock.classloader.servlet.ClassCastExceptionServlet.doGet(ClassCastExceptionServlet.java:42)
javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)请求URL http://localhost:8080/classCastException.do?localFirstfalse
响应页面输出正常
org.apache.mina.proxy.utils.MD4401c8af5 请求的URL加上了localFirstfalse就可以正常的输出而它也就是在CachedClassLoder上设置了一下为什么有这么大的差别。org.apache.mina.proxy.utils.MD4全类名一致为什么会出现ClassCastException呢 在JVM中如何确定一个类型实例答全类名吗不是是类加载器加上全类名。在JVM中类型被定义在一个叫SystemDictionary 的数据结构中该数据结构接受类加载器和全类名作为参数返回类型实例。 SystemDictionary 如图所示 类型加载时需要传入类加载器和需要加载的全类名如果在 SystemDictionary 中能够命中一条记录则返回class 列上对应的类型实例引用如果无法命中记录则会调用loader.loadClass(name);进行类型加载。 这里不会更加深入的介绍 SystemDictionary 如何进行类型加载的过程而是需要指出 JVM中确定一个类型的坐标是通过类加载器和全类名做到的 。回想一下MD4 md4 (MD4) klass.newInstance();是不是代表着等式两边的MD4是不同的类加载器加载的呢那问题一定出在 CachedClassLoader 上。这里贴一下loadClass(String name)方法的部分逻辑。 CachedClassLoader 的loadClass逻辑
if (localFirst) {try {clazz findClass(name);if (clazz ! null) {return clazz;}} catch (ClassNotFoundException ex) {}return super.loadClass(name);
} else {return super.loadClass(name);
}
可以看到在 localFirst 为true时该类加载器会首先加载自身 repository 中的类型如果加载不到则会尝试默认的加载机制进行加载也就是parent优先加载。这样就可以解释MD4 md4 (MD4) klass.newInstance();等式左边MD4 md4这个类型是WebappClassLoader.org.apache.mina.proxy.utils.MD4等式右边klass.newInstance()返回的类型是CachedClassLoader.org.apache.mina.proxy.utils.MD4二者并不是同一个类型所以无法完成类型转换最终抛出 ClassCastException 。而当 localFirst 为false时该类加载器遵循parent优先从而会先委派给WebappClassLoader进行加载当然转型也就不会有问题了。
在传统的双亲委派模型下这种 ClassCastException 是不会发生的因为它的加载顺序杜绝了出现这种问题的可能而在 JavaEE 环境下每个资源模块比如一个war包都优先使用自身的资源正因为突破了双亲委派模型 奇怪的问题 就发生了。
7. LinkageError
有时候事情会变得更糟和 ClassCastException 本质一样加载自不同位置的*相同类*在同一段逻辑比如方法中交互时会出现 LinkageError 。
我们先看一下出错的异常信息然后分析一下它产生的条件和原因
java.lang.LinkageError: loader constraint violation: when resolving overridden method com.murdock.classloader.linkageerror.Param2.generate()Lcom/murdock/classloader/linkageerror/Param2; the class loader (instance of com/murdock/classloader/linkageerror/LinkageErrorTest$1) of the current class, com/murdock/classloader/linkageerror/Param2, and its superclass loader (instance of sun/misc/Launcher$AppClassLoader), have different Class objects for the type com/murdock/classloader/linkageerror/Param2 used in the signatureat java.lang.Class.getDeclaredConstructors0(Native Method)at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)at java.lang.Class.getConstructor0(Class.java:3075)at java.lang.Class.newInstance(Class.java:412)at com.murdock.classloader.linkageerror.LinkageErrorTest.test(LinkageErrorTest.java:34)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 看到一堆出错信息但是不要紧张慢慢的读一下出错信息这种错误一般会让你直觉感觉不会出现。loader constraint violation表示类加载器冲突了这句话暗示 相同的类由不同的ClassLoader加载但是在这里遇到了。when resolving overridden method com.murdock.classloader.linkageerror.Param2.generate()Lcom/murdock/classloader/linkageerror/Param2;表示在解析那条语句出现了问题这里表示在Param2.generate()方法的解析过程中出现了问题。the class loader (instance of com/murdock/classloader/linkageerror/LinkageErrorTest$1) of the current class, com/murdock/classloader/linkageerror/Param2,表示解析的语句所在的类型Param2是LinkageErrorTest$1类加载器加载的。and its superclass loader (instance of sun/misc/Launcher$AppClassLoader), have different Class objects for the type com/murdock/classloader/linkageerror/Param2 used in the signature表示Param2的超类Param中被覆盖的方法返回的类型Param2为Launcher$AppClassLoader加载。 Linkage在常规情况下非常难以制造只有在多个类加载器交互时才有可能出现下面看一下问题代码。出现问题的类和参数
package com.murdock.classloader.linkageerror;/*** author weipeng2k 2015年4月28日 上午10:04:26*/
public class HandleUtils {public void m(Param param) {param.generate();}}package com.murdock.classloader.linkageerror;public class Param {public Param2 generate() {return new Param2();}
}package com.murdock.classloader.linkageerror;public class Param2 extends Param {public Param2 generate() {return new Param2();}
} 测试用例如下
Test
public void test() throws Exception {// cl1在加载HandleUtils和Param时将会使用AppClassLoaderURLClassLoader cl1 new URLClassLoader(new URL[] {new File(target/test-classes).toURI().toURL()}, null) {Overridepublic Class? loadClass(String name) throws ClassNotFoundException {if (com.murdock.classloader.linkageerror.HandleUtils.equals(name)) {return ClassLoader.getSystemClassLoader().loadClass(name);}if (com.murdock.classloader.linkageerror.Param.equals(name)) {return ClassLoader.getSystemClassLoader().loadClass(name);}return super.loadClass(name);}};ClassLoader.getSystemClassLoader().loadClass(com.murdock.classloader.linkageerror.Param2);HandleUtils hu (HandleUtils) cl1.loadClass(com.murdock.classloader.linkageerror.HandleUtils).newInstance();hu.m((Param) cl1.loadClass(com.murdock.classloader.linkageerror.Param2).newInstance());
}
LinkageError 需要观察哪个类被不同的类加载器加载了在哪个方法或者调用处发生交汇的然后才能想解决方法解决方法无外乎两种。第一还是不同的类加载器加载但是相互不再交汇影响这里需要针对发生问题的地方做一些改动比如更换实现方式避免出现上述问题第二冲突的类需要由一个Parent类加载器进行加载。**LinkageError** 和**ClassCastException** 本质是一样的加载自不同类加载器的类型在同一个类的方法或者调用中出现如果有转型操作那么就会抛 ClassCastException 如果是直接的方法调用处的参数或者返回值解析那么就会产生 LinkageError 。
8. 类加载器问题对照表
遇到类加载器问题时可以尝试使用下面的表格进行问题排查。
类找不到加载了不正确的类多于一个类被加载ClassNotFoundException NoClassDefFoundErrorIncompatibleClassChangeError NoSuchMethodError NoSuchFieldError IllegalAccessErrorClassCastException LinkageErrorIDE class lookup (CtrlShiftT in Eclipse)find . -name “*.jar” -exec jar -tf {} \; | grep DateUtils 使用middleware-detector 通过在启动参数中加 -verbose:class观察加载的类来自哪个jar包使用middelware-detector通过-verbose:class观察
9. 使用Middleware-Detector进行类查找
出现了 ClassNotFoundException 或者 NoClassDefFoundError 需要检查一下程序的classpath下面是否存在你所预想的类。这时可以使用Middleware-Detector工具进行类查找该工具是Alibaba中间件团队开发的一款中间件问题诊断工具当然也包括了许多支持性质的工具。
下面我们使用Middleware-Detector进行类查找比如我们要查找apache的Utils我们怀疑这个类在classpath下找不到。
启动middleware-detector查看 Pandora 提供的自定义检查器目前编号为1的Pandora自定义检查器就是进行classpath下的指定类或者接口的查找工作。 配置classpath目录以及需要查找的类名这里类名支持 * 号进行模糊匹配。可以看到设定当前的classpath目录到了WEB-INF/lib 下面然后找寻*apache*comm*A*Utils是否存在如果能够找到则会输出到终端这里就找到了ArchiveUtils和ArrayUtils两个符合要求的类。如果无法找到那么就可能是pom.xml的依赖配置不正确了需要检查一下。 10. 使用Middleware-Detector进行检查类冲突
出现了 NoSuchMethodError 或者 NoSuchFieldError 这时一般是应用的classpath下包含了多个包含了想同类的jar包而很不幸的加载到了 不正确 的jar包。
我们可以通过使用Middleware-Detector的类查找进行定位但是不能发现一个修复一个这里Middleware-Detector提供了一个检查classpath下有冲突jar包的功能。只需要设置classpath的目录然后运行cc –check tomcat#1即可。有冲突的jar就需要自己在pom.xml里面进行仲裁或者排除了。