我是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...");
}
现在,参考在线文档/教程,“日志建议”的用例是我们可以从代码中删除日志代码,“日志建议”将进行日志记录,例如在进入,返回方法时,使用注释。
在上面的例子中,我同意使用@Before,@ After“advice”可以帮助输入,返回方法调用。
我对AOP如何帮助处理方法内部的记录器感到困惑,理想情况下可以在任何地方使用,我们会在某个时间点捕获信息。
我提到了this SO question,但没有明白AOP如何帮助这样的情况。
任何人都可以帮我理解这个吗?
答案 0 :(得分:3)
简短的回答是: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);
}
}
正如您所看到的,我从您的复杂方法中提取了一些方法。为了向您展示更多日志输出,我甚至通过调用在for循环中多次执行复杂内容的方法,再次将复杂性添加到doSomething(..)
。
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));
}
}
日志记录方面可能如下所示:
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);
}
}
控制台日志如下所示:
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
正如您所看到的,您可以根据新的,更模块化的方法结构获得初始问题中您想要的所有日志记录。旧方法变得更具可读性,新方法更简单,因为它们专注于执行您为其提取的内容。
请注意:此示例代码是使用AspectJ运行的,而不是使用“AOP lite”框架Spring AOP运行。所以在Spring AOP中它不会像这样工作,因为:
main
之类的静态方法,因为它只能拦截公共的,非静态的接口方法(当使用Java动态代理时)或者另外受保护的和包范围的方法(当使用CGLIB代理时) )。因此,如果您考虑将代码重构为我建议的内容,并考虑将某些辅助方法设为私有但仍希望将其记录,除了将Spring配置为使用完整AspectJ via LTW (load-time weaving)以便使用之外别无他法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) {}
}
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));
}
}
方面可以保持不变。
日志输出更改为以下内容:
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