javassist 运行期改类
https://www.cnblogs.com/baiqiantao/p/10235049.html
https://www.cnblogs.com/xiaofuge/p/12868742.html
结论:
1 加载前可以直接替换,加载(new,loadclass,forname)后要hotspot开端口
2 函数入参变量使用$1, $2,数组参数$2[0];成员变量用变量名
3 函数中,所有类要用类全名(包括同包),除了String,可能属于启动类加载器
4 空格 \n \t无关紧要
5 泛型不可用
碰到的问题:
1 泛型不可用
2 javassist.CannotCompileException: [source error] no such class 用全限定名
package javassisttest; import lc.A; import javassisttest.ByRef; /** * Created by joyce on 2020/7/29. */ public class ByMod { /** * 测试 * 1 是否能加载器/加载后替换函数体 * 加载前可以直接替换,加载后要hotspot开端口 * 2 局部变量引用 * 要用$1 $2 * 3 局部数组变量引用 * $x[0] $x[1] * 4 String.class是否能引用到,是否需要java.lang.String.class * 不需要,可能启动类加载器的不需要 * 5 \n 空格 \t * 6 用全名引用类,import无用,同包无用 * 7 泛型不可用 */ public static final String MODIFIED = "{System.out.println(\"javassist \" + mem + $1 + $2[0] + String.class + \", modified\");\n" + " \tSystem.out.println(\"done\");\n" + " \tSystem.out.println(lc.A.class);\n" + //7 "Class<? extends java.lang.Object> o = null;" + " \tSystem.out.println(javassisttest.ByRef.class);\n}"; /** * 测试 * 1 函数内如何引用成员变量 */ private String mem = "memval"; /** * 测试 * 1 数组类型入参 * 2 函数内如何引用方法局部变量 * @param xxx * @param objects */ public void print(String xxx, Object [] objects) { System.out.println("not yet mod"); Class<? extends Object> o = null; } }
package javassisttest; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMember; import javassist.CtMethod; import static javassisttest.ByMod.MODIFIED; /** * Created by joyce on 2020/7/29. */ public class ModerBeforeLoad { public static void main(String [] f) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("javassisttest.ByMod"); CtMethod cm = cc.getDeclaredMethod("print", new CtClass[]{pool.get("java.lang.String"), pool.get("java.lang.Object[]")}); cm.setBody(MODIFIED); cc.toClass(); ByMod byMod = new ByMod(); byMod.print("be", new String[]{"heefsf"}); } }
输出:
javassist memvalbeheefsfclass java.lang.String, modified
done
class lc.A
class javassisttest.ByRef
package javassisttest; import com.sun.org.apache.xpath.internal.operations.Mod; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.util.HotSwapper; import lc2.E; import static javassisttest.ByMod.MODIFIED; /** * Created by joyce on 2020/7/29. */ public class ModerAfterLoad { public static void main(String [] f) throws Exception { /** * 这三种方式都会造成 * by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): * attempted duplicate class definition for name: "javassisttest/ByMod" */ // ByMod byMod = new ByMod(); // Class.forName("javassisttest.ByMod"); ModerAfterLoad.class.getClassLoader().loadClass("javassisttest.ByMod"); ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("javassisttest.ByMod"); CtMethod cm = cc.getDeclaredMethod("print", new CtClass[]{pool.get("java.lang.String"), pool.get("java.lang.Object[]")}); cm.setBody(MODIFIED); try { cc.toClass(); } catch (Exception e) { System.out.println(e.getMessage()); } ByMod byMod = new ByMod(); byMod.print("he", new String[]{"xxx"}); // idea VM : -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 HotSwapper hs = new HotSwapper(8000); hs.reload(ByMod.class.getName(), cc.toBytecode()); byMod.print("he", new String[]{"xxx"}); } }
输出:
Listening for transport dt_socket at address: 8000
by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "javassisttest/ByMod"
not yet mod
javassist memvalhexxxclass java.lang.String, modified
done
class lc.A
class javassisttest.ByRef
实践:myorm【重点】$$27
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ToBeInterceptedForMapper { Class<? extends MapperProcessor> mapperProcessor();
abstract public class AbstractTimezoneProcessor extends DefaultMapperProcessor { @Override public Object execute() throws Exception { Object target = super.getValue(); Class c = target.getClass(); Method method = super.getMethod(); logger.info("mapper aop {} - {}.{}", this.getClass().getName(), c, method); //return doExecute(); return super.execute(); } private Object doExecute() throws Exception { Object target = super.getValue(); Class c = target.getClass(); Method method = super.getMethod(); Object[] args = super.getArgs(); ToBeInterceptedForMapper annotation = (ToBeInterceptedForMapper)method.getAnnotation(ToBeInterceptedForMapper.class); if(annotation != null) { return doTimezoneProcessor(); } else { logger.error("error timezone aop : {}, {}, {}", c, method, args); return method.invoke(target, args); } } abstract protected Object doTimezoneProcessor() throws Exception; abstract protected Date turnDate(Date origin, int dbZone); protected void setDate(Object obj, int dbZone) throws Exception { Class cl = obj.getClass(); Field[] fields = cl.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if(!java.util.Date.class.isAssignableFrom(field.getType())) continue; if (Modifier.isStatic(field.getModifiers())) continue; if (field.isAnnotationPresent(SCEF_DB_NO_FIELD.class)) continue; if(field.get(obj) == null) continue; Date date = (Date)field.get(obj); Date tranzDate = turnDate(date, dbZone); field.set(obj, tranzDate); } }
public class TimezoneProcessorForInsertAndUpdate extends AbstractTimezoneProcessor { @Override protected Object doTimezoneProcessor() throws Exception { Object target = super.getValue(); Method method = super.getMethod(); Object[] args = super.getArgs(); int dbZone = -4; setDate(args[0], dbZone); Object res = method.invoke(target, args); return res; } @Override protected Date turnDate(Date origin, int dbZone) { return TimezoneManager.timezoneJvmToDbWhenInsertAndUpdate(origin, dbZone); } }
public class TimezoneProcessorForQuery extends AbstractTimezoneProcessor { @Override protected Object doTimezoneProcessor() throws Exception { Object target = super.getValue(); Method method = super.getMethod(); Object[] args = super.getArgs(); int dbZone = -4; Object res = method.invoke(target, args); Class resType = res.getClass(); if(List.class.isAssignableFrom(resType) || Set.class.isAssignableFrom(resType)) { Collection collection = (Collection) res; Iterator iterator = collection.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); setDate(obj, dbZone); } } else { setDate(res, dbZone); } return res; } @Override protected Date turnDate(Date origin, int dbZone) { return TimezoneManager.timezoneDbToJvmWhenQuery(origin, dbZone); } }