Spring企业级程序设计 • 【第3章 面向切面编程】
阅读原文时间:2023年07月08日阅读:1

全部章节   >>>>


本章目录

3.1 AOP基本概念和术语

3.1.1 AOP概念

3.1.2 AOP的术语解释

3.1.3 通知类型介绍

3.1.4 通过AOP模拟事务操作

3.1.5 实践练习

3.2  基于XML配置的AOP开发

3.2.1  使用标签定义切入点

3.2.1  使用标签定义切入点

3.2.2 使用标签来前置增强

3.2.3 使用标签来后置增强

3.2.4 使用标签处理异常

3.2.5 实践练习

3.3 基于注解的AOP开发

3.3.1 使用@Pointcut和@Around完成环绕增强

3.3.2 使用@After完成最终增强

3.3.3 使用@Before完成前置增强

3.3.4 使用@AfterReturning完成后置增强

3.3.5 使用@AfterThrowing异常拦截

3.3.6 实践练习

3.4 综合案例

3.4.1 需求说明

3.4.2 实现思路

3.4.3 实践练习

总结


3.1 AOP基本概念和术语

面向切面编程(Aspect Oriented Programming,AOP),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP是面向对象编程(Object Oriented Programming,OOP)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。常用于日志记录、性能统计、安全控制、事务处理以及异常处理等等。

AOP的关键术语:

切面(Aspect):是共有功能的实现。

连接点(Join Point):是程序在运行过程中能够插入切面的地点。

通知(Advice):即增强,是切面的具体实现。分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、环绕通知(Around)和最终通知(After)

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。

目标对象(Target):是即将切入切面的对象,即那些被通知的对象。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。

AOP为通知定义了org.asoalliance.Advice接口,Spring支持5种类型的通知:

前置通知:表示在连接点被调用前执行的通知。

后置通知:表示在某个连接点成功执行之后执行的通知。

环绕通知:表示包围一个连接点通知,在被通知的方法调用之前和之后执行自定义的方法

异常通知:表示在方法抛出异常后执行的通知。

最终通知:表示在某个连接点执行之后执行的通知。

使用AOP中的环绕通知来模拟事务操作

引入相关jar包

引入aop约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    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-4.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.2.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
        <!—此处输入内容-->
</beans>

在com.mhys.demo.service包下创建OrderService类,声明insertOrder()方法

在com.mhys.demo.advice包下创建TransactionAdvice类,声明around()方法和after()方法

在applicationContext.xml配置文件中配置目标对象、通知对象和切入点

    <!-- 配置目标对象 -->
    <bean id="orderService" class="com.mhys.demo.service.OrderService"></bean>
    <!-- 配置通知对象 -->
    <bean id="transactionAdvice" class="com.mhys.demo.advice.TransactionAdvice"></bean>
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.mhys.demo.service.*Service.*(..))" id="pointCut"/>
        <aop:aspect ref="transactionAdvice">
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointCut"/>
            <!-- 最终通知 -->
            <aop:after method="after" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

在com.mhys.demo.test包下编写测试类

3.2  基于XML配置的AOP开发

切入点是指哪些方法需要被执行“AOP”,是由“Pointcut Expression”来描述的。

Expression常用方法有方法参数匹配、方法描述匹配、目标类匹配等,其中最常用的是方法描述匹配。

语法:

execution(<修饰符模式>?<返回类型模式><声明类型模式>?<方法名模式>(<参数模式>)<异常模式>?)

6种使用execution()表达式实例。:

execution(public void com.mhys.demo.UserServiceImpl.save())

execution(void com.mhys.demo.UserServiceImpl.save())表达式

execution(* com.mhys.demo.UserServiceImpl.save())表达式

execution(* com.mhys.demo.UserServiceImpl.*())表达式

execution(* com.mhys.demo.*ServiceImpl.*(..))表达式

execution(* com.mhys.demo..*ServiceImpl.*(..))表达式

