- 浏览: 102587 次
- 性别:
- 来自: 北京
文章分类
最新评论
转载自:http://kiral.iteye.com/blog/1198982
附件中有本文的源代码和Pdf版。本文写的很长的原因,是不希望大家学习AOP时到处找资料,大家有时间可以按照本文动手实践下,相信会有非常大的收获的,有什么问题互相交流,有问必答!
1 AOP各种的实现
AOP就是面向切面编程,我们可以从几个层面来实现AOP。
在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较。
类别 |
机制 |
原理 |
优点 |
缺点 |
静态AOP |
静态织入 |
在编译期,切面直接以字节码的形式编译到目标字节码文件中。 |
对系统无性能影响。 |
灵活性不够。 |
动态AOP |
动态代理 |
在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。 |
相对于静态AOP更加灵活。 |
切入的关注点需要实现接口。对系统有一点性能影响。 |
动态字节码生成 |
在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。 |
没有接口也可以织入。 |
扩展类的实例方法为final时,则无法进行织入。 |
|
自定义类加载器 |
在运行期,目标加载前,将切面逻辑加到目标字节码里。 |
可以对绝大部分类进行织入。 |
代码中如果使用了其他类加载器,则这些类将不会被织入。 |
|
字节码转换 |
在运行期,所有类加载器加载字节码前,前进行拦截。 |
可以对所有类进行织入。 |
|
2 AOP里的公民
- Joinpoint:拦截点,如某个业务方法。
- Pointcut:Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint。
- Advice: 要切入的逻辑。
- Before Advice 在方法前切入。
- After Advice 在方法后切入,抛出异常时也会切入。
- After Returning Advice 在方法返回后切入,抛出异常则不会切入。
- After Throwing Advice 在方法抛出异常时切入。
- Around Advice 在方法执行前后切入,可以中断或忽略原有流程的执行。
- 公民之间的关系
织入器通过在切面中定义pointcut来搜索目标(被代理类)的JoinPoint(切入点),然后把要切入的逻辑(Advice)织入到目标对象里,生成代理类。
3 AOP的实现机制
本章节将详细介绍AOP有各种实现机制。
3.1 动态代理
Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,InvocationHandler是切面,它包含了Advice和Pointcut。
3.1.1 使用动态代理
那如何使用动态代理来实现AOP。下面的例子演示在方法执行前织入一段记录日志的代码,其中Business是代理类,LogInvocationHandler是记录日志的切面,IBusiness, IBusiness2是代理类的接口,Proxy.newProxyInstance是织入器。
清单一:动态代理的演示
- 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 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");
- }
- }
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 动态代理原理
本节将结合动态代理的源代码讲解其实现原理。动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。让我们进入newProxyInstance方法观摩下,核心代码其实就三行。
清单二:生成代理类
- //获取代理类
- 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);
- }
//获取代理类 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);
//生成代理类的字节码文件并保存到硬盘中,默认不会写入硬盘 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);
//添加接口中定义的方法,此时方法体为空 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();
- }
- }
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 小结
从前两节的分析我们可以看出,动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,经过测试大概每个代理类比静态代理多出10几毫秒的消耗。其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。
3.2 动态字节码生成
使用动态字节码生成技术实现AOP原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP不需要基于接口。
本节介绍如何使用Cglib来实现动态字节码技术。Cglib是一个强大的,高性能的Code生成类库,它可以在运行期间扩展Java类和实现Java接口,它封装了Asm,所以使用Cglib前需要引入Asm的jar。 清单七:使用CGLib实现AOP
- 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;
- }
- }
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 自定义类加载器
如果我们实现了一个自定义类加载器,在类加载到JVM之前直接修改某些类的方法,并将切入逻辑织入到这个方法里,然后将修改后的字节码文件交给虚拟机运行,那岂不是更直接。
Javassist是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。这比使用Cglib实现AOP更加高效,并且没太多限制,实现原理如下图:
我们使用系统类加载器启动我们自定义的类加载器,在这个类加载器里加一个类加载监听器,监听器发现目标类被加载时就织入切入逻辑,咱们再看看使用Javassist实现AOP的代码:
清单八:启动自定义的类加载器
- //获取存放CtClass的容器ClassPool
- ClassPool cp = ClassPool.getDefault();
- //创建一个类加载器
- Loader cl = new Loader();
- //增加一个转换器
- cl.addTranslator(cp, new MyTranslator());
- //启动MyTranslator的main函数
- cl.run("jsvassist.JavassistAopDemo$MyTranslator", args);
//获取存放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();
- }
- }
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 记录日志 执行业务逻辑
其中Bussiness类在本文的清单一中定义。看起来是不是特别简单,CtClass是一个class文件的抽象描述。咱们也可以使用insertAfter()在方法的末尾插入代码,使用insertAt()在指定行插入代码。
3.3.1 小结
从本节中可知,使用自定义的类加载器实现AOP在性能上要优于动态代理和Cglib,因为它不会产生新类,但是它仍然存在一个问题,就是如果其他的类加载器来加载类的话,这些类将不会被拦截。
3.4 字节码转换
自定义的类加载器实现AOP只能拦截自己加载的字节码,那么有没有一种方式能够监控所有类加载器加载字节码呢?有,使用Instrumentation,它是 Java 5 提供的新特性,使用 Instrumentation,开发者可以构建一个字节码转换器,在字节码加载前进行转换。本节使用Instrumentation和javassist来实现AOP。
3.4.1 构建字节码转换器
首先需要创建字节码转换器,该转换器负责拦截Business类,并在Business类的doSomeThing方法前使用javassist加入记录日志的代码。
- 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;
- }
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 注册转换器
使用premain函数注册字节码转换器,该方法在main函数之前执行。
- public class MyClassFileTransformer implements ClassFileTransformer {
- public static void premain(String options, Instrumentation ins) {
- //注册我自己的字节码转换器
- ins.addTransformer(new MyClassFileTransformer());
- }
- }
public class MyClassFileTransformer implements ClassFileTransformer { public static void premain(String options, Instrumentation ins) { //注册我自己的字节码转换器 ins.addTransformer(new MyClassFileTransformer()); } }
3.4.3 配置和执行
需要告诉JVM在启动main函数之前,需要先执行premain函数。首先需要将premain函数所在的类打成jar包。并修改该jar包里的META-INF\MANIFEST.MF 文件。
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();
- }
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
model/Business sun/misc/Cleaner java/lang/Enum model/IBusiness model/IBusiness2 记录日志 执行业务逻辑 执行业务逻辑2 java/lang/Shutdown java/lang/Shutdown$Lock
从输出中可以看到系统类加载器加载的类也经过了这里。
4 AOP实战
说了这么多理论,那AOP到底能做什么呢? AOP能做的事情非常多。
- 性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
- 缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
- 软件破解,使用AOP修改软件的验证类的判断逻辑。
- 记录日志,在方法执行前后记录系统日志。
- 工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
- 权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。
4.1 Spring的AOP
Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。但Spring的AOP有一定的缺点,第一个只能对方法进行切入,不能对接口,字段,静态代码块进行切入(切入接口的某个方法,则该接口下所有实现类的该方法将被切入)。第二个同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean。第三个性能不是最好的,从3.3章节我们得知使用自定义类加载器,性能要优于动态代理和CGlib。
可以获取代理类
- 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 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) {
public boolean evaluateMsg () { // 执行此方法将不会织入切入逻辑 return evaluateMsg(String message); } @MethodInvokeTimesMonitor("KEY_FILTER_NUM") public boolean evaluateMsg(String message) {
4.2 参考资料
- Java 动态代理机制分析及扩展
- CGlib的官方网站
- ASM官方网站
- JbossAOP
- Java5特性Instrumenttation实践
- Aop实现机制测试代码.rar (2.3 MB)
- 下载次数: 785
- AOP的实现机制.pdf (794.8 KB)
- 下载次数: 1142
发表评论
-
net-snmp相关
2012-08-06 17:39 750final TransportMapping ... -
IBM网站的一些帖子
2012-05-18 14:18 0http://www.ibm.com/developerwor ... -
freeradius-server-2.1.12.tar.bz2+suse10 64+Oracle11配置
2012-04-26 21:47 2104使用的环境 freeradius-server-2.1.12. ... -
<java并发编程实践>第七章读书笔记
2012-04-25 09:02 0s -
ssss
2012-04-19 17:25 0int main(void) { int soc ... -
linux socket
2012-04-19 17:23 0http://blog.csdn.net/stevexk/ar ... -
C语言基本知识点2
2012-04-11 21:05 879scanf() getchar() gets() ----- ... -
cccccc
2012-04-11 15:36 0strlen() scanf() printf() ge ... -
我的第一个和第二个C语言程序
2012-04-10 20:27 764花了我超过6个小时呢,不容易哦。 #include< ... -
C语言基本知识点
2012-04-10 08:47 801编译和链接 1.编译并链接一个完全包含于一个源文件的C程序 ... -
如何计算一个对象的大小
2012-04-09 14:36 0http://blog.csdn.net/hoszb/arti ... -
jvm调优
2012-03-29 19:21 0http://blog.csdn.net/cuker919/a ... -
linux c
2012-03-13 18:15 0http://blog.csdn.net/muge0913/a ... -
sss3
2012-03-10 11:00 0答案就在下面: 因为gnetAppService居然不是单例. ... -
sss2
2012-03-10 10:53 0at org.springframework.beans.fa ... -
ssss
2012-03-10 10:51 01、 因为两个环境一个可以正常启动,一个不正常。怀疑两个环境的 ... -
为最大发挥软件性能,建议对eclipse(eclipse.ini 文件)作以下配置
2012-02-29 20:17 0为最大发挥软件性能,建议对eclipse(eclipse.in ... -
尚珊珊
2012-02-25 13:08 0http://software.intel.com/zh-cn ... -
三种排序方法
2012-02-16 19:38 0http://blog.csdn.net/fenglibing ... -
安全8
2012-02-10 19:08 0安装时提供配置界面,用户可以修改FTP的密码支持的FTP密码修 ...
相关推荐
**Spring AOP 实现机制详解** Spring AOP(面向切面编程)是Spring框架的核心特性之一,它允许程序员在不修改源代码的情况下,通过“切面”来插入额外的业务逻辑,如日志、事务管理等。AOP的引入极大地提高了代码的...
下面将详细探讨AOP的实现机制。 一、AOP核心概念 1. 切面(Aspect):切面是关注点的模块化,它包含了一组相关通知(advice)的集合,以及这些通知如何插入到应用程序的定义。 2. 连接点(Join Point):程序执行...
### AOP的实现机制 #### 1. AOP各种的实现 面向切面编程(Aspect Oriented Programming,简称AOP)是一种编程范式,旨在提高软件模块化程度,通过将横切关注点(如日志记录、事务管理等)与核心业务逻辑分离,从而...
学习AOP的实现机制对于理解和优化应用程序的结构至关重要,它能帮助开发者更好地组织代码,降低复杂性,同时保持代码的清晰性和可测试性。通过深入理解AOP,开发者可以更有效地利用这一工具来提升软件的可维护性和可...
让我们深入探讨这两个概念及其在Spring中的实现机制。 首先,Spring的控制反转(IOC)是一种设计模式,它将对象的创建和管理权从代码中移出,交由Spring容器负责。这样可以降低组件之间的耦合,提高代码的可测试性...
### AOP及其Java实现机制 #### 1. 引言 传统的编程技术通过分解的方式将一个复杂的软件系统拆分成相对较小、易于管理和理解的模块。然而,在实际开发过程中,一些跨模块的行为(如日志记录、事务管理等)难以封装...
NULL 博文链接:https://zhang-yingjie-qq-com.iteye.com/blog/319927
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,旨在提供一种方法来组织和模块化系统中的...通过阅读《AOP动态代理(反射机制)》这本书,你可以深入理解这些概念,并通过丰富的示例掌握其实现方法。
Spring AOP就是基于代理机制实现的,它可以将这些关注点与核心业务逻辑解耦。Spring支持两种类型的代理:JDK动态代理和CGLIB代理。JDK代理适用于目标对象实现了接口的情况,而CGLIB代理则是在目标对象没有接口或者...
下面我们将深入探讨Spring AOP的概念、实现方式以及实际应用。 首先,我们需要理解AOP的基本概念。AOP是一种编程范式,它将关注点分离,使系统中的核心业务逻辑与辅助性任务(如日志、安全检查等)解耦。在Spring中...
在net core2.2时,我当时就尝试过用autofac实现aop,但这次我不想用autofac,我用了一个更轻量级的框架,AspectCore。 用起来非常非常的简单,但一开始还是走了一点弯路,主要是网上都是net core3以下的教程,3以下...
Spring AOP通过动态代理和切面编程,为Java应用提供了一种灵活、强大的横切关注点处理机制。通过理解和应用Spring AOP,开发者可以构建出更加模块化、易于维护的系统。随着技术的不断发展,Spring AOP将继续在企业级...
面向切面编程(AOP)是一种编程范式,旨在将横切关注点(如日志、安全等)与业务逻辑分离,从而提高模块化。...利用Java反射机制和Spring AOP框架,开发者可以方便地实现AOP,从而提升代码的模块化和可维护性。
基于注解实现SpringAop基于注解实现SpringAop基于注解实现SpringAop
在这个"springAOP实现数据字典.zip"压缩包中,我们主要关注的是如何利用Spring AOP来实现数据字典的功能。数据字典是系统设计中一个关键的组成部分,它存储了系统中所有数据的描述,包括数据项、数据结构、数据流、...