Spring中基于XML的AOP配置

目录

实现目的

在业务层方法
执行之前(前置通知)、
执行之后(后置通知)、
产生异常(异常通知)、
最后(最终通知)
执行必须要的方法。

业务层实现类:
AccountServiceImpl.java:

public class AccountServiceImpl implements AccountService {
    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }

    @Override
    public void updateAccount(int id) {
        System.out.println("执行了更新");
    }

    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

实现方式一:

通知
Logger.java

public class Logger {

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

    /**
     * 后置通知
     */
    public void afterReturnPrintLog() {
        System.out.println("后置通知....logger类中的pringLog方法开始记录日志了。。。。。。");
    }

    /**
     * 异常通知
     */
    public void afterThrowingPrintLog() {
        System.out.println("异常通知....logger类中的pringLog方法开始记录日志了。。。。。。");
    }

    /**
     * 最终通知
     */
    public void afterPrintLog() {
        System.out.println("最终通知....logger类中的pringLog方法开始记录日志了。。。。。。");
    }

配置 Spring 配置文件

第一步:配置通知

<!--配置Logger类(通知)-->
<bean id="logger" class="com.hxh.utils.Logger"></bean>

第二步:使用 aop:config 声明开始 aop 的配置

第三步:使用 aop:aspect 配置切面

属性:

  • id:给切面提供一个唯一标识。
  • ref:引用配置好的通知类 bean 的 id。

第四步:使用 aop:pointcut 配置切入点表达式

切入点表达式,就是指定对哪些类的哪些方法进行增强。

属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识

标签位置:
写在 aop:aspect 内部,只能当前切面引用。
写在 aop:aspect 外部,就可以被所有切面引用了。必须放置在 aop:aspect 标签之前,aop:config之内。

第五步:配置对应的通知类型

通知类型

标签 属性 作用
aop:before method:用于指定通知类中的增强方法名称。ponitcut-ref:用于指定切入点的表达式的引用。poinitcut:用于指定切入点表达式 。 配置前置通知。指定增强的方法在切入点方法之前执行
aop:after-returning method: 指定通知中方法的名称。 pointct: 定义切入点表达式 pointcut-ref: 指定切入点表达式的引用 配置后置通知,切入点方法正常执行之后。它和异常通知只能有一个执行
aop:after-throwing method: 指定通知中方法的名称。 pointct: 定义切入点表达式 pointcut-ref: 指定切入点表达式的引用 配置异常通知。切入点方法执行产生异常后执行。它和后置通知只能执行一个
aop:after method: 指定通知中方法的名称。 pointct: 定义切入点表达式。pointcut-ref: 指定切入点表达式的引用 配置最终通知。无论切入点方法执行时是否有异常,它都会在其后面执行。

切入点表达式的写法:

关键字:execution(表达式)

标准的表达式写法:

execution([修饰符] 返回值类型 包名.类名.方法名(参数))

比如:

public void com.hxh.service.impl.AccountServiceImpl.saveAccount()

访问修饰符可以省略:

void com.hxh.service.impl.AccountServiceImpl.saveAccount()

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

* com.hxh.service.impl.AccountServiceImpl.saveAccount()

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

* *.*.*.*.AccountServiceImpl.saveAccount()

包名可以使用 *..表示当前包和子包

* *..AccountServiceImpl.saveAccount()

类名和方法名都可以使用通配符 *

* *..*.*() 无参数函数增强

参数列表

可以直接写数据类型:基本类型直接写名称 比如:int

* *..*.*(int)

引用类型写 包名.类名 比如:java.lang.String

* *..*.*(java.lang.String)

可以使用通配符 *表示任意类型,但是必须有参数:

* *..*.*(*)

可以使用 ..表示有无参数均可,有参数可以是任意类型

* *..*.*(..)

全通配写法表达式写法:

* *..*.*(..)

实际开发中,切入点表达式的通常写法:切到业务层实现下的所有实现类
比如:

* com.hxh.service.impl.*.*(..)

最终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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置Spring的IOC,把Service对象配置进去-->
    <bean id="accountService" class="com.hxh.service.impl.AccountServiceImpl"></bean>

    <!--配置Logger类(通知)-->
    <bean id="logger" class="com.hxh.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
    	<!--配置切入点表达式-->
    	<aop:pointcut id="ptl" expression="execution(* com.hxh.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面(切入点和通知结合)-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,且建立通知方法和切入点方法的关联-->
            <!--配置前置通知:在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut-ref="ptl"></aop:before>
            <!--配置后置通知:在切入点方法执行之后执行。它和异常通知永远只能执行一个-->
            <aop:after-returning method="afterReturnPrintLog" pointcut-ref="ptl"></aop:after-returning>
            <!--配置异常通知:在切入点方法产生异常之后执行。它和后置通知永远只能执行一个-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="ptl"></aop:after-throwing>
            <!--配置最终通知:无论切入点方法发是否正常执行它都会在其后面执行-->
            <aop:after method="afterPrintLog" pointcut-ref="ptl"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

实现方式二

使用环绕通知:
Spring中基于XML的AOP配置
通知类:
Logger.java:

/**
 * @description: 用于记录日志的工具类,提供公共代码
 **/
public class Logger {
    /**
     * 环绕通知
     * 问题:
     *      当配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理中的环绕通知有明确的业务层切入点方法调用,而我们代码中没有,所以出现如上问题。
     * 解决:
     *      Spring 框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法 prioceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架会为我们提供该接口的实现类供我们使用
     *
     * Spring 中的环绕通知:
     *      它是 Spring 框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    public Object arountPringLog(ProceedingJoinPoint pjp) {
        Object returnValue = null;
        try {
            Object[] args = pjp.getArgs(); // 获取方法执行所需的参数

            // 前置通知
            System.out.println("环绕通知....logger类中的arountPringLog方法开始记录日志了");

            pjp.proceed(args); // 明确调用业务层方法(切入点方法)

            // 后置通知
            System.out.println("环绕通知....logger类中的arountPringLog方法开始记录日志了");

            return returnValue;
        } catch (Throwable t) {
            // 异常通知
            System.out.println("环绕通知....logger类中的arountPringLog方法开始记录日志了");
            throw new RuntimeException(t);
        } finally {
            // 最后通知
            System.out.println("环绕通知....logger类中的arountPringLog方法开始记录日志了");
        }
    }
}

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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置Spring的IOC,把Service对象配置进去-->
    <bean id="accountService" class="com.hxh.service.impl.AccountServiceImpl"></bean>
    
    <!--Spring中基于xml的AOP配置步骤-->

    <!--配置Logger类(通知)-->
    <bean id="logger" class="com.hxh.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="ptl" expression="execution(* com.hxh.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面(切入点和通知结合)-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置环绕通知-->
            <aop:around method="arountPringLog" pointcut-ref="ptl"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

测试:

public static void main(String[] args) {

        // 1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2.获取对象
        AccountService as = (AccountService) ac.getBean("accountService");
        as.saveAccount();
        as.deleteAccount();
        as.updateAccount(10);
    }