【问题标题】:how to do logging of method chaining using spring aop如何使用spring aop记录方法链接
【发布时间】:2013-09-28 15:50:34
【问题描述】:

我将 slf4j 与 Spring AOP 一起用于日志记录和异常目的。某些类中的一些方法形成了方法链。我可以在第一个方法的入口和出口点记录,但是当这个方法调用另一个方法时,AOP 只记录第一个方法的入口和出口点。我想使用@Around 注释记录每个方法的入口和出口点这里是伪解释我想要什么的代码

package com.sample;
public class Test implements T{

@Override
 public void show() {
   System.out.println("Test.show()");
   test();
  }
  void Test(){
   //Want to log entry and exit point of this method whenever this method called by any other method
   //The method may belongs to same class or different package's different class
  }

spring.xml 是这样的

<bean id="exceptionAspect" class="com.sample.ExceptionAspect"/>
<bean id="test" class="com.sample.Test"/>

我的Advise class 看起来像

@Aspect
public class LoggingAspect {
@Around(value="execution (* com.sample.*.*(..))||"+
              "execution(* some other package.*.*(..))")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
      final Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
      logger.info("Execution of : " + joinPoint.getSignature() + " Started");
      joinPoint.proceed();
      logger.info("Execution of : " + joinPoint.getSignature() + " completed");
  }

}

客户端类

package com.test;
public class App {
   public static void main(String[] args) throws Exception {

    ApplicationContext appContext = new ClassPathXmlApplicationContext(
                "classpath:/META-INF/spring.xml");

        T test=(T) appContext.getBean("test");
        test.show();
    }

非常感谢任何帮助..

【问题讨论】:

    标签: java spring spring-aop


    【解决方案1】:

    Spring AOP 无法实现您想要做的事情(至少在没有对建议的方法进行一些重构的情况下是不可能的)。原因是 Spring AOP 是proxy based,这意味着它为每个 bean 创建一个代理类并注入它而不是你的实现。代理具有 bean 的所有方法,并添加了方面功能。所以当你调用一个bean的方法(实际上是bean的代理)时,切面代码被执行,然后你的方法被委托调用。因此,当您的方法调用其他方法时,调用是使用真正的 bean 执行的,而不是那些代理 - 如果存在的话 - 因此您不会得到您期望的输出。

    你可以想象一个看起来像这样的代理:

    class MyBeanProxy implements MyBean {
    
        MyBeanImpl theBean;
    
        public void foo() {
            // aspect code
            theBean.foo();
        }
        public void bar() {
            // aspect code
            theBean.bar();
        }
    }
    

    你的豆子在哪里

    interface MyBean {
        foo();
        bar();
    }
    
    @Component("my_bean")
    class MyBeanImpl implements MyBean {
        public void foo() {
            System.out.println("foo");
            bar();
        }
        public void bar() {
            System.out.println("bar");
        }
    }
    

    在上面的示例中,当您通过代理调用 foo() 时,将执行方面代码,并委托到 MyBeanImpl#foo(),其中正在调用 bar()。现在很明显bar() 的切面代码不会被执行。

    现在如何让它发挥作用?

    1 - 重构您的代码,以便对于您希望为其执行方面代码的方法,调用发生在代理对象上而不是 bean 本身上。为此,您可以获得实际的代理并使用它来调用您的方法。

    public void foo() {
        System.out.println("foo");
        MyBean myBeanProxy = (MyBean) AopContext.currentProxy();
        myBeanProxy.bar();
    }
    

    注意,这种方法更像是一种 hack,而不是一种干净的方式来完成这项工作。例如,很明显myBeanProxy 不知道您当前对象的状态。

    2 - 重构您的代码,使 bar() 位于另一个 bean 中,您可以使用 appContext 检索该 bean。

    3- 使用AspectJ:切面代码被注入到目标类本身(真实的东西!)

    这是使用 AspectJ 的小例子

    方面

    package com.aj;
    
    import java.util.Arrays;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    
    @Aspect
    public class MyAspect {
    
        @Around("execution( * com.app.services.*.* (..) )")
        public Object callDurationAdvice(ProceedingJoinPoint pjp) throws Throwable {
            Signature signature = pjp.getSignature();
            Object[] args = pjp.getArgs();
            String argList = Arrays.toString(args);
            System.out.println(signature.getDeclaringTypeName() +
                    "." + signature.getName() + "(" + argList + ") started");
            long s = System.nanoTime();
            Object proceed = pjp.proceed(args);
            long e = System.nanoTime();
            System.out.println(signature.getDeclaringTypeName() +
                    "." + signature.getName() + "(" + argList + ") ended after " +
                    ((double)(e-s)/1000000) + " ms");
            return proceed;
        }
    }
    

    某个包中的一个类应该是方面的目标

    package com.app.services;
    
    public class ServicesVersionInfo {
    
        public static String getVersion() {
            return getVersionNumber() + " " + getVersionStage();
        }
    
        public static String getVersionNumber() {
            return "1.0.0";
        }
    
        public static String getVersionStage() {
            return "ALPHA";
        }
    }
    

    应用程序

    package com.app;
    
    import com.app.services.ServicesVersionInfo;
    
    
    public class App {
    
        public static void main(String[] args) {
            System.out.println("App services version: " + 
                ServicesVersionInfo.getVersion());
        }
    }
    

    运行,这应该输出一些谎言

    com.app.services.ServicesVersionInfo.getVersion([]) started
    com.app.services.ServicesVersionInfo.getVersionNumber([]) started
    com.app.services.ServicesVersionInfo.getVersionNumber([]) ended after 0.004862 ms
    com.app.services.ServicesVersionInfo.getVersionStage([]) started
    com.app.services.ServicesVersionInfo.getVersionStage([]) ended after 0.005673 ms
    com.app.services.ServicesVersionInfo.getVersion([]) ended after 0.378877 ms
    App services version: 1.0.0 ALPHA
    

    最后这里有一些类似的问题和延伸阅读:

    Spring AOP not working for method call inside another method

    Get AOP proxy from the object itself

    Spring AOP top problem #1 - aspects are not applied

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 2011-08-12
      • 2015-07-19
      • 2019-11-24
      • 1970-01-01
      相关资源
      最近更新 更多