上次说了可以改写类,那进一步的我们可以做一下类似于之前提过的通过字节码来做切面的工作 首先我们有一个很简单的类和方法1 2 3 4 5 6 7 8 public class DemoService { public List<String> queryList (String name, String title, String n3) { List<String> list = new ArrayList <>(); list.add(name); list.add(title); return list; } }
就是一个方法,输入几个字符串,然后加到list里,那么比如我们想知道打印入参和返回结果 这里比较类似于我们在用aop的时候就是织入代码 正好javassist有这样的操作接口 就是我们可以找到方法,然后使用1 javassist.CtBehavior#insertBefore(java.lang.String)
和1 javassist.CtBehavior#insertAfter(java.lang.String)
就可以在我们找到类和方法以后1 2 3 ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get(targetClass);CtMethod method = cc.getDeclaredMethod(targetMethod);
对方法的前后进行逻辑织入1 2 3 4 5 6 7 8 9 10 11 12 method.insertBefore( "{ java.lang.StringBuilder _paramValues = new StringBuilder();" + " Object[] params = $args;" + " if(params != null) {" + " for(int i=0; i<params.length; i++) {" + " if(i > 0) _paramValues.append(\", \");" + " _paramValues.append(String.valueOf(params[i]));" + " }" + " }" + " System.out.println(\"[Method Entry] " + targetMethod + " params: \" + _paramValues.toString());" + "}" );
这里我们使用了直接织入代码的形式,在javassist中可以用 $args
来获取参数,然后循环参数, 把每个参数拼接以后作为字符串输出 然后我们想要在函数返回前获取返回值,这个也是有个语法糖 $_
1 2 3 4 5 6 7 StringBuilder after = new StringBuilder (); after.append("{" ) .append(" java.util.List _result = $_;\n" ) .append(" String _resultStr = (_result != null ? _result.toString() : \"null\");\n" ) .append(" int _size = (_result != null ? _result.size() : 0);\n" ) .append(" System.out.println(\"[Method Exit] " + targetMethod + " returns: \" + _resultStr + \" size: \" + _size);\n" ) .append("}" );
这里刚好我是list返回的,就直接这么处理了, 我们通过一个简单的调用来验证下1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public static void main (String[] args) throws Exception { addMonitoringNew("org.example.DemoService" , "queryList" ); ClassLoader classLoader = new ClassLoader () { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name.equals("org.example.DemoService" )) { try { byte [] bytes = java.nio.file.Files.readAllBytes( java.nio.file.Paths.get("org/example/DemoService.class" ) ); return defineClass(name, bytes, 0 , bytes.length); } catch (Exception e) { throw new ClassNotFoundException ("Failed to load enhanced class" , e); } } return super .loadClass(name); } }; Class<?> enhancedClass = classLoader.loadClass("org.example.DemoService" ); Object instance = enhancedClass.newInstance(); Method processMethod = enhancedClass.getMethod("queryList" , String.class, String.class, String.class); Object result = processMethod.invoke(instance, "name1" , "title1" , "n3" ); System.out.println("Method returned: " + result); } } 可以看到我们的输出结果 ```java Successfully enhanced method: queryList [Method Entry] queryList params: name1, title1, n3 [Method Exit] queryList returns: [name1, title1] size: 2 Method returned: [name1, title1]
这里其实核心很依赖两个语法糖,类似这样的还有一些1 2 3 4 5 6 7 8 9 10 11 12 $0 , $1 , $2 , ... this and actual parameters $args An array of parameters. The type of $args is Object[]. $$ All actual parameters. For example, m($$) is equivalent to m ($1 ,$2 ,...) $cflow(...) cflow variable $r The result type. It is used in a cast expression. $w The wrapper type. It is used in a cast expression. $_ The resulting value $sig An array of java.lang.Class objects representing the formal parameter types. $type A java.lang.Class object representing the formal result type. $class A java.lang.Class object representing the class currently edited.
可以结合文档 仔细学习下