Spring AOP 失效的真正元凶

Understanding AOP proxies

Spring AOP is proxy-based. It is vitally important that you grasp the semantics of what that last statement actually means before you write your own aspects or use any of the Spring AOP-based aspects supplied with the Spring Framework.

Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object reference, as illustrated by the following code snippet.

public class SimplePojo implements Pojo {  
 public void foo() {      
 // this next method invocation is a direct call on the 
'this' reference
      this.bar();
   }   
   public void bar() {      // some logic...
   }
}

If you invoke a method on an object reference, the method is invoked directly on that object reference, as can be seen below.

public class Main {   
    public static void main(String[] args) {
      Pojo pojo = new SimplePojo();      
 // this is a direct method call on the 'pojo' reference
      pojo.foo();
   }
}

Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet.

public class Main {   

    public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new
 SimplePojo());
      factory.addInterface(Pojo.class);
      factory.addAdvice(new RetryAdvice());

      Pojo pojo = (Pojo) factory.getProxy();      
      // this is a method call on the proxy!
      pojo.foo();
   }
}

The key thing to understand here is that the client code inside the main(..) of the Main class has a reference to the proxy. This means that method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, the SimplePojo reference in this case, any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

Okay, so what is to be done about this? The best approach (the term best is used loosely here) is to refactor your code such that the self-invocation does not happen. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:

public class SimplePojo implements Pojo {   

  public void foo() {    
      // this works, but... gah!
      ((Pojo) AopContext.currentProxy()).bar();
   }   
   public void bar() {     
       // some logic...
   }
}

This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created:

public class Main {  
 
   public static void main(String[] args) {
    
      ProxyFactory factory = new ProxyFactory
(new SimplePojo());
      factory.adddInterface(Pojo.class);
      factory.addAdvice(new RetryAdvice());
      factory.setExposeProxy(true);

      Pojo pojo = (Pojo) factory.getProxy();

      // this is a method call on the proxy!
      pojo.foo();
   }
}

Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP framework.

Programmatic creation of @AspectJ Proxies

In addition to declaring aspects in your configuration using either <aop:config> or <aop:aspectj-autoproxy>, it is also possible programmatically to create proxies that advise target objects. For the full details of Spring's AOP API, see the next chapter. Here we want to focus on the ability to automatically create proxies using @AspectJ aspects.

The class org.springframework.aop.aspectj.annotation.AspectJProxyFactory can be used to create a proxy for a target object that is advised by one or more @AspectJ aspects. Basic usage for this class is very simple, as illustrated below. See the Javadocs for full information.

// create a factory that can generate a proxy for the given target object AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); // add an aspect, the class must be an @AspectJ aspect // you can call this as many times as you need with different aspects factory.addAspect(SecurityManager.class); // you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect factory.addAspect(usageTracker); // now get the proxy object... MyInterfaceType proxy = factory.getProxy();

Using AspectJ with Spring applications

Everything we've covered so far in this chapter is pure Spring AOP. In this section, we're going to look at how you can use the AspectJ compiler/weaver instead of, or in addition to, Spring AOP if your needs go beyond the facilities offered by Spring AOP alone.

Spring ships with a small AspectJ aspect library, which is available standalone in your distribution as spring-aspects.jar; you'll need to add this to your classpath in order to use the aspects in it. Section 7.8.1, “Using AspectJ to dependency inject domain objects with Spring” and Section 7.8.2, “Other Spring aspects for AspectJ” discuss the content of this library and how you can use it. Section 7.8.3, “Configuring AspectJ aspects using Spring IoC” discusses how to dependency inject AspectJ aspects that are woven using the AspectJ compiler. Finally, Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework” provides an introduction to load-time weaving for Spring applications using AspectJ.

文章摘自:Spring官网

Java高级架构∣干货|交流

原文发布于微信公众号 - JAVA高级架构(gaojijiagou)

原文发表时间:2018-03-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏运维

linux文件树

以前有意找这方面的资料,今天突然发现在系统中就有 linux系统用man hier solaris用man  filesystem 其结果如下     ...

1112
来自专栏c#开发者

domaincontext load 回调

This post is not specific to RIA Services but I thought I'd add it to the title ...

3409
来自专栏Netkiller

Spring boot with Email

本文节选自《Netkiller Java 手札》 地址: http://www.netkiller.cn/java/ 9.12. Spring boot wit...

4485
来自专栏运维

U盘安装FreeBSD8.2+Panabit安装配置+Samba文件服务器+DNS缓存服务器

  dd if=FreeBSD-8.2-RELEASE-i386-memstick.img of=/dev/sdb bs=10240 conv=sync 二,安...

2232
来自专栏Netkiller

Spring boot with Velocity template

本文节选自《Netkiller Java 手札》 地址: http://www.netkiller.cn/java/index.html 9.13. Sprin...

9444
来自专栏生信技能树

天真的我准备把全部流程迁移到GATK4

但是走到了 SplitNCigarReads 才发现,这个命令当初学的太久了,忘记各个参数啥意思了,就想搜索看看如何转换。

1801
来自专栏大数据学习笔记

Hadoop基础教程-第9章 HA高可用(9.3 HDFS 高可用运行)(草稿)

第9章 HA高可用 9.3 HDFS 高可用运行 9.3.1 HA节点规划 节点 IP Zookeeper NameNode JournalNode Da...

2775
来自专栏我的博客

Zend Framework自动加载、简单路由配置(Bootstrap.php)

<?php class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { /* * 自动加...

3568
来自专栏everhad

转载:Package by feature, not layer

The first question in building an application is "How do I divide it up into pac...

1410
来自专栏别先生

Caused by: java.net.ConnectException: Connection refused: master/192.168.3.129:7077

1:启动Spark Shell,spark-shell是Spark自带的交互式Shell程序,方便用户进行交互式编程,用户可以在该命令行下用scala编写spa...

8776

扫码关注云+社区

领取腾讯云代金券