现在有一些老旧项目需要迁移,针对其中某些方法需要做重定向,例如:
Class.getResourceAsStream(String)
方法OldParent
由于这些老旧项目通过动态加载的方式运行,那么能否有方法,在运行期间(不是编译期间)达到这样的目的:
Class.getResourceAsStream(String)
方法调用,由另一个方法提供返回值OldParent
类的改成继承NewParent
,NewParent
继承自OldParent
尝试过采用 Spring AOP,第一个需求由于无法预知旧项目的Class
对象类型,无法拦截。
即项目中可能通过各种Class
对象,例如:
Class.class.getResourceAsStream(...);
String.class.getResourceAsStream(...);
"".getClass().getResourceAsStream(...);
this.getClass().getResourceAsStream(...);
至于第二个需求点,表示完全没有思路,目前在翻看 javassist 文档,不知道是否有解决方案。
JDK 1.7
1
3pmtea 2017-08-08 18:06:25 +08:00
自定义 class loader ?
|
2
alexwu 2017-08-08 18:33:13 +08:00
用 Java agent + asm/bcel/javassist 应该可以实现吧
|
3
breadenglish 2017-08-08 18:42:04 +08:00
为什么要在运行期间,而不是编译期间解决这个问题?
|
4
gam2046 OP @breadenglish 算是历史遗留原因,因为旧项目在不同环境下需要 hack 的方法不同。可以理解为新环境下是提供一种模拟环境,供老旧项目运行。
目前做过这样的尝试,通过 javassist 生成不检查类名安全检查以及 hack 过的 Class 字节码的 ClassLoader 对象,然后试图通过反射修改当前类加载器的父类加载器,以此绕过双亲委派机制,结果并不成功,程序运行直接输出 Exception in thread "main" 且没有退出,仿佛一种假死状态。代码如下: ``` public class JavassistHelp { static ClassLoader hackClassLoader = new ClassLoader() { private byte[] classBytecode = null; private byte[] classBytecode() { if (this.classBytecode == null) { ClassPool classPool = ClassPool.getDefault(); try { CtClass ordClass = classPool.get("java.lang.Class"); ordClass.defrost(); CtMethod ordMethod = ordClass.getDeclaredMethod("getResourceAsStream"); ordMethod.insertBefore("return new java.io.ByteArrayInputStream(new byte[]{1, 3, 5, 7, 9});"); this.classBytecode = ordClass.toBytecode(); } catch (NotFoundException e) { e.printStackTrace(); } catch (CannotCompileException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return classBytecode; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { if ("java.lang.Class".equals(name)) { System.err.println("loadClass hack"); return this.defineClass(name, this.classBytecode(), 0, this.classBytecode().length); } String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { e.printStackTrace(); } return null; } }; public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { // 通过反射修改父类加载器 Field parent = ClassLoader.class.getDeclaredField("parent"); parent.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(parent, parent.getModifiers() & ~Modifier.FINAL); parent.set(JavassistHelp.class.getClassLoader(), JavassistHelp.hackClassLoader); System.err.println("load resource is " + String.class.getResourceAsStream("111")); } } ``` |
5
SoloCompany 2017-08-09 00:23:35 +08:00
第一个问题,你需要直接置换掉 bootstrapclasspath
根据 jvm 的 spec,默认的 classloader 实现总是会优先使用 parent loader 以防止系统类被轻易改写 当然如果你的应用是比如类似 webapp 这类明确说明不符合以上规范的,很简单,把 WebappClassLoader 也置换掉 第二个问题,你可以先使用编译手段把 OldParent 改成自己想要的样子,然后把 patch 过的 class 和你的老程序 jar 放一起,确保在路径上能够优先加载即可 |
6
erhu 2017-08-09 06:50:51 +08:00
为什么用 ordMethod.insertBefore(),直接用 replace() 啊
|