将外部jar加载到应用的ClassLoader中
最近在开发一个应用的过程中,需要支持加载外部的jar包,最初的想法是自定义一个ClassLoader,加载外部jar包,这样就能搞定了,于是google了一下自定义ClassLoader,参考着写了一个loader,代码如下:
public final class DynamicExtensionLoader extends URLClassLoader {
private static final Logger log = LoggerFactory.getLogger(DynamicExtensionLoader.class);
//保存本类加载器加载的class
private static final Map BYTE_CODE_MAP = new HashMap<>();
public DynamicExtensionLoader(URL[] urls) {
super(urls);
this.init(urls);
}
private void init(URL[] urls){
try{
for(URL url : urls) {
this.loadClasses(new JarFile(url.getPath()));
}
}catch(Exception e){
log.error("init", e);
}
}
@Override
public Class> findClass(final String name) throws ClassNotFoundException{
if(BYTE_CODE_MAP.containsKey(name)){
byte[] byteCode = BYTE_CODE_MAP.get(name);
return this.defineClass(name, byteCode, 0, byteCode.length);
}else{
return super.findClass(name);
}
}
private void loadClasses(JarFile jarFile){
Enumeration en = jarFile.entries();
InputStream input = null;
try{
while (en.hasMoreElements()) {
JarEntry je = en.nextElement();
String name = je.getName();
//这里添加了路径扫描限制
if (name.endsWith(".class")) {
String className = name.replace(".class", "").replaceAll("/", ".");
input = jarFile.getInputStream(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = input.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
byte[] classBytes = baos.toByteArray();
BYTE_CODE_MAP.put(className, classBytes);
}
}
} catch (IOException e) {
log.error("loadClasses", e);
} finally {
if(input!=null){
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
for (Map.Entry entry : BYTE_CODE_MAP.entrySet()) {
String key = entry.getKey();
try {
//载入所有的class
this.loadClass(key);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoClassDefFoundError e){
//忽略jar中的三方依赖缺少的异常
System.out.println("NoClassDefFoundError =>" + key);
}
}
}
}
通过测试,确实可以加载外部的jar,但马上发现了一个问题,使用自定义ClassLoader加载的类,在使用的时候,也需要用自定义的loader来加载,非常的不方便。如是又想到将外部的jar加载到系统的中loader中,于是就想直接获取系统的ClassLoader,将jar加载进去不就可以了么。
说干就干,通过ClassLoader.getSystemClassLoader()
能获取到AppClassLoader
,而AppClassLoader
又继承自URLClassLoader
,刚好URLClassLoader
有个addURL
的方法可以将外部jar包加入应用内的ClassLoader,但是addURL
是个受保护的方法,因此需要使用反射来调用这个方法,完整代码如下:
public class LoadExtLibsUtil {
private static final Logger logger = LoggerFactory.getLogger(LoadExtLibsUtil.class);
/**
* 加载目录下的所有jar文件
* @param jarPath
*/
public static void loadJars(String jarPath){
File jarDir = new File(jarPath);
if(jarDir.isDirectory() && jarDir.canRead()){
File[] jars = jarDir.listFiles();
try{
for(File jar : jars){
loadURL(jar.toURI().toURL());
}
}catch (Exception e){
logger.error("loadJars", e);
}
}
}
/**
* 通过反映将jar包加入系统ClassLoader
* @param url
*/
private static void loadURL(URL url){
if(url!=null){
Method addUrl = null;
boolean isAccessible = false;
try{
addUrl = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
isAccessible = addUrl.isAccessible();
addUrl.setAccessible(true);
URLClassLoader loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
addUrl.invoke(loader, url);
}catch (Exception e){
logger.error("loadURL", e);
}finally {
if(addUrl!=null){
addUrl.setAccessible(isAccessible);
}
}
}
}
}