带有注解的 Java 面向方面编程
Posted
技术标签:
【中文标题】带有注解的 Java 面向方面编程【英文标题】:Java Aspect-Oriented Programming with Annotations 【发布时间】:2011-06-17 06:39:17 【问题描述】:在题为"AOP Fundamentals" 的帖子中,我要求国王英语解释什么是AOP,以及它的作用。我收到了一些非常有用的答案和文章链接,这些文章帮助我了解了所有理论。
但是现在 AOP 得到了我的全部关注,所有这些文章和章节摘录都很棒,但是在每一个案例中,它们都包含了崇高的理论、模糊的 UML 模型和抽象的顺序对我来说太高了。
这是我对 AOP 理论的理解,只是为了澄清一下,所以如果你看到看起来不对的地方,请告诉我!:
诸如日志记录、身份验证、同步、验证、异常处理等交叉问题在非 AOP 系统中变得高度耦合,因为它们几乎被代码库中的每个组件/模块普遍使用。
AOP 定义了 aspects(类/方法),通过使用 join points、advice 和 advice 来抽象这些横切关注点strong>切入点。
一个。 建议 - 实现横切关注点的实际代码(可能是方面的方法?)(即执行实际的日志记录、验证、身份验证等)
b. 加入点 - 在非 AOP 代码中触发的事件,导致执行特定方面的建议(“编织”到非 AOP 代码中)
c。 切入点 - 本质上是连接点(触发事件)到建议执行的映射
所有方面(LoggingAspect、AuthenticationAspect、ValidationAspect 等)都模块化为组件,并使用 AspectWeaver 进行注册。当非 AOP/POJO 代码遇到连接点时,AspectWeaver 围绕非 AOP 代码“编织”(集成)映射的建议:
64,000 美元的问题:我对基于 Java 的 AOP 的理解是达到目标还是偏离目标,为什么?如何正确使用注解来实现切面、建议、连接点、切入点和这种所谓的切面编织器?
【问题讨论】:
【参考方案1】:假设您想使用@LogExecTime
注释记录一些带注释的方法所花费的时间。
我先创建一个注解LogExecTime
:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecTime
然后我定义一个方面:
@Component // For Spring AOP
@Aspect
public class LogTimeAspect
@Around(value = "@annotation(annotation)")
public Object LogExecutionTime(final ProceedingJoinPoint joinPoint, final LogExecTime annotation) throws Throwable
final long startMillis = System.currentTimeMillis();
try
System.out.println("Starting timed operation");
final Object retVal = joinPoint.proceed();
return retVal;
finally
final long duration = System.currentTimeMillis() - startMillis;
System.out.println("Call to " + joinPoint.getSignature() + " took " + duration + " ms");
我创建了一个带有 LogExecTime
注释的类:
@Component
public class Operator
@LogExecTime
public void operate() throws InterruptedException
System.out.println("Performing operation");
Thread.sleep(1000);
还有一个主要使用Spring AOP:
public class SpringMain
public static void main(String[] args) throws InterruptedException
ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
final Operator bean = context.getBean(Operator.class);
bean.operate();
如果我运行这个类,我会在标准输出上得到以下输出:
Starting timed operation
Performing operation
Call to void testaop.Operator.Operate() took 1044 ms
现在有了魔法。正如我确实使用 Spring AOP 而不是 AspectJ weaver 一样,神奇之处在于使用代理机制在运行时发生。所以.class
文件保持不变。例如,如果我调试这个程序并在operate
中放置一个断点,您将看到 Spring 是如何发挥作用的:
由于 Spring AOP 实现是非侵入式并使用 Spring 机制,您需要添加 @Component
注释并使用 Spring 上下文而不是普通的 new
创建对象。
另一边的AspectJ 将更改.class
文件。我用 AspectJ 尝试了这个项目,并用 jad 反编译了 Operator 类。这导致:
public void operate()
throws InterruptedException
JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, this);
operate_aroundBody1$advice(this, joinpoint, LogTimeAspect.aspectOf(), (ProceedingJoinPoint)joinpoint, (LogExecTime)(ajc$anno$0 == null && (ajc$anno$0 = testaop/Operator.getDeclaredMethod("operate", new Class[0]).getAnnotation(testaop/LogExecTime)) == null ? ajc$anno$0 : ajc$anno$0));
private static final void operate_aroundBody0(Operator ajc$this, JoinPoint joinpoint)
System.out.println("Performing operation");
Thread.sleep(1000L);
private static final Object operate_aroundBody1$advice(Operator ajc$this, JoinPoint thisJoinPoint, LogTimeAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint, LogExecTime annotation)
long startMillis = System.currentTimeMillis();
Object obj;
System.out.println("Starting timed operation");
ProceedingJoinPoint proceedingjoinpoint = joinPoint;
operate_aroundBody0(ajc$this, proceedingjoinpoint);
Object retVal = null;
obj = retVal;
long duration = System.currentTimeMillis() - startMillis;
System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
return obj;
Exception exception;
exception;
long duration = System.currentTimeMillis() - startMillis;
System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
throw exception;
private static void ajc$preClinit()
Factory factory = new Factory("Operator.java", testaop/Operator);
ajc$tjp_0 = factory.makeSJP("method-execution", factory.makeMethodSig("1", "operate", "testaop.Operator", "", "", "java.lang.InterruptedException", "void"), 5);
private static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0; /* synthetic field */
private static Annotation ajc$anno$0; /* synthetic field */
static
ajc$preClinit();
【讨论】:
您的示例代码目前使用@Around(value = "@annotation(annotation)")
,但我认为应该是@Around(value = "@annotation(LogExecTime)")
(Spring AOP documentation)。
对我来说,它看起来像是观察者模式 (en.wikipedia.org/wiki/Observer_pattern) 的一个奇特实现。不是吗?【参考方案2】:
几个月前,我写了一篇文章,其中包含一个示例,介绍了我如何实现将 Aspect/J 方面与 Java 注释相结合的实际案例,您可能会发现它很有用:
http://technomilk.wordpress.com/2010/11/06/combining-annotations-and-aspects-part-1/
我相信应用于注释的方面是一个很好的组合,因为它们使方面在您的代码中更加明确,而且以一种简洁的方式,并且您可以在注释中使用参数以获得更大的灵活性。
顺便说一句,Aspect/J 的工作方式是在编译时修改你的类,而不是在运行时。您通过 Aspect/J 编译器运行您的源代码和切面,它会创建修改后的类文件。
据我所知,Spring AOP 通过创建代理对象以不同的方式进行编织(操纵类文件以包含方面处理),我相信在实例化时(但不要相信我的话为它)。
【讨论】:
Besides different compile-time weaving strategies, AspectJ also allows load-time weaving (LTW)。使用 LTW,当这个特定的类第一次被它的class loader 加载时,一个类会被修改(== 与方面交织在一起)。 LTW 可以被视为“在运行时编织”,尽管它发生在与类交互的最早点(......而不是在运行时的某个其他时间点)。【参考方案3】:经过大量挖掘和肘部油脂,我自己找到了答案......
是的,AOP 在 Java 世界中应该是基于注解的,但是您不能像常规(元数据)注解那样处理与方面相关的注解。要拦截标记的方法调用并在它之前/之后“编织”建议方法,您需要一些非常漂亮的以 AOP 为中心的引擎(例如 AspectJ)的帮助。 @Christopher McCann 在另一个与注释相关的线程中提供了一个非常好的解决方案,他建议将 AOP Alliance 与 Google Guice 结合使用。在阅读了 Guice 关于 AOP 支持的文档之后,这正是我正在寻找的:一个易于理解的框架,用于编织横切关注点的“建议”(方法调用),例如日志记录、验证、缓存、等等
这是一个笨蛋。
【讨论】:
【参考方案4】:更改评论
// The AspectWeaver *magically* might weave in method calls so main now becomes
到
// The AspectWeaver *magically* might weave in method calls so main now
// becomes effectively (the .class file is not changed)
我喜欢 AOP 的春季文章。查看Chapter 7
【讨论】:
请不要使用 html<code>
标签。只需将您的代码缩进 4 个空格或单击带有花括号的图标【参考方案5】:
这是我对这篇非常有用的帖子的贡献。
我们将举一个非常简单的例子:我们需要对某些方法的处理采取行动。 它们使用自定义注释进行注释,其中包含要处理的数据。鉴于这些数据,我们希望引发异常或让流程继续进行,就像方法没有被注释一样。
用于定义我们方面的 Java 代码:
package com.example;
public class AccessDeniedForCustomAnnotatedMethodsAspect
public Object checkAuthorizedAccess(ProceedingJoinPoint proceedingJointPoint)
throws Throwable
final MethodSignature methodSignature = (MethodSignature) proceedingJointPoint
.getSignature();
// how to get the method name
final String methodName = methodSignature
.getMethod()
.getName();
// how to get the parameter types
final Class<?>[] parameterTypes = methodSignature
.getMethod()
.getParameterTypes();
// how to get the annotations setted on the method
Annotation[] declaredAnnotations = proceedingJointPoint
.getTarget()
.getClass()
.getMethod(methodName, parameterTypes)
.getDeclaredAnnotations();
if (declaredAnnotations.length > 0)
for (Annotation declaredAnnotation : Arrays.asList(declaredAnnotations))
// I just want to deal with the one that interests me
if(declaredAnnotation instanceof CustomAnnotation)
// how to get the value contained in this annotation
(CustomAnnotation) declaredAnnotation).value()
if(test not OK)
throw new YourException("your exception message");
// triggers the rest of the method process
return proceedingJointPoint.proceed();
xml配置:
<aop:config>
<aop:aspect id="accessDeniedForCustomAnnotatedMethods"
ref="accessDeniedForCustomAnnotatedMethodsAspect">
<aop:around pointcut="execution(@xxx.zzz.CustomAnnotation * *(..))"
method="checkAuthorizedAccess" />
</aop:aspect>
</aop:config>
<bean id="accessDeniedForCustomAnnotatedMethodsAspect"
class="xxx.yyy.AccessDeniedForCustomAnnotatedMethodsAspect" />
希望对你有帮助!
【讨论】:
以上是关于带有注解的 Java 面向方面编程的主要内容,如果未能解决你的问题,请参考以下文章