示例:定义一个切入点配置某个包以及子包下的以Service结尾的类中任意返回值、任意参数的以add开头的所有方法,然后在织入最终通知

<!-- 配置目标对象 -->
    <bean id="userService" class="com.mhys.demo.user.UserService"></bean>
    <bean id="orderService" class="com.mhys.demo.order.OrderService"></bean>
    <!-- 配置通知对象 -->
    <bean id="myAdvice" class="com.mhys.demo.advice.MyAdvice"></bean>
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.mhys.demo..*Service.add*(..))" id="pointCut"/>
        <aop:aspect ref="myAdvice">
            <!—最终通知 -->
            <aop:after method="afterAdvice" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

前置通知是在目标方法之前执行。常见的应用场景是使用前置通知可以在目标方法执行之前执行,插入系统日志。

语法:

<aop:before method="切面类的方法名" pointcut-ref="切入点表达式是引用"/>

示例:在MyAdvice通知类中,声明一个beforeAdvice()前置增强方法,然后在applicationContext.xml配置文件中使用标签织入前置通知

    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.mhys.demo..*Service.add*(..))" id="pointCut"/>
        <aop:aspect ref="myAdvice">
            <!-- 最终通知 -->
            <aop:after method="afterAdvice" pointcut-ref="pointCut"/>
            <!-- 前置通知 -->
            <aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

后置通知是在目标方法执行成功之后执行。其使用方法和前置通知类似。通过后置通知可以为目标方法添加新的逻辑代码,使业务方法增强。

语法:

<aop:after-runturning method="切面类的方法名" pointcut-ref="切入点表达式是引用"/>

示例:在MyAdvice通知类中,声明一个afterReturningAdvice()后置增强方法,然后在applicationContext.xml配置文件中使用标签织入后置通知。

    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.mhys.demo..*Service.add*(..))" id="pointCut"/>
        <aop:aspect ref="myAdvice">
            <!-- 最终通知 -->
            <aop:after method="afterAdvice" pointcut-ref="pointCut"/>
            <!-- 前置通知 -->
            <aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
            <!-- 后置通知 -->
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

异常通知是在方法抛出异常后执行的通知,它最适合的应用场景是在事务管理中。当参与事务的某个Dao发生异常时,事务管理器就必须回滚事务。

语法:

<aop:after-throwing method="切面类的方法名" pointcut-ref="切入点表达式是引用"/>

示例:

在MyAdvice通知类中,声明一个afterthrowingAdvice()异常增强方法;然后在applicationContext.xml配置文件中使用标签织入异常通知。

<aop:config>
    <!-- 配置切入点 -->
    <aop:pointcut expression="execution(* com.mhys.demo..*Service.add*(..))" id="pointCut"/>
    <aop:aspect ref="myAdvice">
        <!-- 最终通知 -->
        <aop:after method="afterAdvice" pointcut-ref="pointCut"/>
        <!-- 前置通知 -->
        <aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
        <!-- 后置通知 -->
        <aop:after-returning method="afterReturningAdvice" pointcut-ref="pointCut"/>
        <!-- 异常通知 -->
        <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointCut"/>
    </aop:aspect>
</aop:config>

3.3 基于注解的AOP开发

示例:使用@Pointcut定义切入点,@Round定义环绕增强方法。模拟实现事务的开启和提交操作。

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.mhys.demo"></context:component-scan>
    <!-- 启动AOP注解 -->
    <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>

示例:在TransactionAdvice通知类中声明一个afterAdvice()最终通知方法,然后使用@After定义最终通知。

@Component
@Aspect
// 表示该类是一个通知类
public class TransactionAdvice {
    // 原有代码省略
    @After("TransactionAdvice.pc()")
    public void afterAdvice(){
        System.out.println("关闭事务");
    }
}

新增afterAdvice()最终增强方法

