![ioc和aop的原理面试](https://i-blog.csdnimg.cn/blog_migrate/947cdd8b8afecde856197418cc9222ee.png)
ioc和aop的原理面试
有时,我想记录(通过slf4j和log4j )方法的每次执行,查看其接收到的参数,返回的内容以及每次执行花费的时间。 这就是我在AspectJ , jcabi-aspects和Java 6注释的帮助下所做的事情:
public class Foo {
@Loggable
public int power(int x, int p) {
return Math.pow(x, p);
}
}
这是我在log4j输出中看到的:
[INFO] com.example.Foo #power(2, 10): 1024 in 12μs
[INFO] com.example.Foo #power(3, 3): 27 in 4μs
很好,不是吗? 现在,让我们看看它是如何工作的。
带有运行时保留的注释
注释是Java 6中引入的一种技术。它是一种元编程工具,它不会改变代码的工作方式,但会为某些元素(方法,类或变量)提供标记。 换句话说,注释只是附加到可以查看和阅读的代码的标记。 某些注释仅在编译时可见-编译后在.class
文件中不存在。 其他的在编译后仍然可见,并且可以在运行时访问。
例如, @Override
是第一种类型(其保留类型为SOURCE
),而来自JUnit的@Test
是第二种类型(保留类型为RUNTIME
)。 @Loggable
(我在上面的脚本中使用的那个)是jcabi-aspects中第二种类型的注释。 编译后,它与.class
文件中的字节码保持在一起。
同样,重要的是要理解,即使方法power()
已被注释和编译,但到目前为止它不会向slf4j发送任何内容。 它仅包含一个标记,上面写着“请记录我的执行情况”。
面向方面的编程(AOP)
AOP是一种有用的技术,它允许在不显式更改源代码的情况下将可执行块添加到源代码中。 在我们的示例中,我们不想在类内部记录方法的执行情况。 相反,我们希望其他类拦截对方法power()
每次调用,测量其执行时间,然后将此信息发送给slf4j。
我们希望拦截器理解我们的@Loggable
注释,并记录对该特定方法power()
每次调用。 而且,当然,其他的方法应使用相同的拦截器,将来我们将在其中放置相同的注释。
这种情况完全符合AOP的初衷-避免在多个类中重新实现某些常见行为。
日志记录是我们主要功能的补充功能,我们不想使用多个日志记录指令来污染我们的代码。 相反,我们希望在后台进行日志记录。
在AOP方面,我们的解决方案可以解释为创建一个方面,该方面在某些连接点处横切代码,并应用环绕建议以实现所需的功能。
AspectJ
让我们看看这些神奇的词是什么意思。 但是,首先,让我们看一下jcabi-aspects如何使用AspectJ实现它们(这是一个简化的示例,您可以在MethodLogger.java
找到完整的代码):
@Aspect
public class MethodLogger {
@Around("execution(* *(..)) && @annotation(Loggable)")
public Object around(ProceedingJoinPoint point) {
long start = System.currentTimeMillis();
Object result = point.proceed();
Logger.info(
"#%s(%s): %s in %[msec]s",
MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
point.getArgs(),
result,
System.currentTimeMillis() - start
);
return result;
}
}
这是一个方面,里面有一个关于around()
建议。 方面用@Aspect
注释,而建议用@Around
注释。 如上所述,这些注释只是.class
文件中的标记。 除了为那些对运行时感兴趣的人提供一些元信息外,他们什么也不做。
注释@Around
有一个参数,在这种情况下,该参数表示在以下情况下建议应适用于方法:
- 其可见性修饰符为
*
(public
,protected
或private
); - 它的名字是名字
*
(任何名字); - 它的参数是
..
(任何参数); 和 - 它用
@Loggable
注释
如果要截获对带注释的方法的调用,则在执行实际方法之前,先执行around()
方法around()
。 当要拦截对power()
方法的调用时, around()
方法将接收ProceedingJoinPoint
类的实例,并且必须返回一个对象,该对象将作为power()
方法的结果使用。
为了调用原始方法power()
,建议必须调用连接点对象的proceed()
。
我们编译此方面,并使其与主文件Foo.class
一起在classpath中可用。 到目前为止,一切都很好,但是我们需要采取最后一步,以便将我们的方面付诸实践-我们应该应用我们的建议。
二元方面编织
方面编织是建议应用过程的名称。 Aspect Weaver通过注入对Aspect的调用来修改原始代码。 AspectJ正是这样做的。 我们给它提供了两个二进制Java类Foo.class
和MethodLogger.class
; 它给退三-修改Foo.class
, Foo$AjcClosure1.class
和未修改MethodLogger.class
。
为了了解哪些建议应应用于哪些方法,AspectJ weaver正在使用.class
文件中的注释。 同样,它使用反射来浏览类路径上的所有类。 它通过@Around
注释分析哪些方法满足条件。 当然,它找到我们的方法power()
。
因此,有两个步骤。 首先,我们使用javac
编译.java
文件并获得两个文件。 然后,AspectJ编织/修改它们并创建自己的额外类。 编织后,我们的Foo
类如下所示:
public class Foo {
private final MethodLogger logger;
@Loggable
public int power(int x, int p) {
return this.logger.around(point);
}
private int power_aroundBody(int x, int p) {
return Math.pow(x, p);
}
}
AspectJ weaver将我们的原始功能移动到新方法power_aroundBody()
,并将所有power()
调用重定向到方面类MethodLogger
。
现在,我们有四个类一起工作,而不是Foo
类中的power()
方法。 从现在开始,这就是每次调用power()
幕后发生的事情:
方法power()
原始功能由图中的绿色小生命线指示。
如您所见,方面编织过程将类和方面连接在一起,并通过连接点在它们之间转移调用。 无需编织,类和方面都只是带有附加注释的已编译Java二进制文件。
jcabi方面
jcabi-aspects是一个JAR库,其中包含Loggable
注释和MethodLogger
方面(顺便说一句,还有更多方面和注释)。 您无需为方法记录编写自己的方面。 只需在类路径中添加一些依赖项,然后配置jcabi-maven-plugin进行编织(在Maven Central中获取其最新版本):
<project>
<depenencies>
<dependency>
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
</dependency>
</depenencies>
<build>
<plugins>
<plugin>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>ajc</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
由于此编织过程需要大量的配置工作,因此我创建了一个方便的带有ajc
目标的Maven插件,该插件可以完成整个方面的编织工作。 您可以直接使用AspectJ,但是我建议您使用jcabi-maven-plugin 。
而已。 现在,您可以使用@com.jcabi.aspects.Loggable
注释,您的方法将通过slf4j记录。
如果某些内容无法按照说明进行操作,请随时提交Github问题。
相关文章
您可能还会发现以下有趣的帖子:
翻译自: https://www.javacodegeeks.com/2014/09/java-method-logging-with-aop-and-annotations.html
ioc和aop的原理面试