Cur*_*ind 3 logging aop spring aspectj spring-aop
我是 AOP 的新手,我发现它有助于分离crosscutting关注点,并且似乎是一个很好的功能,为 OOP 编程增添了魅力。
一如既往,我发现的经典示例是“日志记录”,其中使用 AOP,可以使用 AOP 完成日志记录。
public void doSomething(String name, Object someObj) {
logger.info("Just entered doSomething() method));
int result;
// Some complex code
if(result != 0) {
logger.severe("There is some issues, fix it...");
// some other code
}
logger.trace("Did some more work, value is " +value);
// Other business code..
logger.info("Returning from method...");
}
Run Code Online (Sandbox Code Playgroud)
现在,参考在线文档/教程,“日志记录建议”的用例是我们可以从代码中删除日志记录代码,“日志记录建议”将执行日志记录,例如在方法的输入、返回期间,使用注释。
在上面的例子中,我同意使用@Before、@After“建议”可以帮助进入、返回方法调用。
我很困惑 AOP 如何帮助方法内部的记录器,并且理想情况下可以在我们在某个时间点捕获信息的任何地方使用。
我提到了这个SO问题,但没有清楚AOP如何帮助这种情况。
谁能帮助我理解这一点?
简短的答案是:AOP 并不是要研究您的方法,因为方法一直在重构,并且应该被视为黑匣子。
因此 Spring AOP 和 AspectJ 都不会为您做您所期望的事情。AOP的思想是实现横切关注点。日志记录只是这样的问题之一。如果您认为需要方法内日志记录,您仍然可以手动执行。但是,如果干净的代码对您来说很重要,那么您可以重构代码以使其更易于维护(并且也更易于记录)。方法应该简短,不要有太多的输入参数,也不要太复杂。
因此,您可以将复杂的意大利面条式代码方法分解为一组较小的方法,甚至提取新类并从您的方法中使用它们。我为你的代码做了这个(见下文)。此外,返回 0 或 -1 或其他而不是抛出异常不是 OOP,而是 C 风格的编程。因此,不要根据返回值记录问题,而是根据抛出的异常记录它们,并根据应用程序逻辑处理这些异常(或者让它们升级,如果存在致命错误)。我的示例代码也表明了这一点。
该示例可以很好地与 AspectJ 配合使用,因为 AspectJ 不基于委派动态代理,因此不存在自调用问题,例如类内的内部方法调用。
package de.scrum_master.app;
public class UnexpectedResultException extends Exception {
private static final long serialVersionUID = 1L;
public UnexpectedResultException(String message) {
super(message);
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我从您的复杂方法中提取了一些方法。为了向您展示更多日志输出,我什至doSomething(..)通过在 for 循环中多次调用执行复杂操作的方法来再次增加复杂性。
package de.scrum_master.app;
import java.util.Random;
public class Application {
public void doSomething(String name, Object someObj) {
int result = new Random().nextInt(100);
for (int counter = 0; counter < 5; counter++) {
try {
result = doComplexThing(result + 1);
} catch (UnexpectedResultException unexpectedResultException) {
result = 4;
}
}
result = doSomeMoreWork(result);
otherBusinessCode(result);
}
public int doComplexThing(int input) throws UnexpectedResultException {
if (input % 2 == 0)
throw new UnexpectedResultException("uh-oh");
return input % 5;
}
public int doSomeMoreWork(int input) {
return input * input;
}
public void otherBusinessCode(int input) {}
public static void main(String[] args) {
Application application = new Application();
application.doSomething("John Doe", new Integer(11));
}
}
Run Code Online (Sandbox Code Playgroud)
日志记录方面可能如下所示:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class LoggingAspect {
@Pointcut("within (de.scrum_master.app..*) && execution(* *(..))")
private void loggingTargets() {}
@Before("loggingTargets()")
public void logEnterMethod(JoinPoint thisJoinPoint) {
System.out.println("ENTER " + thisJoinPoint);
}
@AfterReturning(pointcut = "loggingTargets()", returning = "result")
public void logExitMethod(JoinPoint thisJoinPoint, Object result) {
System.out.println("EXIT " + thisJoinPoint + " -> return value = " + result);
}
@AfterThrowing(pointcut = "loggingTargets()", throwing = "exception")
public void logException(JoinPoint thisJoinPoint, Exception exception) {
System.out.println("ERROR " + thisJoinPoint + " -> exception = " + exception);
}
}
Run Code Online (Sandbox Code Playgroud)
控制台日志如下所示:
ENTER execution(void de.scrum_master.app.Application.main(String[]))
ENTER execution(void de.scrum_master.app.Application.doSomething(String, Object))
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
ERROR execution(int de.scrum_master.app.Application.doComplexThing(int)) -> exception = de.scrum_master.app.UnexpectedResultException: uh-oh
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
EXIT execution(int de.scrum_master.app.Application.doComplexThing(int)) -> return value = 0
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
EXIT execution(int de.scrum_master.app.Application.doComplexThing(int)) -> return value = 1
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
ERROR execution(int de.scrum_master.app.Application.doComplexThing(int)) -> exception = de.scrum_master.app.UnexpectedResultException: uh-oh
ENTER execution(int de.scrum_master.app.Application.doComplexThing(int))
EXIT execution(int de.scrum_master.app.Application.doComplexThing(int)) -> return value = 0
ENTER execution(int de.scrum_master.app.Application.doSomeMoreWork(int))
EXIT execution(int de.scrum_master.app.Application.doSomeMoreWork(int)) -> return value = 0
ENTER execution(void de.scrum_master.app.Application.otherBusinessCode(int))
EXIT execution(void de.scrum_master.app.Application.otherBusinessCode(int)) -> return value = null
EXIT execution(void de.scrum_master.app.Application.doSomething(String, Object)) -> return value = null
EXIT execution(void de.scrum_master.app.Application.main(String[])) -> return value = null
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,您根据新的、更加模块化的方法结构获得了最初问题中想要的所有日志记录。旧方法变得更具可读性,新方法更简单,因为它们专注于执行您提取它们的目的。
请注意:此示例代码是使用 AspectJ 运行的,而不是使用“AOP lite”框架 Spring AOP 运行的。所以在 Spring AOP 中它不会这样工作,因为:
main它只能拦截公共、非静态接口方法(当使用 Java 动态代理时)或附加受保护和包范围的方法(当使用 CGLIB 代理时)。因此,如果您考虑将代码重构为我建议的内容,并考虑将一些辅助方法设为私有,但仍希望将它们记录下来,那么除了配置 Spring通过 LTW(加载时编织)使用完整的 AspectJ 之外,没有其他方法了使用 AOP 的全部功能。
如果您宁愿坚持使用 Spring AOP 及其代理,但仍然需要那些通过 AOP 记录的内部调用方法,则需要在重构中更进一步,并将这三个新方法提取到您连接的额外 Spring 组件/bean 中到您的应用程序中。那么方法调用将不再是内部的,而是跨越组件/bean 边界,从而被 Spring AOP 日志记录方面拦截。
工作Application方法类将被提取并调用,如下所示:
package de.scrum_master.app;
// Make this into a @Component
public class MyWorker {
public int doComplexThing(int input) throws UnexpectedResultException {
if (input % 2 == 0)
throw new UnexpectedResultException("uh-oh");
return input % 5;
}
public int doSomeMoreWork(int input) {
return input * input;
}
public void otherBusinessCode(int input) {}
}
Run Code Online (Sandbox Code Playgroud)
package de.scrum_master.app;
import java.util.Random;
public class Application {
// In a Spring context this would be injected via configuration
private MyWorker worker = new MyWorker();
public void doSomething(String name, Object someObj) {
int result = new Random().nextInt(100);
for (int counter = 0; counter < 5; counter++) {
try {
result = worker.doComplexThing(result + 1);
} catch (UnexpectedResultException unexpectedResultException) {
result = 4;
}
}
result = worker.doSomeMoreWork(result);
worker.otherBusinessCode(result);
}
public static void main(String[] args) {
Application application = new Application();
application.doSomething("John Doe", new Integer(11));
}
}
Run Code Online (Sandbox Code Playgroud)
该方面可以保持不变。
日志输出更改为如下内容:
ENTER execution(void de.scrum_master.app.Application.main(String[]))
ENTER execution(void de.scrum_master.app.Application.doSomething(String, Object))
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 2
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 3
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
ERROR execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> exception = de.scrum_master.app.UnexpectedResultException: uh-oh
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 0
ENTER execution(int de.scrum_master.app.MyWorker.doComplexThing(int))
EXIT execution(int de.scrum_master.app.MyWorker.doComplexThing(int)) -> return value = 1
ENTER execution(int de.scrum_master.app.MyWorker.doSomeMoreWork(int))
EXIT execution(int de.scrum_master.app.MyWorker.doSomeMoreWork(int)) -> return value = 1
ENTER execution(void de.scrum_master.app.MyWorker.otherBusinessCode(int))
EXIT execution(void de.scrum_master.app.MyWorker.otherBusinessCode(int)) -> return value = null
EXIT execution(void de.scrum_master.app.Application.doSomething(String, Object)) -> return value = null
EXIT execution(void de.scrum_master.app.Application.main(String[])) -> return value = null
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3751 次 |
| 最近记录: |