示例:在TransactionAdvice通知类中声明一个beforeAdvice()前置增强方法,然后使用@Before定义前置通知。

@Component
@Aspect
// 表示该类是一个通知类
public class TransactionAdvice {
    // 代码省略
    @Before("TransactionAdvice.pc()")
    public void beforeAdvice(){
        System.out.println("执行前置通知方法!");
    }
}

新增beforeAdvice()前置增强方法

示例:在TransactionAdvice通知类中声明一个afterReturningAdvice()后置增强方法,然后使用@AfterReturning定义后置通知。

@Component
@Aspect
// 表示该类是一个通知类
public class TransactionAdvice {
    // 代码省略
    @AfterReturning("TransactionAdvice.pc()")
    public void afterReturningAdvice(){
        System.out.println("执行后置通知方法!");
    }
}

新增afterReturningAdvice()后置增强方法

示例:在TransactionAdvice通知类中声明一个afterThrowingAdvice()异常增强方法,然后使用@AfterThrowingg定义异常通知。

@Component
@Aspect
// 表示该类是一个通知类
public class TransactionAdvice {
    // 代码省略
        @AfterThrowing("TransactionAdvice.pc()")
        public void afterThrowingAdvice(){
            System.out.println("发现异常,执行异常通知方法!");
        }
}

新增afterThrowingAdvice异常增强方法

3.4 综合案例

对于CRM的系统而言,现在有很多的Dao类,比如客户的Dao,联系人的Dao等等。

客户提出一个需求,需要开发人员实现一个功能,对所有的Dao的类中的以get、find开头的方法实现日志输出打印,检测方法的性能,对所有的Dao类中的以save、add、insert、modify、update、delete以及del开头的方法实现事务的开启、提交和关闭操作。要求如下:

  • 使用前置通知和后置通知实现日志输出打印。
  • 使用环绕通知实现事务的开启和提交操作。
  • 使用最终通知实现事务的关闭操作。

需求说明解决思路:

在applicationContext.ml配置文件中开启注解扫描功能和AOP注解功能。

在com.mhys.demo.dao包下创建CustomerDao接口,声明getCustomerAll()方法、addCustomer()方法、modifyCustomer()方法和deleteCustomer()方法。 在com.mhys.demo.dao包下创建CustomerDaoImpl类,实现CustomerDao接口,实现接口4个方法。

在com.mhys.demo.dao包下创建LinkmanDao接口,声明getLinkmanAll()方法、addLinkman()方法、modifyLinkman()方法和deleteLinkman()方法。

在com.mhys.demo.dao包下创建LinkmanDaoImpl类,实现LinkmanDao接口,实现接口4个方法。

在com.mhys.demo.service包下创建CustomerService接口,声明getCustomerAll()方法、addCustomer()方法、modifyCustomer()方法和deleteCustomer()方法。

在com.mhys.demo.service包下创建CustomerServiceImpl类,实现CustomerService接口,实现接口4个方法。

com.mhys.demo.service包下创建LinkmanService接口,声明getLinkmanAll()方法、addLinkman()方法、modifyLinkman()方法和deleteLinkman()方法。

在com.mhys.demo.service包下创建LinkmanServiceImpl类,实现LinkmanService接口,实现接口4个方法。

在com.mhys.demo.advice包下创建LoggerAdvice通知类,定义切入点,声明loggerBeforeAdvice()方法作为环绕增强。

在com.mhys.demo.advice包下创建TransactionAdvice通知类,定义切入点,声明transactionBefor()方法、transactionAfterReturning()方法和transactionAfter()方法。

在com.mhys.demo.test包下编写测试类。

总结

Spring AOP是OOP的补充,它也提供了模块化。在面向对象编程中,关键的单元是对象,AOP的关键单元是切面。

通知(advice)是在程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型:前置通知、后置通知(After Retuning Advice)、异常拦截通知(After Throwing Advice)、最终通知(After Advice)、围绕通知(Around Advice)