Spring AOP 使用

1. 简介

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如:事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 基于动态代理。如果要代理的对象,实现了某个接口,那么Spring AOP会使用 JDK 代理,去创建代理对象;而对于没有实现接口的对象,使用 CGLIB 生成一个被代理对象的子类来作为代理。

AspectJ 算是 Java 生态系统中最完整的 AOP 框架,Spring AOP 也已经集成了 AspectJ。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单。

Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。
Spring AOP 基于代理,而 AspectJ 基于字节码操作。
当切面比较少,两者性能差异不大;当切面比较多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

2. 术语

2.1 切面(Aspect)

切面是通知(Advice)和切点(Pointcut)的结合。

通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。

2.2 通知(Advice)

通知是切面开启后,切面的方法。

  1. 前置通知(before):在目标方法或环绕通知调用前执行。
  2. 后置通知(after):在目标方法或环绕通知完成后执行。无论是否抛出异常,都会执行。
  3. 返回通知(afterReturning):在目标方法或环绕通知完成后执行。
  4. 异常通知(afterThrowing):在目标方法或环绕通知产生异常后执行。
  5. 环绕通知(around):包裹了目标方法,可同时定义前置通知和后置通知。

2.3 引入(Introduction)

引入允许我们在现有的类里添加自定义的类和方法。

2.4 切点(Pointcut)

切点是指通知(Advice)所要织入(Weaving)的具体位置。

切点定义了在何处工作,也就是真正被切入的地方,也就是在哪个方法应用通知。切点的定义会匹配通知所有要织入的一个或多个连接点。

2.5 连接点(Join point)

连接点是在应用执行过程中能够插入切面(Aspect)的一个点。这些点可以是调用方法时、甚至修改一个字段时。

连接点是一个虚拟的概念,可以理解为所有满足切点扫描条件的所有的时机。

切点表达式描述
execution匹配方法执行的连接点,这是最常用的匹配。
within匹配连接点指定的包。
args匹配参数为指定类型的方法。
this匹配代理对象。
target匹配目标对象。
@within匹配带有指定注解的类。
@annotaion匹配带有指定注解的方法。
@args匹配带有指定注解的类作为参数的方法。

2.6 织入(Weaving)

织入是一个生成代理对象的过程,是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以织入。

  1. 编译器:切面在目标类编译时被织入。这种方式需要特殊的编译器。
  2. 类加载期:切面在目标类被引入应用之前增强该目标类的字节码。
  3. 运行期:切面在应用运行的某个时刻被织入。

3. 使用

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.1 execution

匹配方法执行的连接点

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

  1. modifiers-pattern:方法的可见性。
  2. ret-type-pattern:方法的返回值类型。
  3. declaring-type-pattern:方法所在类的全路径名。
  4. name-pattern:方法名类型。
  5. param-pattern:方法的参数类型。
  6. throws-pattern:方法抛出的异常类型。

创建接口和接口实现

public interface AopService {
    void first(Integer i);
}
@Service("aopService")
public class AopServiceImpl implements AopService {
    @Override
    public void first(Integer i) {
        if (i == null) {
            System.out.println("AopServiceImpl first(" + i + ") throw new NullPointerException()");
            throw new NullPointerException();
        }
        System.out.println("AopServiceImpl first(" + i + ")");
    }
}

定义切面

@Aspect
@Component
public class ExecutionAop {

    // 匹配AopService下带任意参数的方法
    @Pointcut("execution(public * com.shpun.service.AopService.*(..))")
    public void executionAopPointcut() {
    }

    @Before("executionAopPointcut()")
    public void doBefore() {
        System.out.println("ExecutionAop Before");
    }

    @After("executionAopPointcut()")
    public void doAfter() {
        System.out.println("ExecutionAop After");
    }

    @Around("executionAopPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        System.out.println("ExecutionAop Around before");

        Object result;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e.getMessage(), e);
        }

        System.out.println("ExecutionAop Around after");
        return result;
    }

    @AfterReturning("executionAopPointcut()")
    public void doAfterReturning() {
        System.out.println("ExecutionAop AfterReturning");
    }

    @AfterThrowing("executionAopPointcut()")
    public void doAfterThrowing() {
        System.out.println("ExecutionAop AfterThrowing");
    }
}

测试

@SpringBootTest
public class ExecutionAopTest {
    @Autowired
    private AopService aopService;

    @Test
    public void executionAopTest() {
        aopService.first(1);
        System.out.println("------");
        aopService.first(null);
    }
}

