处理在AspectJ中从proceed()赋值的空方法

huangapple go评论85阅读模式
英文:

Handling void methods in assignment from proceed() in AOP using AspectJ

问题

我正在编写一个非常通用的代码,使用around来捕获返回类型,如下所示:result = proceed();,然后是return result;

有些方法的类型是void。例如:

void doPrint() { 
   System.out.println("Doing something"); 
}

这些返回类型为void的方法如何以通用的方式处理,以及如何处理返回值或抛出异常的方法?

我目前的代码如下:

import java.util.Arrays;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.aspectj.lang.SoftException;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
    private pointcut executionJoinPoints(): !within(LogInjector) && execution (* *.*(..));

    Object around(): executionJoinPoints(){
    	SourceLocation loc;
    	CodeSignature sig;
    	
    	Class<?> type;
    	
        Logger logger;
        
        Object result;
        try {
        	loc = thisJoinPointStaticPart.getSourceLocation();
        	sig = (CodeSignature) thisJoinPointStaticPart.getSignature();
        	
        	type = loc.getWithinType();
    	    if (type == null) type = sig.getDeclaringType();
        	
        	logger = LogManager.getLogger(type);
        	
        	result = proceed();
            return result;
        } catch (RuntimeException rte) {
            result = rte;
            throw rte;
        } catch (Throwable t) {
            result = t;
            throw new SoftException(t);
        } finally {
        	logger.trace("Source location: {} | Join Point: {} | Signature: {} | Args: {} | Result: {}", loc, thisJoinPointStaticPart, sig, Arrays.deepToString(thisJoinPoint.getArgs()), result);
        }
    }

}

此答案进行了修正,由此用户提供。

英文:

I am writing a very generic code to capture the return type using around as result = proceed(); followed by return result;.

Some methods are of type void. E.g.

void doPrint() { 
   System.out.println(&quot;Doing something&quot;); 
}

How can these methods of return type void be handled in a generic way along with methods returning a value or throwing an exception?

The code I have so far is:

import java.util.Arrays;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.aspectj.lang.SoftException;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
    private pointcut executionJoinPoints(): !within(LogInjector) &amp;&amp; execution (* *.*(..));

    Object around(): executionJoinPoints(){
    	SourceLocation loc;
    	CodeSignature sig;
    	
    	Class&lt;?&gt; type;
    	
        Logger logger;
        
        Object result;
        try {
        	loc = thisJoinPointStaticPart.getSourceLocation();
        	sig = (CodeSignature) thisJoinPointStaticPart.getSignature();
        	
        	type = loc.getWithinType();
    	    if (type == null) type = sig.getDeclaringType();
        	
        	logger = LogManager.getLogger(type);
        	
        	result = proceed();
            return result;
        } catch (RuntimeException rte) {
            result = rte;
            throw rte;
        } catch (Throwable t) {
            result = t;
            throw new SoftException(t);
        } finally {
        	logger.trace(&quot;Source location: {} | Join Point: {} | Signature: {} | Args: {} | Result: {}&quot;, loc, thisJoinPointStaticPart, sig, Arrays.deepToString(thisJoinPoint.getArgs()), result);
        }
    }

}

Corrections adapted from this answer by this user.

答案1

得分: 1

Sure, here's the translated version of the provided content:

在您之前提到的问题的第一个版本中,我评论说您在result = proceed();之后忘记了return result;,后来您已经纠正了这个问题。这是一个很好的第一步。但是您的代码仍然无法编译,因为:

  • around()通知没有声明返回类型。
  • 你不能从around()通知中抛出Throwable,只能抛出RuntimeException。因此,您需要将每个已检查的ExceptionErrorThrowable包装在类似AspectJ的SoftException中,或者简单地包装在RuntimeException中。

因此,请在发布不可编译的代码时,请提到这个事实,并且还要发布您收到的编译器错误。不要假装代码可以工作,只是关于void方法处理的细节问题。

进一步的问题包括:

  • 您打印了冗余的签名,因为您已经打印了已经包含相同签名的连接点。
  • 您确定了声明类型,但从未打印过。
  • 您导入了org.aspectj.ajdt.internal.compiler.ast.Proceed,使得切面依赖于一个非AspectJ类。更准确地说,这个类来自AJDT(AspectJ Development Tools),这是一个用于AspectJ的Eclipse插件。

