基于java agent方式
新建一个maven工程
public static void premain(String agentArgs, Instrumentation inst) {
Trace.info("HotAgent-premain-start");
Trace.info("isReadefineClassesSupported:" +inst.isRedefineClassesSupported());
new Reloader(inst).reload();
}
public static void agentmain(String agentArgs, Instrumentation inst) {
premain(agentArgs, inst);
}
再pom中增加一下
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestEntries>
<Can-Redefine-Classes>
true
</Can-Redefine-Classes>
<Premain-Class>
com.reload.HotAgent
</Premain-Class>
<Agent-Class>
com.reload.HotAgent
</Agent-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
核心代码如下
主要是利用JDK中Instrument的redefine方法去重定义class,去重新加载了Class.
for (ClassDefinitionWrap rWrap : redefineClassWrap) {
Trace.info("redefine class" + rWrap.getClassname());
inst.redefineClasses(new ClassDefinition(rWrap.getCls(), rWrap.getBs()));
result.appendMsg("热加载类:ࣺ"+rWrap.getClassname()+"成功");
}
基于Classloader方式
热加载就是利用新建自定义Classloader去加载Class,让后利用Thread的contexClassloader去替换老的classloader加载的class,这样就能实现 热加载。以下是demo的代码:
TestClassLoader testClassLoader = new TestClassLoader();
System.out.println("parent classloader:" + testClassLoader.getParent());
Thread.currentThread().setContextClassLoader(testClassLoader);
Class clazz = testClassLoader.loadClass("com.plugin.AutoReload");
System.out.println(clazz.hashCode());
System.out.println(clazz.getClassLoader());
System.out.println(testClassLoader);
TestClassLoader newClassLoader = new TestClassLoader(Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(newClassLoader);
System.out.println(newClassLoader);
Class clazz1 = newClassLoader.loadClass("com.plugin.AutoReload");
System.out.println(clazz1.hashCode());
System.out.println(clazz1.getClassLoader());
自定义Classloader的示例代码:
public class TestClassLoader extends ClassLoader {
public TestClassLoader(ClassLoader parent) {
super(parent);
}
public TestClassLoader(){
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class c = null;
synchronized (getClassLoadingLock(name)) {
if (c == null) {
try {
ClassLoader parent = getParent();
if (parent != null) {
c = parent.loadClass(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
}
if (c != null) {
return c;
}
byte[] bytes = loadclassData(name);
System.out.println("=====" + name);
if(bytes != null) {
return defineClass(name, bytes, 0, bytes.length);
}
}
throw new ClassNotFoundException(name);
}
private byte[] loadclassData(String className) {
return getContent(getResourceStream(className));
}
private InputStream getResourceStream(String toLoadClassName) {
System.out.println("load class name " + toLoadClassName);
try {
JarFile jar = new JarFile(Constants.WATCH_PACKAGE + File.separator + "plugin-1.0-SNAPSHOT.jar");
// 包名
Enumeration<JarEntry> entry = jar.entries();
JarEntry jarEntry;
String name;
String className;
Class clazz = null;
while (entry.hasMoreElements()) {
jarEntry = entry.nextElement();
name = jarEntry.getName();
if (name.startsWith("/")) {
name = name.substring(1);
}
if (jarEntry.isDirectory() || !name.endsWith(".class")) {
continue;
}
// 去掉 .class
className = name.substring(0, name.length() - 6).replace("/", ".");
System.out.println(className);
if (className.equals(toLoadClassName)) {
return jar.getInputStream(jarEntry);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public byte[] getContent(InputStream inputStream) {
// 读取Class文件呢
ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
// 写入byteStream
int len = 0;
try (InputStream in = inputStream) {
while ((len = in.read()) != -1) {
byteSt.write(len);
}
} catch (
IOException e) {
e.printStackTrace();
}
// 转换为数组
return byteSt.toByteArray();
}
}
基于groovy方式
基于Groovy方式,就是利用GroovyClassLoader的重新加载脚本,重新生成一个Class,GroovyClassLoader每次调用parseClass都是新城一个新的Class,这样就能实现Class的热加载.
String scriptContent = "package com.reload\n" +
"\n" +
"import org.springframework.beans.factory.annotation.Autowired\n" +
"\n" +
"class Hello {\n" +
"\n" +
" @Autowired\n" +
" HelloService service;\n" +
"\n" +
" HelloService getService() {\n" +
" return service\n" +
" }\n" +
"\n" +
" def run() {\n" +
" print(service.hello())\n" +
" }\n" +
"}";
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
Class clazz = null;
for (int i = 0; i < 3; i++) {
clazz = groovyClassLoader.parseClass(scriptContent);
System.out.println(clazz.hashCode());
}
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext("com.reload");
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
((DefaultListableBeanFactory)annotationConfigApplicationContext.getBeanFactory()).registerBeanDefinition("hello", beanDefinition);
// annotationConfigApplicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(beanDefinition, "hello");
GroovyObject object = (GroovyObject)annotationConfigApplicationContext.getBean("hello");
object.invokeMethod("run",null);
总结
本文主要介绍了三种实现JAVA中Class的热加载的三种方式.\
- 基于java agent方式,只能实现class的增加、修改、删除方法和属性等,不能实现Class的新增,
- 基于Classloader方式, 可以实现class的增加、修改、删除方法和属性等,可以实现Class的新增,
- 基于Groovy方式, 可以实现class的增加、修改、删除方法和属性等,可以实现Class的新增,