结果打印

ExecutionAop Around before
ExecutionAop Before
AopServiceImpl first(1)
ExecutionAop AfterReturning
ExecutionAop After
ExecutionAop Around after
------
ExecutionAop Around before
ExecutionAop Before
AopServiceImpl first(null) throw new NullPointerException()
ExecutionAop AfterThrowing
ExecutionAop After

3.2 within

匹配连接点指定的包

within(declaring-type-pattern)

  1. declaring-type-pattern:方法所在类的全路径名。

定义切面

@Aspect
@Component
public class WithinAop {

    // 匹配com.shpun.service包及子包下的所有类
    @Pointcut("within(com.shpun.service..*)")
    public void withinAopPointcut() {
    }

	...
    // 和 ExecutionAop 中类似,注意修改切点
    ...
}

3.3 args

匹配参数为指定类型的方法

args(param-pattern)

  1. param-pattern:方法的参数类型。

定义切面

@Aspect
@Component
public class ArgsAop {

    // 只使用args(java.lang.Integer),条件太广泛会报错
    // 匹配com.shpun.service包及子包下的所有类的,有任意参数的方法 并且 只有一个参数类型为Integer的方法
    @Pointcut("execution(* com.shpun.service..*(..)) && args(java.lang.Integer)")
    public void argsAopPointcut() {
    }
    
    ...
    // 和 ExecutionAop 中类似,注意修改切点
    ...
}

3.4 bean

@Aspect
@Component
public class BeanAop {
    /**
     * 匹配bean name
     */
    @Pointcut("bean(aopService)")
    public void beanAopPointcut() {
    }
}

3.5 this 与 target

匹配代理对象 this(declaring-type-pattern)
匹配目标对象 target(declaring-type-pattern)

  1. declaring-type-pattern:方法所在类的全路径名,只能指定类或接口。

this 和 target 区别:

  1. 纯接口:this 与 target 表现一致
  2. 纯类:this 与 target 表现一致
  3. 实现某个接口的类:this 与 target 不一致

下面以实现某个接口的类为例:

定义切面

@Aspect
@Component
public class ThisAop {

    @Pointcut("this(com.shpun.service.impl.AopServiceImpl)")
    public void thisAopPointcut() {
    }
    
    ...
    // 和 ExecutionAop 中类似,注意修改切点
    ...
}

定义切面

@Aspect
@Component
public class TargetAop {

    @Pointcut("target(com.shpun.service.impl.AopServiceImpl)")
    public void targetAopPointcut() {
    }
    
	...
    // 和 ExecutionAop 中类似,注意修改切点
    ...
}

修改AopService,添加带默认实现的方法second()

public interface AopService {
    void first(Integer i);

    default void second() {
        System.out.println("AopService second()");
    }
}

测试

@SpringBootTest
public class ThisAndTargetAopTest {

    @Autowired
    private AopServiceImpl aopServiceImpl;

    @Test
    public void aopServiceImplTest() {
        aopServiceImpl.first(1);
        System.out.println("------");
        aopServiceImpl.second();
    }
}

结果打印

TargetAop Around before
TargetAop Before
ThisAop Around before
ThisAop Before
AopServiceImpl first(1)
ThisAop AfterReturning
ThisAop After
ThisAop Around after
TargetAop AfterReturning
TargetAop After
TargetAop Around after
------
TargetAop Around before
TargetAop Before
AopService second()
TargetAop AfterReturning
TargetAop After
TargetAop Around after

3.6 @within

匹配带有指定注解的类

@within(annotation-type)

  1. annotation-type:注解的全路径。

添加注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetType {
}

定义切面

@Aspect
@Component
public class AnnotationAop {

    // 匹配 类有注解@TargetType
    @Pointcut("@within(com.shpun.annotaion.TargetType)")
    public void annotationAopPointcut() {
    }
    
	...
    // 和 ExecutionAop 中类似,注意修改切点
    ...
}

修改AopServiceImpl,类上添加注解@TargetType

@Service("aopService")
@TargetType
public class AopServiceImpl implements AopService {
    @Override
    public void first(Integer i) {
        if (i == null) {
            System.out.println("AopServiceImpl first(" + i + ") throw new NullPointerException()");
            throw new NullPointerException();
        }
        System.out.println("AopServiceImpl first(" + i + ")");
    }
}

测试

@SpringBootTest
public class AnnotationAopTest {