我还不太确定将结果用捕获的异常覆盖并将其打印为这种形式是否是一个好主意,但在我的下面的MCVE(最小完整可复现示例,来源:https://stackoverflow.com/help/mcve)中,我没有对其进行更改。

驱动程序应用:

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    Application application = new Application();
    application.doSomething("foo");
    try {
      application.doSomethingSpecial("bar");
    } catch (Exception e) {
      e.printStackTrace();
    }
    application.doSomethingElse("zot");
  }

  public int doSomething(String string) {
    System.out.println("Doing something: " + string);
    return 42;
  }

  public void doSomethingSpecial(String string) throws Exception {
    throw new Exception("checked exception");
  }

  public void doSomethingElse(String string) {
    throw new IllegalArgumentException("runtime exception");
  }
}

切面:

请注意,我去除了Log4J,只是在控制台上打印,以便将代码简化为重要部分。您可以轻松地再次添加该库。我不想手动将它添加到我的示例程序中,然后尝试是否为Log4J 1.x或2.x。

package de.scrum_master.aspect;

import static java.util.Arrays.deepToString;

import org.aspectj.lang.SoftException;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
  private pointcut executionJoinPoints() :
    !within(LogInjector) && execution (* *(..));

  Object around() : executionJoinPoints() {
    SourceLocation sourceLocation = thisJoinPointStaticPart.getSourceLocation();
    CodeSignature signature = (CodeSignature) thisJoinPointStaticPart.getSignature();
    Class<?> type = sourceLocation.getWithinType();
    if (type == null)
      type = signature.getDeclaringType();
    Object result = null;
    try {
      result = proceed();
      return result;
    } catch (RuntimeException rte) {
      result = rte;
      throw rte;
    } catch (Throwable t) {
      result = t;
      throw new SoftException(t);
    } finally {
      System.out.printf(
        "Source location: %s | Type: %s | Join Point: %s | Args: %s | Result: %s%n",
        sourceLocation, type, thisJoinPoint, deepToString(thisJoinPoint.getArgs()), result
      );
    }
  }
}

正如您所看到的,该切面只是重新抛出运行时异常(无需将其包装为另一个运行时异常),并包装其他可抛出项。这也是必要的,否则通过调用层次结构抛出的异常将像俄罗斯套娃一样被多次包装,这是我们要避免的。

控制台日志:

Doing something: foo
Source location: Application.java:15 | Type: class de.scrum_master.app.Application | Join Point: execution(int de.scrum_master.app.Application.doSomething(String)) | Args: [foo] | Result: 42
Source location: Application.java:20 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingSpecial(String)) | Args: [bar] | Result: java.lang.Exception: checked exception
org.aspectj.lang.SoftException
	at de.scrum_master.app.Application.doSomethingSpecial_aroundBody5$advice(Application.java:28)
	at de.scrum_master.app.Application.doSomethingSpecial(Application.java:1)
	at de.scrum_master.app.Application.main_aroundBody0(Application.java:8)
	at de.scrum_master.app.Application.main_aroundBody1$advice(Application.java:21)
	at de.scrum_master.app.Application.main(Application.java:1)
Caused by: java.lang.Exception: checked exception
	at de.scrum_master.app.Application.doSomethingSpecial_aroundBody4(Application.java:21)
	at de.scrum_master.app.Application.doSomethingSpecial_aroundBody5$advice(Application.java:21)
	... 4 more
