首先,你的切面类应该有一个@Aspect 注释。其次,如果你想使用 Spring AOP 而不是完整的 AspectJ,你的方面和所有目标类也应该是 Spring @Components。
话虽如此,这里有一个小样本。我是用普通的 AspectJ 创建的,但是 Spring AOP 中的切面代码应该是一样的。
帮助代码编译和运行的类:
package de.scrum_master.app;
import java.util.Random;
public class Host {
private static final Random RANDOM = new Random();
private String name;
public Host(String name) {
this.name = name;
}
public void doSomething() {
if (RANDOM.nextBoolean())
throw new RuntimeException("oops!");
}
@Override
public String toString() {
return "Host(name=" + name + ")";
}
}
package de.scrum_master.app;
import java.util.HashMap;
import java.util.Map;
public class ConfigReader {
private Map<Integer, Host> hostMap = new HashMap<>();
public ConfigReader() {
hostMap.put(1, new Host("mercury"));
hostMap.put(2, new Host("venus"));
hostMap.put(3, new Host("earth"));
hostMap.put(4, new Host("mars"));
}
public Map<Integer, Host> getHostMap() {
return hostMap;
}
}
驱动程序应用:
我不喜欢旧 JDK 版本遗留下来的 Iterator,因此我将其替换为更现代的 Java 风格 for 循环。
package de.scrum_master.app;
class MyClass {
private ConfigReader configReader = new ConfigReader();
void check() throws Exception {
for (Host host : configReader.getHostMap().values()) {
System.out.println(host);
host.doSomething();
}
}
public static void main(String[] args) throws Exception {
new MyClass().check();
}
}
切入点/建议方面同时进行日志记录和异常处理:
还请注意我在代码末尾的评论。
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AopLogger {
private static final InheritableThreadLocal<String> indent = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return "";
}
};
@Around("execution(* de.scrum_master.app..*(..)) && !execution(* toString())")
public Object logAround(ProceedingJoinPoint thisJoinPoint) throws Throwable {
Object result = null;
System.out.println(indent.get() + ">> " + thisJoinPoint);
try {
indent.set(indent.get() + " ");
result = thisJoinPoint.proceed();
indent.set(indent.get().substring(2));
} catch (Exception e) {
System.out.println(indent.get() + "Caught exception: " + e);
indent.set(indent.get().substring(2));
}
System.out.println(indent.get() + "<< " + thisJoinPoint);
// Attention: If a method with a caught exception does not have 'void'
// return type, we return a (probably unexpected) result of 'null' here.
// So maybe we should not catch all execptions but rather pick more
// specific joinpoints where we are sure we can cleanly handle the
// corresponding exceptions.
return result;
}
}
控制台日志:
>> execution(void de.scrum_master.app.MyClass.main(String[]))
>> execution(void de.scrum_master.app.MyClass.check())
>> execution(Map de.scrum_master.app.ConfigReader.getHostMap())
<< execution(Map de.scrum_master.app.ConfigReader.getHostMap())
Host(name=mercury)
>> execution(void de.scrum_master.app.Host.doSomething())
Caught exception: java.lang.RuntimeException: oops!
<< execution(void de.scrum_master.app.Host.doSomething())
Host(name=venus)
>> execution(void de.scrum_master.app.Host.doSomething())
<< execution(void de.scrum_master.app.Host.doSomething())
Host(name=earth)
>> execution(void de.scrum_master.app.Host.doSomething())
Caught exception: java.lang.RuntimeException: oops!
<< execution(void de.scrum_master.app.Host.doSomething())
Host(name=mars)
>> execution(void de.scrum_master.app.Host.doSomething())
Caught exception: java.lang.RuntimeException: oops!
<< execution(void de.scrum_master.app.Host.doSomething())
<< execution(void de.scrum_master.app.MyClass.check())
<< execution(void de.scrum_master.app.MyClass.main(String[]))
将日志记录与异常处理分开的第二个方面变体:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AopLogger {
private static final InheritableThreadLocal<String> indent = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return "";
}
};
@Around("execution(* de.scrum_master.app..*(..)) && !execution(* toString())")
public Object logAround(ProceedingJoinPoint thisJoinPoint) throws Throwable {
System.out.println(indent.get() + ">> " + thisJoinPoint);
try {
indent.set(indent.get() + " ");
Object result = thisJoinPoint.proceed();
indent.set(indent.get().substring(2));
System.out.println(indent.get() + "<< " + thisJoinPoint);
return result;
} catch (Exception e) {
indent.set(indent.get().substring(2));
System.out.println(indent.get() + "<< " + thisJoinPoint);
throw e;
}
}
@Around("execution(void de.scrum_master.app.Host.doSomething())")
public void handleException(ProceedingJoinPoint thisJoinPoint) throws Throwable {
try {
thisJoinPoint.proceed();
} catch (Exception e) {
System.out.println(indent.get() + "Caught exception: " + e);
}
}
}
日志输出保持不变,但这次异常处理在一个单独的建议中,具有更精确的切入点。日志记录建议只处理日志记录(如果不是正确的缩进,它甚至不需要 try-catch)。异常处理建议只做自己的工作。
请随时提出后续问题。