    @Autowired
    private AopService aopService;

    @Test
    public void withinTest() {
        aopService.first(1);
        System.out.println("------");
        aopService.second();
    }
}

结果打印

AnnotationAop Around before
AnnotationAop Before
AopServiceImpl first(1)
AnnotationAop AfterReturning
AnnotationAop After
AnnotationAop Around after
------
AopService second()

3.7 @annotaion

匹配带有指定注解的方法

@annotation(annotation-type)

  1. annotation-type:注解的全路径。

添加注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetMethod {
}

定义切面

@Aspect
@Component
public class AnnotationAop {

    // 匹配 方法有注解@TargetMethod
    @Pointcut("@annotation(com.shpun.annotaion.TargetMethod)")
    public void annotationAopPointcut() {
    }
    
	...
    // 和 ExecutionAop 中类似,注意修改切点
    ...
}

修改AopServiceImpl,给方法first()添加注解@TargetMethod

@Service("aopService")
public class AopServiceImpl implements AopService {
    @TargetMethod
    @Override
    public void first(Integer i) {
        if (i == null) {
            System.out.println("AopServiceImpl first(" + i + ") throw new NullPointerException()");
            throw new NullPointerException();
        }
        System.out.println("AopServiceImpl first(" + i + ")");
    }
}

测试

@SpringBootTest
public class AnnotationAopTest {

    @Autowired
    private AopService aopService;

    @Test
    public void annotationTest() {
        aopService.first(1);
        System.out.println("------");
        aopService.second();
    }
}

打印结果

AnnotationAop Around before
AnnotationAop Before
AopServiceImpl first(1)
AnnotationAop AfterReturning
AnnotationAop After
AnnotationAop Around after
------
AopService second()

3.8 @args

匹配带有指定注解的类作为参数的方法

@args(annotation-type)

  1. annotation-type:注解的全路径。

定义注解

@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetParameter {
}

定义切面

@Aspect
@Component
public class AnnotationAop {

    // 匹配 com.shpun.service包及子包下的所有类的,有任意参数的方法 并且 类有注解@TargetParameter作为方法的唯一参数
    @Pointcut("execution(* com.shpun.service..*(..)) && @args(com.shpun.annotaion.TargetParameter)")
    public void annotationAopPointcut() {
    }
    
	...
    // 和 ExecutionAop 中类似,注意修改切点
    ...
}

添加User类,用于当作方法的参数

@TargetParameter
public class User {
}

修改AopService,添加带默认实现的方法third()

public interface AopService {
    void first(Integer i);

    default void second() {
        System.out.println("AopService second()");
    }

    default void third(User user) {
        System.out.println("AopService third()");
    }
}

测试

@SpringBootTest
public class AnnotationAopTest {

    @Autowired
    private AopService aopService;

    @Test
    public void argsTest() {
        aopService.first(1);
        System.out.println("------");
        aopService.second();
        System.out.println("------");
        aopService.third(new User());
    }
}

结果打印

AopServiceImpl first(1)
------
AopService second()
------
AnnotationAop Around before
AnnotationAop Before
AopService third()
AnnotationAop AfterReturning
AnnotationAop After
AnnotationAop Around after

3.9 @DeclareParents

添加待织入的接口和实现

public interface TestService {
    void test();
}
@Service("testService")
public class TestServiceImpl implements TestService {
    @Override
    public void test() {
        System.out.println("TestServiceImpl test()");
    }
}

定义切面

@Aspect
@Component
public class IntroductionAop {
    /**
     * value 要织入的目标类,全路径,+表示及其子类
     * defaultImpl 为待织入的实现类
     */
    @DeclareParents(value = "com.shpun.service.AopService+", defaultImpl = TestServiceImpl.class)
    public TestService testService;
}

测试

@SpringBootTest
public class IntroductionAopTest {

    @Autowired
    private AopService aopService;

    @Test
    public void test() {
        aopService.first(1);
        TestService testService = (TestService) aopService;
        testService.test();
    }
}

打印结果

AopServiceImpl first(1)
TestServiceImpl test()

4. 总结

  1. 方法被多个切面环绕时,多个切面像栈一样,按后入先出的顺序执行。
  2. 后置通知(after)像finally,不管是否报错,都会执行。

参考:
Spring AOP 官方文档
Spring AOP 面向切面编程入门
Spring AOP SpringBoot集成
Spring AOP切点表达式用法总结
Spring AOP术语:连接点和切点的区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值