锁定老帖子 主题:AOP的实现机制
该帖已经被评为精华帖
|
||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | |||||||||||||||||||||||||||
发表时间:2011-10-18
最后修改:2011-10-20
附件中有本文的源代码和Pdf版。本文写的很长的原因,是不希望大家学习AOP时到处找资料,大家有时间可以按照本文动手实践下,相信会有非常大的收获的,有什么问题互相交流,有问必答!
AOP就是面向切面编程,我们可以从几个层面来实现AOP。
在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较。
3 AOP的实现机制
public static void main(String[] args) { //需要代理的接口,被代理类实现的多个接口都必须在这里定义 Class[] proxyInterface = new Class[] { IBusiness.class, IBusiness2.class }; //构建AOP的Advice,这里需要传入业务类的实例 LogInvocationHandler handler = new LogInvocationHandler(new Business()); //生成代理类的字节码加载器 ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader(); //织入器,织入代码并生成代理类 IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler); //使用代理类的实例来调用方法。 proxyBusiness.doSomeThing2(); ((IBusiness) proxyBusiness).doSomeThing(); } /** * 打印日志的切面 */ public static class LogInvocationHandler implements InvocationHandler { private Object target; //目标对象 LogInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //执行原有逻辑 Object rev = method.invoke(target, args); //执行织入的日志,你可以控制哪些方法执行切入逻辑 if (method.getName().equals("doSomeThing2")) { System.out.println("记录日志"); } return rev; } } 接口IBusiness和IBusiness2定义省略。
业务类,需要代理的类。 public class Business implements IBusiness, IBusiness2 { @Override public boolean doSomeThing() { System.out.println("执行业务逻辑"); return true; } @Override public void doSomeThing2() { System.out.println("执行业务逻辑2"); } }
输出 执行业务逻辑2 记录日志 执行业务逻辑
可以看到“记录日志”的逻辑切入到Business类的doSomeThing方法前了。
3.1.2 动态代理原理 //获取代理类 Class cl = getProxyClass(loader, interfaces); //获取带有InvocationHandler参数的构造方法 Constructor cons = cl.getConstructor(constructorParams); //把handler传入构造方法生成实例 return (Object) cons.newInstance(new Object[] { h });
其中getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。清单三:查找代理类。 // 缓存的key使用接口名称生成的List Object key = Arrays.asList(interfaceNames); synchronized (cache) { do { Object value = cache.get(key); // 缓存里保存了代理类的引用 if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 代理类已经存在则返回 return proxyClass; } else if (value == pendingGenerationMarker) { // 如果代理类正在产生,则等待 try { cache.wait(); } catch (InterruptedException e) { } continue; } else { //没有代理类,则标记代理准备生成 cache.put(key, pendingGenerationMarker); break; } } while (true); }
代理类的生成主要是以下这两行代码。 清单四:生成并加载代理类
//生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘) proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces); //使用类加载器将字节码加载到内存中 proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。 //添加接口中定义的方法,此时方法体为空 for (int i = 0; i < this.interfaces.length; i++) { localObject1 = this.interfaces[i].getMethods(); for (int k = 0; k < localObject1.length; k++) { addProxyMethod(localObject1[k], this.interfaces[i]); } } //添加一个带有InvocationHandler的构造方法 MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1); //循环生成方法体代码(省略) //方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略) this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;") //将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。 localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class"); localFileOutputStream.write(this.val$classFile);
那么通过以上分析,我们可以推出动态代理为我们生成了一个这样的代理类。把方法doSomeThing的方法体修改为调用LogInvocationHandler的invoke方法。
public class ProxyBusiness implements IBusiness, IBusiness2 { private LogInvocationHandler h; @Override public void doSomeThing2() { try { Method m = (h.target).getClass().getMethod("doSomeThing", null); h.invoke(this, m, null); } catch (Throwable e) { // 异常处理(略) } } @Override public boolean doSomeThing() { try { Method m = (h.target).getClass().getMethod("doSomeThing2", null); return (Boolean) h.invoke(this, m, null); } catch (Throwable e) { // 异常处理(略) } return false; } public ProxyBusiness(LogInvocationHandler h) { this.h = h; } //测试用 public static void main(String[] args) { //构建AOP的Advice LogInvocationHandler handler = new LogInvocationHandler(new Business()); new ProxyBusiness(handler).doSomeThing(); new ProxyBusiness(handler).doSomeThing2(); } }
3.1.3 小结
public static void main(String[] args) { byteCodeGe(); } public static void byteCodeGe() { //创建一个织入器 Enhancer enhancer = new Enhancer(); //设置父类 enhancer.setSuperclass(Business.class); //设置需要织入的逻辑 enhancer.setCallback(new LogIntercept()); //使用织入器创建子类 IBusiness2 newBusiness = (IBusiness2) enhancer.create(); newBusiness.doSomeThing2(); } /** * 记录日志 */ public static class LogIntercept implements MethodInterceptor { @Override public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { //执行原有逻辑,注意这里是invokeSuper Object rev = proxy.invokeSuper(target, args); //执行织入的日志 if (method.getName().equals("doSomeThing2")) { System.out.println("记录日志"); } return rev; } }
3.3 自定义类加载器
//获取存放CtClass的容器ClassPool ClassPool cp = ClassPool.getDefault(); //创建一个类加载器 Loader cl = new Loader(); //增加一个转换器 cl.addTranslator(cp, new MyTranslator()); //启动MyTranslator的main函数 cl.run("jsvassist.JavassistAopDemo$MyTranslator", args); 清单九:类加载监听器 public static class MyTranslator implements Translator { public void start(ClassPool pool) throws NotFoundException, CannotCompileException { } /* * * 类装载到JVM前进行代码织入 */ public void onLoad(ClassPool pool, String classname) { if (!"model$Business".equals(classname)) { return; } //通过获取类文件 try { CtClass cc = pool.get(classname); //获得指定方法名的方法 CtMethod m = cc.getDeclaredMethod("doSomeThing"); //在方法执行前插入代码 m.insertBefore("{ System.out.println(\"记录日志\"); }"); } catch (NotFoundException e) { } catch (CannotCompileException e) { } } public static void main(String[] args) { Business b = new Business(); b.doSomeThing2(); b.doSomeThing(); } } 输出: 执行业务逻辑2 记录日志 执行业务逻辑 public class MyClassFileTransformer implements ClassFileTransformer { /** * 字节码加载到虚拟机前会进入这个方法 */ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println(className); //如果加载Business类才拦截 if (!"model/Business".equals(className)) { return null; } //javassist的包名是用点分割的,需要转换下 if (className.indexOf("/") != -1) { className = className.replaceAll("/", "."); } try { //通过包名获取类文件 CtClass cc = ClassPool.getDefault().get(className); //获得指定方法名的方法 CtMethod m = cc.getDeclaredMethod("doSomeThing"); //在方法执行前插入代码 m.insertBefore("{ System.out.println(\"记录日志\"); }"); return cc.toBytecode(); } catch (NotFoundException e) { } catch (CannotCompileException e) { } catch (IOException e) { //忽略异常处理 } return null; }
3.4.2 注册转换器 public class MyClassFileTransformer implements ClassFileTransformer { public static void premain(String options, Instrumentation ins) { //注册我自己的字节码转换器 ins.addTransformer(new MyClassFileTransformer()); } }
3.4.3 配置和执行 Manifest-Version: 1.0 Premain-Class: bci. MyClassFileTransformer 然后在JVM的启动参数里加上。-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar 3.4.4 输出 执行main函数,你会发现切入的代码无侵入性的织入进去了。 public static void main(String[] args) { new Business().doSomeThing(); new Business().doSomeThing2(); } 输出 model/Business sun/misc/Cleaner java/lang/Enum model/IBusiness model/IBusiness2 记录日志 执行业务逻辑 执行业务逻辑2 java/lang/Shutdown java/lang/Shutdown$Lock
从输出中可以看到系统类加载器加载的类也经过了这里。
4 AOP实战
4.1 Spring的AOP public IMsgFilterService getThis() { return (IMsgFilterService) AopContext.currentProxy(); } public boolean evaluateMsg () { // 执行此方法将织入切入逻辑 return getThis().evaluateMsg(String message); } @MethodInvokeTimesMonitor("KEY_FILTER_NUM") public boolean evaluateMsg(String message) { 不能获取代理类 public boolean evaluateMsg () { // 执行此方法将不会织入切入逻辑 return evaluateMsg(String message); } @MethodInvokeTimesMonitor("KEY_FILTER_NUM") public boolean evaluateMsg(String message) {
4.2 参考资料
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
不错 仔细看下
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
很详细,慢慢复习,学习
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
lz写书呢,写这么多章节阿
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
agapple 写道 lz写书呢,写这么多章节阿
主要是想写全,所以花了一个星期的时间写的,后续有想到的再补充下,附件里有个更全的版本。 |
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
说实在的,只看了大概.不过可以看出LZ很用心的整理出所有AOP实现的方法和原理.标记以下,以后慢慢看.
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
别的不说,这个排版看出楼主就很用心。收了,正好最近可能会用到aop。谢了
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
正好像了解一下aop的内容,
学习一下 |
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
正在学习AOP,感谢LZ的分享整理。
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||
发表时间:2011-10-18
aop能用到的地方其实并不多。
|
||||||||||||||||||||||||||||
返回顶楼 | ||||||||||||||||||||||||||||