【问题标题】:Spring AOP not working for method call inside another methodSpring AOP 不适用于另一个方法中的方法调用
【发布时间】:2012-11-13 22:00:06
【问题描述】:

ABC.java

中定义了两种方法
public void method1(){
   .........
   method2();
  ...........
}


public void method2(){
  ...............
  ...............  
}

我想要调用 method2 的 AOP。所以, 我创建了一个类,AOPLogger.java,在方法 checkAccess
中提供了方面功能 在配置文件中,我做了如下的事情

<bean id="advice" class="p.AOPLogger" />
<aop:config>
  <aop:pointcut id="abc" expression="execution(*p.ABC.method2(..))" />
  <aop:aspect id="service" ref="advice">
    <aop:before pointcut-ref="abc" method="checkAccess" />          
  </aop:aspect>
</aop:config>

但是当我的 method2 被调用时,AOP 功能没有被调用,即 checkAccess 方法没有被调用 AOPLogger 类。

有什么我遗漏的吗?

【问题讨论】:

    标签: java spring spring-aop


    【解决方案1】:

    切面被应用到 bean 周围的 代理。请注意,每次获得对 bean 的引用时,它实际上并不是配置中引用的类,而是实现相关接口、委托给实际类并添加功能(例如 AOP)的合成类。

    在上面的示例中,您在类上直接调用 ,而如果该类实例作为 Spring bean 注入到另一个实例中,它会作为其代理注入,因此将调用方法调用在代理上(并且方面将被触发)

    如果您想实现上述目标,您可以将method1/method2 拆分为单独的 bean,或者使用非弹簧导向的 AOP 框架。

    Spring doc (section "Understanding AOP Proxies") 详细说明了这一点,以及一些解决方法(包括我上面的第一个建议)

    【讨论】:

    • 对不起,没有得到..你能否简单解释一下,因为我是新手或提供任何好的链接
    • 我认为 Spring 文档链接包含很多信息。但我在上面扩展了一点
    • Spring 服务中围绕方法定义的方面没有运行,并认为是由于配置错误。最后我发现这个问题和答案为我节省了几个小时.. 谢谢!
    • @BrianAgnew - 你知道其他“非 Spring-Oriented AOP”框架吗?
    • @HVT7 直接使用 AspectJ,无论是在构建时使用 AspectJ 编译器还是在运行时使用 AspectJ Load-Time Weaver。
    【解决方案2】:

    2022 年更新:

    现在我个人更喜欢使用here 描述的 TransactionHandler 类 - 更清洁和灵活的方式。

    原答案:

    可以通过自注入使用来完成。您可以通过注入的实例调用内部方法:

    @Component
    public class Foo {
        @Resource
        private Foo foo;
        
        public void method1(){
            ..
            foo.method2();
            ..
        }
        public void method2(){
            ..
        }
    }
    

    从 Spring 4.3 开始,您也可以使用 @Autowired 来实现。

    从 4.3 开始,@Autowired 还考虑注入的自引用, 即引用回当前注入的 bean。

    【讨论】:

    • 是的,但是可怜的 Foo#foo#foo 呢?
    • 这只是一种解决方法。我没有说它是完美的,但它确实有效
    • 我在 Spring 2.3.4 收到 APPLICATION FAILED TO START The dependencies of some of the beans in the application context form a cycle:
    【解决方案3】:

    Spring AOP 框架是基于“代理”的,Understanding AOP Proxies 的文档对此进行了很好的解释。

    当 Spring 构造一个配置了方面的 bean(如您的示例中的“ABC”)时,它实际上创建了一个“代理”对象,该对象的行为就像真正的 bean。代理只是将调用委托给“真实”对象,但通过创建这种间接,代理有机会实现“建议”。例如,您的建议可以为每个方法调用记录一条消息。在此方案中,如果真实对象(“method1”)中的方法调用同一对象(例如,method2)中的其他方法,则这些调用会在图片中没有代理的情况下发生,因此它没有机会实现任何建议。

    在您的示例中,当调用 method1() 时,代理将有机会执行它应该执行的操作,但如果 method1() 调用 method2(),则图片中没有任何方面。但是,如果从其他 bean 调用 method2,代理将能够执行通知。

    【讨论】:

      【解决方案4】:

      我遇到了同样的问题,我通过实现Spring的ApplicationContextAwareBeanNameAware并实现了如下相应的方法克服了。

      class ABC implements ApplicationContextAware,BeanNameAware{
      
            @Override
            public void setApplicationContext(ApplicationContext ac) throws BeansException {
                applicationContext=ac;
            }
      
            @Override
            public void setBeanName(String beanName) {
                this.beanName=beanName;
            }
            private ApplicationContext applicationContext;
            private String beanName;
      }
      

      然后我在调用同一类的方法时将this. 替换为((ABC) applicationContext.getBean(beanName)).。这确保了对同一类的方法的调用仅通过代理发生。

      所以method1() 更改为

       public void method1(){
          .........
          ((ABC) applicationContext.getBean(beanName)).method2();
          ...........
        }
      

      希望这会有所帮助。

      【讨论】:

      • 虽然此方法确实有效,但缺点是您的类现在与 Spring 框架紧密耦合。如果您使用的是纯 xml 配置而不是注释,那么您的类可以独立于 Spring。通常最佳实践是将逻辑重构为两个单独的 bean。但是,如果您不关心与 spring 的耦合,那么这个解决方案可能就足够了。
      【解决方案5】:

      使用@Autowired 它可以工作。 您可以这样做:

      @Autowired
      Foo foo;
      

      然后调用:

      foo.method2();
      

      【讨论】:

      • 但这应该会导致运行时错误,因为您最终会陷入循环依赖
      • 你会得到APPLICATION FAILED TO START The dependencies of some of the beans in the application context form a cycle:
      【解决方案6】:

      你想要达到的目标是不可能的。解释在Spring Reference Documentation

      【讨论】:

        【解决方案7】:

        Spring docs 中所述,第 5.6.1 章了解 AOP 代理,还有另一种方法可以做到:

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

        虽然作者不推荐这种方式。因为:

        这完全将您的代码与 Spring AOP 耦合在一起,并且它使类本身意识到它正在 AOP 上下文中使用,这与 AOP 相悖。在创建代理时还需要一些额外的配置。

        【讨论】:

          【解决方案8】:

          用@EnableAspectJAutoProxy(exposeProxy = true)注释调用,用((Class)AopContext.currentProxy()).method();调用实例方法;

          不建议这样做,因为它会增加耦合

          【讨论】:

            【解决方案9】:

            我很惊讶没有人提到这一点,但我认为我们可以使用 Spring 提供的 ControlFlowPointcut。

            ControlFlowPointcut 仅在堆栈跟踪中找到特定方法时才会查看堆栈跟踪并匹配切入点。本质上,只有在特定上下文中调用方法时才会匹配切入点。

            在这种情况下,我们可以创建一个类似的切入点

            ControlFlowPointcut cf = new ControlFlowPointcut(MyClass.class, "method1");
            

            现在,使用 ProxyFactory 在 MyClass 实例上创建一个代理并调用 method1()。

            在上述情况下,只会建议method2(),因为它是从method1()调用的。

            【讨论】:

              【解决方案10】:

              另一种解决方法是将method2() 模式化为其他类文件,并且该类使用@Component 进行注释。然后在需要时使用 @Autowired 注入。这样 AOP 可以拦截它。

              示例:

              You were doing this...
              
              
              Class demo{
                 method1(){
                  -------
                  -------
                  method2();
                  -------
                  -------
                 }
              
                 method2(){
                  ------
                  -----
                 }
              }
              
              Now if possible do this :
              
              @Component
              class NewClass{
                  method2(){
                  ------
                  -----
                 }
              }
              
              
              Class demo{
              
               @AutoWired
               NewClass newClass;
              
                 method1(){
                  -------
                  -------
                  newClass.method2();
                  -------
                  -------
                 }
              
              }
              

              【讨论】:

                【解决方案11】:

                你可以参考这个问题:https://stackoverflow.com/a/30611671/7278126

                这是一种解决方法,但可以解决问题,这里的关键是使用相同的 bean(代理)来调用 'method2' 而不是这个。

                【讨论】:

                  【解决方案12】:

                  经过大量研究, 我发现以下内容就像一个魅力。但是 ASPECTJ WEAVING 总是有更好的方法。在本质上使用自引用。

                  @Autowired
                  private ApplicationContext applicationContext;
                  private <<BEAN>> self;
                  

                  请注意 属于同一类,需要使用 AOP 记录或用于其他用途。

                  @PostConstruct
                  private void init() {
                      self = applicationContext.getBean(MyBean.class);
                  }
                  

                  有了这个改变,你所要做的就是移动调用

                  this.methodName(args) -> self.methodName(args)

                  希望这可以帮助所有想要在应用程序中使用性能日志记录解决方案的人。

                  【讨论】:

                    【解决方案13】:

                    你可以这样做:

                    @Autowired // to make this bean refers to proxy class 
                    
                    ABC self;
                    
                    public void method1(){
                    
                       .........
                    
                       self.method2();
                    
                      ...........
                    
                    }
                    
                    
                    public void method2(){
                    
                      ...............
                    
                      ...............  
                    
                    }
                    

                    【讨论】:

                      【解决方案14】:

                      你可以通过这种方式进行自注入,这样这个类就可以在 Spring 应用程序之外使用。

                      @Component
                      public class ABC {
                      
                          @Resource
                          private ABC self = this;
                      
                          public void method1() {
                              self.method2();
                          }
                      
                          public void method2() {
                      
                          }
                      
                      }
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-07-03
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多