Loading

Spring AOP 面向切面编程

初识 AOP

什么是AoP

AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程
AOP 是 OOP (面向对象编程) 的延,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合降低,提高程序的可重用性,同时提高了开发的效率。
这样做的好处是:

  1. 在程序运行期间,在不修改源码的情况下对方法进行功能增强
  2. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
  3. 减少重复代码,提高开发效率,便于后期维护

AOP底层实现

实际上,AOP的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间, Spring 通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

AOP 相关概念

Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
AOP 常用的术语如下:

  • Target(目标对象):代理的目标对象。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  • Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在 Spring中,这些点指的是方法,因为 Spring 只支持方法类型的连接点。
  • PoIntcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
  • Advice(通知/增强):所谓通知是指拦馘到 Joinpoint之后所要做的事情就是通知
    分类:前置通知、后置通知、异常通知、最终通知、环绕通知。
  • Aspect(切面):是切入点和通知(引介)的结合。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。 Spring 采用动态代理织入,而 Aspect 采用编译期织入和类装载期织入。

基于 XML 的 AOP 开发

Maven 相关依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.10</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.10</version>
</dependency>
<!-- aspect织入(切点表达式需要用到该jar包) -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

创建主体方法和接口

public interface AccountService {
    public void transfer();
}

public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer() {
        System.out.println("开始转账");
    }
}

创建通知类及方法

package com.orginly.advice;

public class TransferAdvice {
    public void before() {
        System.out.println("前置通知执行了");
    }

    public void afterReturning() {
        System.out.println("后置通知执行了");
    }
}

将目标类和通知类的创建权交给 Spring

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 将目标类交给 IOC 容器 -->
    <bean id="accountService" class="com.orginly.service.impl.AccountServiceImpl"/>
    <!-- 将通知类交给 IOC 容器 -->
    <bean id="transferAdvice" class="com.orginly.advice.TransferAdvice"/>
</beans>

配置 AOP 前置增强

<!-- 将目标类交给 IOC 容器 -->
<bean id="accountService" class="com.orginly.service.impl.AccountServiceImpl"/>
<!-- 将通知类交给 IOC 容器 -->
<bean id="transferAdvice" class="com.orginly.advice.TransferAdvice"/>

<!-- AOP 配置 -->
<aop:config>
    <!-- 配置切面:切入点 + 通知 -->
    <aop:aspect ref="transferAdvice">
        <!-- 前置通知 pointcut:切点表达式 -->
        <aop:before method="before" pointcut="execution(public void com.orginly.service.impl.AccountServiceImpl.transfer())"/>
        <!-- 后置通知 -->
        <aop:after-returning method="afterReturning" pointcut="execution(public void com.orginly.service.impl.AccountServiceImpl.transfer())"/>
    </aop:aspect>
</aop:config>

XML 配置 AOP 详解

切点表达式

表达式语法:

execution([修饰符]返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
  • 包名与类名之间一个点,代表当前包下的类,两个点 .. 表示当的包及其子包下的类
  • 参数列表可以使用两个点..表示任意个数,任意类型的参数列表

例如:

execution(public void com.orginly.service.impl.AccountServiceImpl.transfer());

execution(public void com.orginly.service.impl.AccountServiceImpl.transfer(java.lang.String));

execution(void com.orginly.service.impl.AccountServiceImpl.*(..));

execution(* com.orginly.service.impl.*.*(..));

execution(* com.orginly.service..*.*(..));

切点表达式抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

<!-- AOP 配置 -->
<aop:config>
    <!-- 配置切面:切入点 + 通知 -->
    <aop:aspect ref="transferAdvice">
        <!-- 切点表达式 -->
        <aop:pointcut id="acService" expression="execution(public void com.orginly.service.impl.AccountServiceImpl.*())"/>
        <!-- 前置通知 pointcut:切点表达式 -->
        <aop:before method="before" pointcut-ref="acService"/>
        <!-- 后置通知 -->
        <aop:after-returning method="afterReturning" pointcut-ref="acService" />
    </aop:aspect>
</aop:config>

通知类型详解

通知的配置语法:

<aop:通知类型 method="通知类中方法名" pointcut=“切点表达式"></aop:通知类型>
名称 标签 说明
前置通知 < aop:before > 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 < aop:afterReturning > 用于配置后置通知。指定增强的方法在切入点方法之后执行
异常通知 < aop:afterThrowing > 用于配置异常通知。指定增强的方法出现异常后执行
最终通知 < aop:after > 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行
环绕通知 < aop:around > 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行

注意:通常情况下,环绕通知都是独立使用的

环绕通知

<aop:config>
    <!-- 配置切面:切入点 + 通知 -->
    <aop:aspect ref="transferAdvice">
        <!-- 切点表达式 -->
        <aop:pointcut id="acService" expression="execution(public void com.orginly.service.impl.AccountServiceImpl.*())"/>
        <aop:around method="around" pointcut-ref="acService"/>
    </aop:aspect>
</aop:config>

注意:如果只配置 XML 是不生效的,需要在环绕通知方法中进行调用

// ProceedingJoinPoint 正在执行的连接点:切点
public Object around(ProceedingJoinPoint pjp) {
    Object proceed = null;
    try {
        System.out.println("前置通知...");
        // 切点方法执行
        proceed = pjp.proceed();
        System.out.println("后置通知...");
    } catch (Throwable e) {
        e.printStackTrace();
        System.out.println("异常通知...");
    } finally {
        System.out.println("最终通知...");
    }
    return proceed;
}

基于注解 AOP 开发

创建主体方法和接口

public interface AccountService {
    public void transfer();
}

@Service
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer() {
        System.out.println("开始转账");
    }
}

通知类升级为切面类

**在通知类中添加 @Aspect 升级为切面类

@Component
@Aspect // 升级为切面类
public class TransferAdvice {

    // 配置切入点和通知的关系
    @Before("execution(* com.orginly.service.impl.AccountServiceImpl.*(..))")
    public void before() {
        System.out.println("前置通知执行了");
    }

开启 AOP 自动代理

<!-- 开启 IOC 注解扫描 -->
<context:component-scan base-package="com.orginly"/>

<!-- 开启 AOP 自动代理 -->
<aop:aspectj-autoproxy />

如果是纯注解模式可以在核心配置类增加

@EnableAspectJAutoProxy  // 开启 AOP 自动代理

抽取切点表达式

// 抽取切点表达式
@Pointcut("execution(* com.orginly.service.impl.AccountServiceImpl.*(..))")
private void myPointcut() {}

// 配置切入点和通知的关系
@Before("myPointcut()")
public void before() {
    System.out.println("前置通知执行了");
}

通知类型

通知的配置语法:@诵知注解("切点表达式")

名称 标签 说明
前置通知 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行
异常通知 @AfterThrowing 用于配置异常通知。指定增强的方法出现异常后执行
最终通知 @After 用于配置最终通知。无论切入点方法执行时是否有异常,都会执行
环绕通知 @Around 用于配置环绕通知。开发者可以手动控制增强代码在什么时候执行
注意:
当前四个通知组合在一起时,执行顺序如下:

@Before → @Ater → @After → Returning(如果有异常:@AfterThrowing)

posted @ 2021-09-28 15:11  白日醒梦  阅读(59)  评论(0编辑  收藏  举报