Skip to content

Spring教程 - 8 面向切面编程AOP - 基于XML配置

下面介绍 Spring 基于XML的配置方式来实现 AOP,这种方式现在的项目已经很少用了。

你要是赶时间,你跳过也行。不赶时间就了解一下。


8.1 AOP的使用

新建一个空项目,从空maven项目开始。

1 引入依赖

首先在项目的 pom.xml 中,引入 AOP 相关的依赖:

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.foooor</groupId>
    <artifactId>hello-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <dependencies>
        <!-- spring核心依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.2.9</version>
        </dependency>

        <!-- 1.AOP相关 引入spring aop依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.2.9</version>
        </dependency>

        <!-- 2.AOP相关 引入spring aspects依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.2.9</version>
        </dependency>


        <!-- junit5依赖 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.13.4</version>
        </dependency>

        <!-- logback依赖,日志框架实现 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.18</version>
        </dependency>

        <!-- slf4j依赖,日志框架接口门面 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.17</version>
        </dependency>

    </dependencies>
</project>

2 创建业务类

还是前面介绍的计算用的业务类。

首先定义接口:

ICalculatorService.java

java
package com.foooor.hellospring.service;

public interface ICalculatorService {
    /**
     * 加法
     */
    int add(int a, int b);
    
    /**
     * 减法
     */
    int sub(int a, int b);
    
    /**
     * 乘法
     */
    int mul(int a, int b);
    
    /**
     * 除法
     */
    int div(int a, int b);
}

然后定义实现类:

CalculatorServiceImpl.java

java
package com.foooor.hellospring.service.impl;

import com.foooor.hellospring.service.ICalculatorService;

public class CalculatorServiceImpl implements ICalculatorService {

    /**
     * 加法
     */
    public int add(int a, int b) {
        int result = a + b;
        return result;
    }

    /**
     * 减法
     */
    public int sub(int a, int b) {
        int result = a - b;
        return result;
    }

    /**
     * 乘法
     */
    public int mul(int a, int b) {
        int result = a * b;
        return result;
    }

    /**
     * 除法
     */
    public int div(int a, int b) {
        int result = a / b;
        return result;
    }
}
  • 类上的 Spring 注解都去掉了。

3 创建切面类

不像注解方式,不需要 @Aspect@Component。去掉注解其他都一样:

java
package com.foooor.hellospring.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;

/**
 * 日志切面
 */
public class LogAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 前置通知:方法执行前
     */
    public void beforeAdvice(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        Object[] args = jp.getArgs();
        logger.info("[Before] 调用方法: {}, 参数: {}", methodName, Arrays.toString(args));
    }

    /**
     * 后置通知:方法执行后(无论是否异常)
     */
    public void afterAdvice(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        logger.info("[After] 方法执行完成: {}", methodName);
    }

    /**
     * 返回通知:方法正常返回后执行
     */
    public void afterReturningAdvice(JoinPoint jp, Object result) {
        String methodName = jp.getSignature().getName();
        logger.info("[AfterReturning] 方法 {} 返回值: {}", methodName, result);
    }

    /**
     * 异常通知:方法抛出异常后执行
     */
    public void afterThrowingAdvice(JoinPoint jp, Throwable ex) {
        String methodName = jp.getSignature().getName();
        logger.error("[AfterThrowing] 方法 {} 异常: {}", methodName, ex.getMessage(), ex);
    }

    /**
     * 环绕通知:可控制方法执行前后,以及返回值和异常
     */
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();

        logger.info("[Around-Before] 调用方法: {}, 参数: {}", methodName, Arrays.toString(args));

        Object result = null;
        try {
            // 执行目标方法
            result = pjp.proceed();
            logger.info("[Around-AfterReturning] 方法 {} 返回值: {}", methodName, result);
        } catch (Throwable ex) {
            logger.error("[Around-AfterThrowing] 方法 {} 出现异常: {}", methodName, ex.getMessage(), ex);
            throw ex; // 必须抛出异常,否则目标方法异常会被吞掉
        } finally {
            logger.info("[Around-Finally] 方法 {} 执行完成", methodName);
        }

        return result;
    }
}

4 创建Spring配置文件

在项目的 resources 目录下创建 bean.xml,名称自定义,测试的时候加载这个xml就可以。

xml
<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置业务类 -->
    <bean id="calculatorService" class="com.foooor.hellospring.service.impl.CalculatorServiceImpl"/>

    <!-- 日志切面 -->
    <bean id="logAspect" class="com.foooor.hellospring.aspect.LogAspect"/>

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

    <!-- AOP 配置 -->
    <aop:config>
        <!-- 定义切入点表达式 -->
        <aop:pointcut id="serviceMethods"
                      expression="execution(* com.foooor.hellospring.service.ICalculatorService.*(..))"/>

        <!-- 定义切面 -->
        <aop:aspect ref="logAspect">

            <!-- 前置通知,需要指定切面中的 通知 和 切入点 -->
            <aop:before method="beforeAdvice" pointcut-ref="serviceMethods"/>

            <!-- 后置通知 (finally 一定执行) -->
            <aop:after method="afterAdvice" pointcut-ref="serviceMethods"/>

            <!-- 返回通知 -->
            <aop:after-returning method="afterReturningAdvice"
                                 pointcut-ref="serviceMethods"
                                 returning="result"/>

            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrowingAdvice"
                                pointcut-ref="serviceMethods"
                                throwing="ex"/>

            <!-- 环绕通知 -->
            <aop:around method="aroundAdvice" pointcut-ref="serviceMethods"/>
        </aop:aspect>
    </aop:config>

</beans>
  • 需要配置业务类、切面类交给 Spring 来管理;
  • 需要开启 AOP 切面,并对 AOP 进行配置;
  • 返回通知的returning="result" 和异常通知的 throwing="ex" 参数名必须与切面类中的对应的方法参数名一致。
  • aroundAdvice 必须有 ProceedingJoinPoint 参数并返回 Object
  • XML 配置方式下,复用切入点要通过 <aop:pointcut id="xxx" expression="..."/> 来定义。

5 测试

创建测试类测试即可:

java
package com.foooor.hellospring;

import com.foooor.hellospring.service.ICalculatorService;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AdviceTest {
    // 创建日志对象
    private static final Logger logger = LoggerFactory.getLogger(AdviceTest.class);

    @Test
    public void testUserService() {
        // 加载配置类
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        // 获取bean
        ICalculatorService userController = context.getBean(ICalculatorService.class);
        // 调用bean中的方法
        int result = userController.add(2, 3);
        logger.info("result:{}", result);
    }
}

整个过程,没啥特别的,过!

内容未完......