最近被classloader整郁闷了……
目的:实现扩展接口的所有业务子类的动态加载,在系统运行时避免频繁重起
现已实现对单一类的动态加载:就是extends ClassLoader,然后自定义了一个方法
public Object getInstance(String className)
在最后define一下class就可以了现在的问题是:
如果某一业务子类在静态初始化块中调用了自制的ClassLoader来加载指定类,将不能实现静态加载(不在同一命名空间里……)我也试过
AppLoader loader = new AppLoader();
Thread.currentThread().setContextClassLoader(loader);但结果依旧……好象对静态初始化块,JVM会默认使用系统的类装载器;不知是否我创建ClassLoader时有问题……有兄台做过这类东西么?
目的:实现扩展接口的所有业务子类的动态加载,在系统运行时避免频繁重起
现已实现对单一类的动态加载:就是extends ClassLoader,然后自定义了一个方法
public Object getInstance(String className)
在最后define一下class就可以了现在的问题是:
如果某一业务子类在静态初始化块中调用了自制的ClassLoader来加载指定类,将不能实现静态加载(不在同一命名空间里……)我也试过
AppLoader loader = new AppLoader();
Thread.currentThread().setContextClassLoader(loader);但结果依旧……好象对静态初始化块,JVM会默认使用系统的类装载器;不知是否我创建ClassLoader时有问题……有兄台做过这类东西么?
classload应该有点线索
//所有子模块要实现的接口
public interface Handler{
public void execute();
}
//先模拟一个实现
public class HandlerImpl implements Handler{
public void execute(){
System.out.println("Handler executing..");
} } //自定制classloader,用来实现动态加载
import java.io.*;
import java.util.*; public final class AppLoader extends ClassLoader{
public AppLoader(){
super(AppLoader.class.getClassLoader());
}
//得到一个可动态加载的实例
public Object getReloadableClass(String className)
throws ClassNotFoundException{ byte[] clazzByte = getByteByClassFile(className);
Class clazz = this.defineClass(className,clazzByte,0,clazzByte.length); Object instance = null;
try{
instance = clazz.newInstance();
}catch(IllegalAccessException iiex){
iiex.printStackTrace();
}catch(InstantiationException inex){
inex.printStackTrace();
throw new RuntimeException(className+"cant instantiation");
}
return instance;
}
//将指定的class文件大小返回
private byte[] getByteByClassFile(String className){
byte[] bytes = null;
InputStream in = null;
try{
Class clazz = Class.forName(className); File clazzFile = null; //在classpath中查找指定的class文件
for(int i = 0;i < pathElements.length;i ++ ){
String name = className.replace('.',File.separatorChar)+".class";
String dir = pathElements[i];
clazzFile = new File(dir + File.separatorChar + name);
if(clazzFile.exists())
break;
}
in = new FileInputStream(clazzFile); bytes = new byte[in.available()];
in.read(bytes);
}catch(ClassNotFoundException clsex){
throw new RuntimeException("[Not foud class file:" + className
+ " in classpath]");
}catch(FileNotFoundException fex){
throw new RuntimeException("[" + className + " not exist]");
}catch(IOException ioex){
ioex.printStackTrace();
}finally{
try{
in.close();
}catch(IOException ioex){
ioex.printStackTrace();
}
}
return bytes;
}
} //实验动态加载的效果
public class Lab{
//存放所有子实现模块
public static Hashtable map; static{
init();
} public static void init(){
map = new Hashtable();
MOHandler m1 = getInstance("MOHandlerImpl");
map.put("1",m1);
} private static MOHandler getInstance(String className){
AppLoader loader = new AppLoader();
MOHandler instance = null;
try{
instance = (Handler)loader.getReloadableClass(className);
}catch(ClassNotFoundException clsex){
clsex.printStackTrace();
}
return instance;
}
} //开始执行,并设置线程ClassLoader
public class HandlerExecute{
public static void main(String[] args)throws Exception{
AppLoader loader = new AppLoader();
Thread.currentThread().setContextClassLoader(loader);
Handler h = (Handler)Lab.map.get("1");
while(true){
Thread.sleep(1*1500);
h.execute();
}
}
}
不知为什么,这样子做,无法达到动态加载的效果,但是如果不把init写在static{}中就可以,我想是因为静态加载是由JVM的默认ClassLoader装载的,即使在静态块中进行改变线程ClassLoader设置也无效??
那TOMCAT是如何做到这点的??我也看了些它的源码,它是在Bootstrap时先指定的每一个应用块的ClassLoader,我和它的做法类似,为什么我的静态初始化块中的代码就无法实现动态加载??;
……郁闷……
这一行指定了Lab类是用系统类装载器来装载
Thread.currentThread().setContextClassLoader(loader);
这一行无效果原因见下面的JDK文档:
public void setContextClassLoader(ClassLoader cl)
Sets the context ClassLoader for this Thread. The context ClassLoader can be set when a thread is created, and allows the creator of the thread to provide the appropriate class loader to code running in the thread when loading classes and resources.
JDK的文档说的是线程创建(不是运行)时来set类装载器。
当前线程已经处于运行状态不能修改它的类装载器(基于安全原因)。
Handler m = (Handler)loader.getReloadableClass("HandlerImpl");
这样子也是不行的;
getReloadableClass里定义了打开了类 -- >虚拟机的通道;
用这个方法得到一个非static{}初始化的对象是可以实现动态加载的,但在这里就不可以了;这里是改过后的,可以实现动态装载的部分
public class HandlerExecute{
public static void main(String[] args)throws Exception{
while(true){
Thread.sleep(1*1500);
AppLoader loader = new AppLoader();
Handler h = (Handler)loader.getReloadableClass("HandlerImpl");
h.execute();
}
}
} 但很显然这不是我们想要的,总不能每次调用实例都要去初始化一次吧……
<SUP><B>UP</B></SUP>
步骤一、拷贝下面这个类到你的工程,我写的,可以进一步优化
package dmis;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.JarURLConnection;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;
import java.util.jar.Attributes;
import java.io.IOException;
import javax.swing.*;/**
* A class loader for loading jar files, both local and remote.
*/
class JarClassLoader extends URLClassLoader {
private URL url; /**
* Creates a new JarClassLoader for the specified url.
*
* @param url the url of the jar file
*/
public JarClassLoader(URL url) {
super(new URL[] { url });
this.url = url;
} /**
* Returns the name of the jar file main class, or null if
* no "Main-Class" manifest attributes was defined.
*/
public String getMainClassName() throws IOException {
URL u = new URL("jar", "", url + "!/");
JarURLConnection uc = (JarURLConnection)u.openConnection();
Attributes attr = uc.getMainAttributes();
return attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null;
} /**
* Invokes the application in this jar file given the name of the
* main class and an array of arguments. The class must define a
* static method "main" which takes an array of String arguemtns
* and is of return type "void".
*
* @param name the name of the main class
* @param args the arguments for the application
* @exception ClassNotFoundException if the specified class could not
* be found
* @exception NoSuchMethodException if the specified class does not
* contain a "main" method
* @exception InvocationTargetException if the application raised an
* exception
*/
public void invokeClass(String name, String[] args)
throws ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException
{
Class c = loadClass(name);
Method m = c.getMethod("main", new Class[] { args.getClass() });
m.setAccessible(true);
int mods = m.getModifiers();
if (m.getReturnType() != void.class || !Modifier.isStatic(mods) ||
!Modifier.isPublic(mods)) {
throw new NoSuchMethodException("main");
}
try {
m.invoke(null, new Object[] { args });
} catch (IllegalAccessException e) {
// This should not happen, as we have disabled access checks
}
} public void invokeClass(String name)
throws ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException
{
Class c=loadClass(name);
Method m=c.getMethod("main",null);
m.setAccessible(true);
try{
m.invoke(null,null);
}catch(IllegalAccessException e){
}
}}二、子模块(jar文件)写接口
即在子模块的main class中多一个方法:public static main(){//这里启动整个子模块}
然后把该子模块打包成jar文件(注意manifest文件中必须声明该jar 文件的main class)
三、动态调用子模块
JarClassLoader cl=new JarClassLoader(url);//url指向你的子模块即jar文件的路径
String name=cl.getMainClassName();
cl.invokeClass(name);//OK!
你给我的地址打不开……另外,你给的这个ClassLoader我找的时候也看到过,这是用反射得到JAR包里的类信息,然后进行动态实例化,调用;如果你不需要从网上调用异地class文件的话,完全可以将这段代码改成Class.forName来处理;大概你没明白我的意思,我的意思是想在某一程序中的某段代码做更改后,在不重启服务的情况下,动态将其加载上来;现在已经快做出来了,一起参考下这两篇文章吧
http://www.developer.com/java/other/article.php/10936_2248831_2
http://jakarta.apache.org/commons/logging/xref-test/org/apache/commons/logging/LoadTest.html#19
class CryptoClassLoader extends ClassLoader
{ public CryptoClassLoader(int k)
{ key = k;
} protected synchronized Class loadClass(String name,
boolean resolve) throws ClassNotFoundException
{ // check if class already loaded
Class cl = (Class)classes.get(name); if (cl == null) // new class
{ try
{ // check if system class
return findSystemClass(name);
}
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {} // load class bytes--details depend on class loader byte[] classBytes = loadClassBytes(name);
if (classBytes == null)
throw new ClassNotFoundException(name); cl = defineClass(name, classBytes,
0, classBytes.length);
if (cl == null)
throw new ClassNotFoundException(name); classes.put(name, cl); // remember class
} if (resolve) resolveClass(cl); return cl;
} private byte[] loadClassBytes(String name)
{ String cname = name.replace('.', '/') + ".caesar";
FileInputStream in = null;
try
{ in = new FileInputStream(cname);
ByteArrayOutputStream buffer
= new ByteArrayOutputStream();
int ch;
while ((ch = in.read()) != -1)
{ byte b = (byte)(ch - key);
buffer.write(b);
}
in.close();
return buffer.toByteArray();
}
catch (IOException e)
{ if (in != null)
{ try { in.close(); } catch (IOException e2) { }
}
return null;
}
} private Map classes = new HashMap();
private int key;
}
{ // check if system class
return findSystemClass(name);
}
这样做的后果还是使用SystemClassLoader去读的这个类;但这并不影响动态载入; 经过这几天,我发现我把问题的起点搞错了...自定制ClassLoader只是为了满足特殊需要而做的,例如从异地动态载入类并调用,定制自己的安全策略; 从JVM1.2以后完全可以靠Class.forName()来实现动态载入的; 服务器上没有达到动态载入的效果,可能是因为哪里的调用问题....慢慢再找找看吧..
还有这里也人人有分~~~不够我再散,轻易不散分的, 嘿嘿
http://community.csdn.net/Expert/topic/4093/4093704.xml?temp=9.104556E-02
import java.lang.reflect.*;
import java.lang.*;
import java.io.*;
public class testReflect{
public static void main(String[] args){
try{
Class aaa = Class.forName("buffer");
Object o = aaa.newInstance();
Class[] params = new Class[1];
params[0] = Class.forName("java.lang.String");
Method m = aaa.getMethod("printsome", params);
m.invoke(o, new String[]{"bbbbbbbbbbbbbb"});
String newClass = "public class dynClass{"+"public void print1(String str1){" +
"System.out.println(str1);}" +
"public void print2(String str2){" +
"System.out.println(str2);" +
"System.out.println(str2);" +
"System.out.println(str2);}}";
BufferedWriter os = new BufferedWriter(new FileWriter("dynClass.java"));
os.write(newClass);
os.flush();
os.close();
Runtime runtime = Runtime.getRuntime();
runtime.exec("javac dynClass.java"); //*************//Thread.sleep(1000); test(); File f1 = new File("dynClass.java");
File f2 = new File("dynClass.class");
f1.delete();
f2.delete();
System.out.println("delete files---------------------");
}
catch(Exception e){e.printStackTrace(System.err);}
} private static void test(){
try{
Class[] params = new Class[1];
params[0] = Class.forName("java.lang.String");
CryptoClassLoader cryptoClassLoader = new CryptoClassLoader(0); //Class testDyn = Class.forName("dynClass");
Class testDyn = cryptoClassLoader.loadClass("dynClass",false); Object dynObject = testDyn.newInstance();
Method method1 = testDyn.getMethod("print1", params);
Method method2 = testDyn.getMethod("print2", params);
method1.invoke(dynObject, new String[]{"11111111111"});
System.out.println("------------------");
method2.invoke(dynObject, new String[]{"22222222222"});
}
catch(Exception e){e.printStackTrace(System.err);
File f1 = new File("dynClass.java");
File f2 = new File("dynClass.class");
f1.delete();
f2.delete();
System.out.println("delete files---------------------");
}
}
}
现在碰到两个问题,如果直接用Class.forName装载类,就会报classNotFound,只能用cryptoClassLoader.loadClass方法。其次,如果在*************处不加Thread.sleep,就会报classNotFound,而且sleep的时间太短了也不行,时间长到一定程度就可以保证load成功,在两者之间的话就要凭运气了,这怎么办,有没有什么办法知道编译成功了?