Source location: Application.java:24 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingElse(String)) | Args: [zot] | Result: java.lang.IllegalArgumentException: runtime exception
Source location: Application.java:4 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.main(String[])) | Args: [[--command, --option=123]] | Result: java.lang.IllegalArgumentException: runtime exception
Exception in thread "main" java.lang.IllegalArgumentException: runtime exception
	at de.scrum_master.app.Application.doSomethingElse_aroundBody6(Application.java:25)
	at de.scrum_master.app.Application.doSomethingElse_aroundBody7$advice(Application.java:21)
	at de.scrum_master.app.Application.doSomethingElse(Application.java:1)
	at de.scrum_master.app.Application.main_aroundBody0(Application.java:12

<details>
<summary>英文:</summary>

In the first version of this question I commented on you had forgotten `return result;` after `result = proceed();` which later you have corrected. That was a good first step. But still your code would not compile because:

  * The `around()` advice does not declare a return type.
  * You cannot throw a `Throwable` from an `around()` advice, only a `RuntimeException`. Hence, you need to wrap each checked `Exception`, `Error` or `Throwable` in something like AspectJ&#39;s `SoftException` or simply in a `RuntimeException`.

So please when posting non-compilable code, please mention that fact and also post the compiler errors you get. Do not pretend the code works and there is just a detail problem about `void` method handling.

Further problems are:
  * You print the signature which is redundant because you also print the joinpoint already containing the same signature.
  * You determine the declaring type but never print it.
  * You import `org.aspectj.ajdt.internal.compiler.ast.Proceed`, making the aspect dependent on a non-AspectJ class. More precisely, the class if from AJDT (AspectJ Development Tools), an Eclipse plugin for AspectJ.

I am also not so sure it is a good idea to overwrite the result by a caught exception and print it as such, but in my following [MCVE](https://stackoverflow.com/help/mcve) (which was your job to provide and again you didn&#39;t do it just like for the previous question) I left it unchanged.

**Driver application:**

```java
package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    Application application = new Application();
    application.doSomething(&quot;foo&quot;);
    try {
      application.doSomethingSpecial(&quot;bar&quot;);
    } catch (Exception e) {
      e.printStackTrace();
    }
    application.doSomethingElse(&quot;zot&quot;);
  }

  public int doSomething(String string) {
    System.out.println(&quot;Doing something: &quot; + string);
    return 42;
  }

  public void doSomethingSpecial(String string) throws Exception {
    throw new Exception(&quot;checked exception&quot;);
  }

  public void doSomethingElse(String string) {
    throw new IllegalArgumentException(&quot;runtime exception&quot;);
  }
}

Aspect:

Please note that I removed Log4J and just print to the console in order to boil down the code to the important part. You can easily add the library again. I did not want to manually add it to my sample program and then try if it was Log4J 1.x or 2.x.

package de.scrum_master.aspect;

import static java.util.Arrays.deepToString;

import org.aspectj.lang.SoftException;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
  private pointcut executionJoinPoints() :
    !within(LogInjector) &amp;&amp; execution (* *(..));

  Object around() : executionJoinPoints() {
    SourceLocation sourceLocation = thisJoinPointStaticPart.getSourceLocation();
    CodeSignature signature = (CodeSignature) thisJoinPointStaticPart.getSignature();
    Class&lt;?&gt; type = sourceLocation.getWithinType();
    if (type == null)
      type = signature.getDeclaringType();
    Object result = null;
    try {
      result = proceed();
      return result;
    } catch (RuntimeException rte) {
      result = rte;
      throw rte;
    } catch (Throwable t) {
      result = t;
      throw new SoftException(t);
    } finally {
      System.out.printf(
        &quot;Source location: %s | Type: %s | Join Point: %s | Args: %s | Result: %s%n&quot;,
        sourceLocation, type, thisJoinPoint, deepToString(thisJoinPoint.getArgs()), result
      );
    }
  }

}

As you can see, the aspect simply re-throws runtime exceptions (no need to wrap them into another runtime exception) and wraps other throwables. This is also necessary because otherwise exceptions thrown through a hierarchy of calls would be wrapped multiple times like Russian Matryoshka dolls, which is something we want to avoid.

Console log:

Doing something: foo
Source location: Application.java:15 | Type: class de.scrum_master.app.Application | Join Point: execution(int de.scrum_master.app.Application.doSomething(String)) | Args: [foo] | Result: 42
Source location: Application.java:20 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingSpecial(String)) | Args: [bar] | Result: java.lang.Exception: checked exception
org.aspectj.lang.SoftException
	at de.scrum_master.app.Application.doSomethingSpecial_aroundBody5$advice(Application.java:28)
	at de.scrum_master.app.Application.doSomethingSpecial(Application.java:1)
	at de.scrum_master.app.Application.main_aroundBody0(Application.java:8)
	at de.scrum_master.app.Application.main_aroundBody1$advice(Application.java:21)
	at de.scrum_master.app.Application.main(Application.java:1)
Caused by: java.lang.Exception: checked exception
	at de.scrum_master.app.Application.doSomethingSpecial_aroundBody4(Application.java:21)
	at de.scrum_master.app.Application.doSomethingSpecial_aroundBody5$advice(Application.java:21)
	... 4 more
Source location: Application.java:24 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingElse(String)) | Args: [zot] | Result: java.lang.IllegalArgumentException: runtime exception
Source location: Application.java:4 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.main(String[])) | Args: [[--command, --option=123]] | Result: java.lang.IllegalArgumentException: runtime exception
Exception in thread &quot;main&quot; java.lang.IllegalArgumentException: runtime exception
	at de.scrum_master.app.Application.doSomethingElse_aroundBody6(Application.java:25)
	at de.scrum_master.app.Application.doSomethingElse_aroundBody7$advice(Application.java:21)
	at de.scrum_master.app.Application.doSomethingElse(Application.java:1)
	at de.scrum_master.app.Application.main_aroundBody0(Application.java:12)
	at de.scrum_master.app.Application.main_aroundBody1$advice(Application.java:21)
	at de.scrum_master.app.Application.main(Application.java:1)

The log shows that both void and non-void methods are handles nicely and that even the contents of the main() arguments array - I started the sample application with command line parameters --command --option=123 - are nicely printed because I am using Arrays.deepToString(thisJoinPoint.getArgs()) instead of Arrays.toString(thisJoinPoint.getArgs()).


So, like I said: I don't understand why you think you have problems with void methods while in reality you have a whole bunch of other problems. Did you ever read an AspectJ manual or tutorial and using sample code to start with or are you just using the "trial & error" method?

P.S.: I would never use the source location in an aspect because an aspect is not a debugger and source code is prone to refactoring anyway. If the code is compiled without debug info, you do not have the source location information in the byte code anyway, so you cannot rely on it. An aspect or logging in general are not replacements for a debugger.


Update: In comparison to your original question now you don't log both before and after the target method call but only afterwards. In this case a combination of after() returning and after() throwing advice would both be more efficient and simpler to implement because you could avoid exception handling, re-throwing, and the finally block. When in the other question I recommended an around() advice I did so because I saw that your original before() and after() advice each had to determine the same information in order to log it, i.e. twice per method. But if you only need it once, around() is actually unnecessary.


Update 2: You asked:

> I am running into another problem. By throwing SoftException and RuntimeException the catch blocks are not catching the exceptions supposed to be thrown and caught as per the method signatures in libraries as per normal behaviour.

If you want the exceptions unchanged, use a combination of after() returning (printing the result) and after() throwing (unchanged exceptions, no need to wrap them) as mentioned above. See the AspectJ manual for more information.

package de.scrum_master.aspect;

import static java.util.Arrays.deepToString;

import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.SourceLocation;

public aspect LogInjector {
  private pointcut executionJoinPoints() :
    !within(LogInjector) &amp;&amp; execution (* *(..));

  after() returning (Object result): executionJoinPoints() {
    SourceLocation sourceLocation = thisJoinPointStaticPart.getSourceLocation();
    CodeSignature signature = (CodeSignature) thisJoinPointStaticPart.getSignature();
    Class&lt;?&gt; type = sourceLocation.getWithinType();
    if (type == null)
      type = signature.getDeclaringType();
    System.out.printf(&quot;Source location: %s | Type: %s | Join Point: %s | Args: %s | Result: %s%n&quot;,
      sourceLocation, type, thisJoinPoint, deepToString(thisJoinPoint.getArgs()), result
    );
  }

  after() throwing (Throwable error): executionJoinPoints() {
    SourceLocation sourceLocation = thisJoinPointStaticPart.getSourceLocation();
    CodeSignature signature = (CodeSignature) thisJoinPointStaticPart.getSignature();
    Class&lt;?&gt; type = sourceLocation.getWithinType();
    if (type == null)
      type = signature.getDeclaringType();
    System.out.printf(&quot;Source location: %s | Type: %s | Join Point: %s | Args: %s | Error: %s%n&quot;,
      sourceLocation, type, thisJoinPoint, deepToString(thisJoinPoint.getArgs()), error
    );
  }

}

The console log will change to:

Doing something: foo
Source location: Application.java:15 | Type: class de.scrum_master.app.Application | Join Point: execution(int de.scrum_master.app.Application.doSomething(String)) | Args: [foo] | Result: 42
Source location: Application.java:20 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingSpecial(String)) | Args: [bar] | Error: java.lang.Exception: checked exception
java.lang.Exception: checked exception
at de.scrum_master.app.Application.doSomethingSpecial(Application.java:21)
at de.scrum_master.app.Application.main(Application.java:8)
Source location: Application.java:24 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.doSomethingElse(String)) | Args: [zot] | Error: java.lang.IllegalArgumentException: runtime exception
Source location: Application.java:4 | Type: class de.scrum_master.app.Application | Join Point: execution(void de.scrum_master.app.Application.main(String[])) | Args: [[--command, --option=123]] | Error: java.lang.IllegalArgumentException: runtime exception
Exception in thread &quot;main&quot; java.lang.IllegalArgumentException: runtime exception
at de.scrum_master.app.Application.doSomethingElse(Application.java:25)
at de.scrum_master.app.Application.main(Application.java:12)

huangapple
  • 本文由 发表于 2020年9月25日 19:26:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/64063223.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定