spring中基于xml的aop配置

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

AOP中的术语

AOP 领域中的特性术语:

  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 切面(Aspect): 切面是通知和切点的结合。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

概念看起来总是有点懵,并且上述术语,不同的参考书籍上翻译还不一样,所以需要慢慢在应用中理解。

简单的案例

需要一个方法记录日志

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 用于记录日志的工具类,提供了公共的代码
*/
public class Logger {

/**
* 用于打印日志:计划让其在切入点方法之前执行(切入点方法就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的printLog方法开始记录日志了。。。");
}
}

业务层中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 账户的业务层实现类
*/
public class AccountService implements IAccountService {

/**
* 模拟保存账户
*/
@Override
public void saveAccount() {
System.out.println("执行了保存");
}

/**
* 模拟更新账户
*/
@Override
public void updateAccount(int i) {
System.out.println("执行了更新" + i);
}

/**
* 模拟删除账户
*/
@Override
public void deleteAccount() {
System.out.println("执行了删除");
}
}

spring中基于xml的aop配置步骤

  1. 导入aspectjweaver 依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    </dependency>
  2. 把通知的bean也交给spring来管理

    1
    2
    <!--配置spring的ioc,把service对象配置进来-->
    <bean id="accountService" class="com.xzxj.service.impl.AccountService"/>
  3. 使用 <aop:config> 标签表明开始aop的配置

  4. 使用 <aop:aspect> 标签表明配置切面

    • id:给切面提供一个唯一标识
    • ref:指定通知类bean的id
  5. <aop:aspect> 标签的内部使用对应的标签来配置通知的类型

    printLog() 方法在切入点方法执行之前执行

    • <aop:before>:表示配置前置通知
    • method:用于指定logger类中哪个方法是前置通知
    • pointcut:用于指定切入点表达式,指的是对业务层中的哪些方法增强
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--配置Logger类-->
    <bean id="logger" class="com.xzxj.utils.Logger"/>

    <!--配置aop-->
    <aop:config>
    <!--配置切面-->
    <aop:aspect id="logAdvice" ref="logger">
    <!--配置通知类型且建立通知方法和切入点方法的关联-->
    <aop:before method="printLog"
    pointcut="execution(* com.xzxj.service.impl.*.*(..))"/>
    </aop:aspect>
    </aop:config>

测试方法

1
2
3
4
5
6
7
8
9
10
11
12
public class AOPTest {
public static void main(String[] args) {
// 1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2.获取对象
IAccountService service = ac.getBean("accountService", IAccountService.class);
// 3.执行方法
service.saveAccount();
service.updateAccount(1);
service.deleteAccount();
}
}

输出结果:

Logger类中的printLog方法开始记录日志了。。。
执行了保存
Logger类中的printLog方法开始记录日志了。。。
执行了更新1
Logger类中的printLog方法开始记录日志了。。。
执行了删除

可以看到 AccountService 中的方法都被增强了,但并没有修改类中的任何方法,也没有修改测试类的方法。几乎是完全无侵入式地实现了需求。这就是 AOP 的“神奇”之处。

切入点表达式的写法

  • 关键字:execution(表达式)

  • 表达式

    访问修饰符 返回值 全限定类名.方法名(参数列表)

  • 标准的表达式写法

    public void com.xzxj.service.impl.AccountService.saveAccount()

  • 访问修饰符可以省略

    void com.xzxj.service.impl.AccountService.saveAccount()

  • 返回值可以使用通配符,表示任意返回值

    com.xzxj.service.impl.AccountService.saveAccount()

  • 包名可以使用通配符,表示任意包。但是有几级包,就要写几个 *

    * *.*.*.*.AccountService.saveAccount()

  • 包名可以使用..表示当前包及其子包

    * *..AccountService.saveAccount()

  • 类名和方法名都可以使用来实现通配

    * *..*.*()

  • 参数列表:

    • 可以直接写数据类型
      • 基本类型直接写名称 int
      • 引用类型写全限定类名 java.lang.String
    • 可以使用通配符表示任意类型,但是必须有参数
    • 可以使用 .. 表示有无参数均可,有参数可以是任意类型
  • 全通配写法:

    * *..*.*(..)

  • 实际开发中切入点表达式的通常写法:

    切到业务层实现类下的所有方法

    * com.xzxj.service.impl.*.*(..)

通知类型

applictionContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--配置aop-->
<aop:config>
<!--配置切入点表达式 -->
<aop:pointcut id="pt1" expression="execution(* com.xzxj.service.impl.*.*(..))"/>

<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"/>

<!--后置通知:在切入点方法执行之后执行。和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>

<!--异常通知:在切入点方法执行产生异常之后执行。和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>

<!--最终通知:无论切入点方法是否正常执行他都会在最后执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"/>

<!--配置环绕通知 详细注释看Logger类中-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>

环绕通知

问题:

配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。

分析:

通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。

解决:

spring框架提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),该方法就相当于明确调用切入点方法。

该接口可以作为环绕通知的方法参数,在程序执行了,spring框架会提供该接口的实现类供我们使用。

spring中的环绕通知:

是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。

  • Copyrights © 2020-2023 夕子学姐
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信