Spring面试(三)AOP

本文详细探讨了如何在Spring中使用@AspectJ进行切面编程,包括切入点表达式、通知类型(前置、后置、环绕等)、通知参数和顺序,以及SpringAOP与AspectJ的区别。涵盖实例演示和常见面试问题解答。
摘要由CSDN通过智能技术生成

文章目录

@AspectJ support

@AspectJ refers to a style of declaring aspects as regular Java classes annotated with annotations. The @AspectJ style was introduced by the AspectJ project as part of the AspectJ 5 release. Spring interprets the same annotations as AspectJ 5, using a library supplied by AspectJ for pointcut parsing and matching. The AOP runtime is still pure Spring AOP, though, and there is no dependency on the AspectJ compiler or weaver.

大意:@AspectJ 指的是一种将切面声明为带有注解的常规 Java 类的风格。 @AspectJ 样式是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。 Spring 解释与 AspectJ 5 相同的注释,使用 AspectJ 提供的库进行切入点解析和匹配。 但是,AOP 运行时仍然是纯 Spring AOP,并且不依赖于 AspectJ 编译器或编织器。

1. Declaring an Aspect

With @AspectJ support enabled, any bean defined in your application context with a class that is an @AspectJ aspect (has the @Aspect annotation) is automatically detected by Spring and used to configure Spring AOP. The next two examples show the minimal definition required for a not-very-useful aspect.

大意:启用@AspectJ 支持后,在应用程序上下文中定义的具有@AspectJ 方面(具有@Aspect 注释)的类的任何bean 都会被Spring 自动检测并用于配置Spring AOP。 接下来的两个示例显示了一个不太有用的方面所需的最小定义。

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

2 Declaring a Pointcut

2.1 支持的切入点表达式

2.1.1 execution 表达式

有如下表达式:

execution (* com.sample.service.impl..*.*(..))

含义如下:

符号含义
execution()
表达式的主体;
第一个 * 符号
表示的任意类型返回值; 如果返回值为对象,则需指定全路径的类名。
com.sample.service.implAOP所切的服务的包名,即,我们的业务部分
包名后面的”..“表示当前包及子包
第二个 * 符号表示类名,*即所有类。
.*(..)表示任何方法名,括号表示参数,两个点表示任何参数类型

2.1.2 annotation

  1. 下面的通知将作用在标注了@RW注解的方法上(@RW是一个自定义注解)
@Before("@annotation(com.zhx.annotation.RW)")
public void before(JoinPoint point){
    System.out.pringtln("before advice")
}
  1. 还可以为advice传递注解参数;RW是一个自定义注解
@Before("within(com.zhx.controller.*) && @annotation(rw)")
    public void before(JoinPoint point, RW rw){
        String name = rw.value();w
        DynamicDataSource.name.set(name);
    }

2.1.3 within

文档:Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).

大意:将匹配限制为具有给定注解的类型中的连接点(使用 Spring AOP 时执行在具有给定注解的类型中声明的方法)。

@within(org.springframework.transaction.annotation.Transactional)

2.2 Combining Pointcut Expressions

文档中有如下部分:

You can combine pointcut expressions by using &&, || and !.

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

2.3 Sharing Common Pointcut Definitions (共享通用切入点定义-使用@Pointcut)

When working with enterprise applications, developers often want to refer to modules of the application and particular sets of operations from within several aspects. We recommend defining a CommonPointcuts aspect that captures common pointcut expressions for this purpose. Such an aspect typically resembles the following example:

package com.xyz.myapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CommonPointcuts {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.myapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.myapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.myapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
     * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

2.4 一些定义切入点的例子 和 execution() 详解

官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples

3. Declaring Advice

3.1 before(前置通知)/after(后置通知): 在方法开始执行前(执行后)执行

使用@Before/@(After)声明一个前置(后置)通知;

3.2 afterReturning(返回后通知): 在方法返回后执行

After returning advice runs when a matched method execution returns normally. You can declare it by using the @AfterReturning annotation

3.3 afterThrowing(异常通知): 在抛出异常时执行

After throwing advice runs when a matched method execution exits by throwing an exception. You can declare it by using the @AfterThrowing annotation

3.4 around(环绕通知): 在方法执行前和执行后都会执行

官方文档:https://docs.spring.io/spring-framework/docs/5.2.22.RELEASE/spring-framework-reference/core.html#aop-ataspectj-around-advice

Around advice is declared by annotating a method with the @Around annotation. The method should declare Object as its return type, and the first parameter of the method must be of type ProceedingJoinPoint. Within the body of the advice method, you must invoke proceed() on the ProceedingJoinPoint in order for the underlying method to run. Invoking proceed() without arguments will result in the caller’s original arguments being supplied to the underlying method when it is invoked. For advanced use cases, there is an overloaded variant of the proceed() method which accepts an array of arguments (Object[]). The values in the array will be used as the arguments to the underlying method when it is invoked.

大意:使用@Around声明一个环绕通知,环绕通知的方法应该声明为具有object返回值,并且第一个参数必须是ProceedingJoinPoint类型的方法,在方法体中必须调用 ProceedingJoinPoint提供的proceed()方法;

3.5 Advice Parameters (通知参数)

3.5.1 Access to the Current JoinPoint (获取连接点的信息)

官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj-advice-params-the-joinpoint

3.5.1.1 Joinpoint
public interface JoinPoint {

Joinpoint提供如下get方法:

  • Object getThis() :返回AOP代理对象
  • Object getTarget() :返回目标对象,即被代理的对象
  • Object [ ] getArgs() :返回切入点的参数
  • Signature getSignature() :返回Signature ,包含方法名等信息
  • SourceLocation getSourceLocation() :the source location corresponding to the join point.
  • String getKind() :返回一个表示连接点类型的字符串
3.5.1.2 ProceedingJoinPoint
public interface ProceedingJoinPoint extends JoinPoint {

Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。
环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的

3.5.2 Passing Parameters to Advice (向通知中传递参数 args()@annotation()

官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj-advice-params-passing

适用于 args()@annotation表达式

  • 例一:
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}
  • 例二:@annotation() 向advice 传递参数
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}

3.6 Advice Ordering (通知优先级)

各种advice(通知)的执行顺序
官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj-advice-ordering

3.6.1 当多个切面的通知切入到同一个切入点时,他们的执行顺序是什么样的?可以调整顺序吗?

文档中有如下部分:

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first “on the way in” (so, given two pieces of before advice, the one with highest precedence runs first). “On the way out” from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the one with the highest precedence will run second).

大意:在进入切点时,最高优先级的通知方法总是最先执行;在离开切入点时,最高优先级的通知方法总是最后执行。

When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise, the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the @Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getOrder() (or the annotation value) has the higher precedence.

大意:两个不同切面中的通知针对同一切入点时,顺序是未知的,可以通过实现 org.springframework.core.Ordered 接口,返回切面的优先级或者使用@Order 注解定义优先级。

3.6.2 一个切面的多个不同类型通知切入到同一切入点时,执行顺序是什么?

文档中有如下部分:

As of Spring Framework 5.2.7, advice methods defined in the same @Aspect class that need to run at the same join point are assigned precedence based on their advice type in the following order, from highest to lowest precedence: @Around, @Before, @After, @AfterReturning, @AfterThrowing. Note, however, that an @After advice method will effectively be invoked after any @AfterReturning or @AfterThrowing advice methods in the same aspect, following AspectJ’s “after finally advice” semantics for @After.

大意:自spring5.2.7之后,定义在同一切面,针对同一切入点的advice(增强)有如下的优先级(从高到底) @Around, @Before, @After, @AfterReturning, @AfterThrowing. 。但是:@After会在同一个切面中的@AfterReturning or @AfterThrowing被调用之后才会被有效的调用。

如下图所示:
1. 正常情况下:
在这里插入图片描述
2. 出现异常
在这里插入图片描述

3.6.3 当一个切面的多个相同类型通知切入到同一切入点时,他的执行顺序是什么样的?

文档中有如下部分

Whentwo pieces of the same type of advice (for example, two @After advice methods) defined in the same @Aspectclass both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the source code declaration order through reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each @Aspect class or refactor the pieces of advice into separate @Aspect classes that you can order at the aspect level via Ordered or @Order.

大意:同一个切面中两个相同类型的advice,针对同一个切点,顺序是未知的,。可以考虑将这两个相同的通知方法合并成一个通知方法,或者将两个通知方法拆分到两个切面类中去,然后实现Ordered接口或者使用@Order注解。

面试题

1. 什么是AOP?

OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并不适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

在这里插入图片描述

2. Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

AspectJ

Spring AOP

AspectJ是静态代理的增强

静态代理,是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象;

Spring AOP使用的动态代理(运行时增强)

动态代理就是AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法

AspectJ提供了完全的AOP 支持,它还支持属性级别的PointCut

SpringAOP 仅支持方法级别的PointCut

AspectJ基于字节码操作(Bytecode Manipulation)

Spring AOP基于代理(Proxying)

Spring AOP已经集成了AspectJ

AspectJ比Spring AOP功能更强大

Spring AOP 相对来说更简单

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

3. JDK动态代理和CGLIB动态代理的区别(Aop中动态代理的主要两种方式)

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

4. 解释一下Spring AOP里面的几个名词

(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

(3)通知(Advice):在AOP术语中,切面的工作被称为通知。

(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。

(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。

(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多个点可以进行织入:

  1. 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
  2. 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
  3. 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。

5. 在Spring AOP 中,关注点和横切关注的区别是什么?在 spring aop 中 concern 和 cross-cutting concern 的不同之处

关注点(concern)是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。

横切关注点(cross-cutting concern)是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

6. Aop 相关注解

名称说明
@Aspect用于定义一个切面。
@Pointcut用于定义一个切入点。
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于 ThrowAdvice。
@After用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值