当前位置:   article > 正文

SpringAOP机制详解_spring框架的aop机制

spring框架的aop机制

一、AOP概述

(一)什么是AOP

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

(二)AOP编程思想

AOP面向切面编程是一种编程思想,是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

(三)Spring中的常用术语

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

(四)AOP编程底层的实现机制

1、JDK动态代理

只提供接口的代理,不支持类的代理。JDK动态代理通过反射来接受被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是invocationHandler接口和Proxy类

2、CGLB动态代理

通过继承的方式来实现动态代理,因此如果某个类被标记为final,那么他是无法使用CGLB做动态代理。

(五)AOP的作用及优势

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。

优势:减少重复代码,提高开发效率,并且便于维护。

二、Spring基于XML的AOP配置

(一)环境搭建

1、添加依赖 

  1. <properties>
  2. <spring.version>5.2.5.RELEASE</spring.version>
  3. </properties>
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.springframework</groupId>
  7. <artifactId>spring-context</artifactId>
  8. <version>${spring.version}</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.aspectj</groupId>
  12. <artifactId>aspectjweaver</artifactId>
  13. <version>1.8.7</version>
  14. </dependency>
  15. </dependencies>

2、添加代码

准备实体类
  1. public class Account implements Serializable {
  2. private Integer id;
  3. private String name;
  4. private double money;
  5. public Integer getId() {
  6. return id;
  7. }
  8. public void setId(Integer id) {
  9. this.id = id;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public double getMoney() {
  18. return money;
  19. }
  20. public void setMoney(double money) {
  21. this.money = money;
  22. }
  23. @Override
  24. public String toString() {
  25. return "Account{" +
  26. "id=" + id +
  27. ", name='" + name + '\'' +
  28. ", money=" + money +
  29. '}';
  30. }
  31. }
创建AccountDao接口以及实现类
  1. public interface AccountDao {
  2. public Account findByName(String name);
  3. public void update(Account account);
  4. }
  1. public class AccountDaoImpl implements AccountDao {
  2. private QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
  3. @Override
  4. public Account findByName(String name) {
  5. try {
  6. String sql ="select *from account where name =? ";
  7. Account account = queryRunner.query(sql, new BeanHandler<>(Account.class), name);
  8. return account;
  9. } catch (SQLException throwables) {
  10. throwables.printStackTrace();
  11. }
  12. return null;
  13. }
  14. @Override
  15. public void update(Account account) {
  16. try {
  17. String sql ="update account set money =? where name =? ";
  18. queryRunner.update(sql, account.getMoney(), account.getName());
  19. } catch (SQLException throwables) {
  20. throwables.printStackTrace();
  21. }
  22. }
  23. }
创建AccountService接口以及实现类
  1. public interface AccountService {
  2. /**
  3. * 定义业务方法, 实现转账
  4. * @param sourceAccountName 来源账户
  5. * @param targetAccountName 目标账户
  6. * @param money 转账金额
  7. */
  8. public void transfer(String sourceAccountName,String targetAccountName,double money);
  9. }
  1. public class AccountServiceImpl implements AccountService {
  2. //依赖dao层
  3. private AccountDao accountDao = new AccountDaoImpl();
  4. @Override
  5. public void transfer(String sourceAccountName, String targetAccountName,double money) {
  6. //查询来源账户和目标账户
  7. Account sAccount = accountDao.findByName(sourceAccountName);
  8. Account tAccount = accountDao.findByName(targetAccountName);
  9. //来源账户减钱,目标账户加钱
  10. sAccount.setMoney(sAccount.getMoney()-money);
  11. tAccount.setMoney(tAccount.getMoney()+money);
  12. //持久化到数据库
  13. accountDao.update(sAccount);
  14. //模拟异常发生
  15. int i=1/0;
  16. accountDao.update(tAccount);
  17. }
  18. }

3、创建配置文件并导入约束

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop.xsd">
  9. </beans>

4、配置Spring的IOC

  1. <!-- 配置数据源 -->
  2. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  3. <!--连接数据库的必备信息-->
  4. <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  5. <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
  6. </property>
  7. <property name="user" value="root"></property>
  8. <property name="password" value="root"></property>
  9. </bean>
  10. <!-- 配置Connection的工具类 ConnectionUtils -->
  11. <bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils">
  12. <!-- 注入数据源-->
  13. <property name="dataSource" ref="dataSource"></property>
  14. </bean>
  15. <!--配置queryRunner-->
  16. <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean>
  17. <!--配置accountDao-->
  18. <bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
  19. <property name="queryRunner" ref="queryRunner"></property>
  20. <property name="connectionUtils" ref="connectionUtils"></property>
  21. </bean>
  22. <!--配置accountService-->
  23. <bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
  24. <property name="accountDao" ref="accountDao"></property>
  25. </bean>

5、抽取公共代码制作成通知

  1. /**
  2. * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
  3. */
  4. public class TransactionManager {
  5. private ConnectionUtils connectionUtils;
  6. public void setConnectionUtils(ConnectionUtils connectionUtils) {
  7. this.connectionUtils = connectionUtils;
  8. }
  9. /**
  10. * 开启事务
  11. */
  12. public void beginTransaction(){
  13. try {
  14. connectionUtils.getThreadConnection().setAutoCommit(false);
  15. }catch (Exception e){
  16. e.printStackTrace();
  17. }
  18. }
  19. /**
  20. * 提交事务
  21. */
  22. public void commit(){
  23. try {
  24. connectionUtils.getThreadConnection().commit();
  25. }catch (Exception e){
  26. e.printStackTrace();
  27. }
  28. }
  29. /**
  30. * 回滚事务
  31. */
  32. public void rollback(){
  33. try {
  34. connectionUtils.getThreadConnection().rollback();
  35. }catch (Exception e){
  36. e.printStackTrace();
  37. }
  38. }
  39. /**
  40. * 释放连接
  41. */
  42. public void release(){
  43. try {
  44. connectionUtils.removeConnection();
  45. connectionUtils.getThreadConnection().close();//还回连接池中
  46. }catch (Exception e){
  47. e.printStackTrace();
  48. }
  49. }
  50. }

(二)AOP配置步骤

1、配置bean标签

  1. <!--配置通知:txManager-->
  2. <bean id="txManager" class="com.offcn.utils.TransactionManager">
  3. <property name="connectionUtils" ref="connectionUtils"></property>
  4. </bean>

2、使用aop:config 声明AOP配置

aop:config:
    作用: 开始声明aop配置

  1. <aop:config>
  2. <!-- 配置的代码都写在此处 -->
  3. </aop:config>

3、使用aop:aspect 配置切面

aop:aspect
        作用: 用于配置切面
        属性:id :给切面提供一个唯一标识。
                   ref:引用配置好的通知类 bean 的 id。

  1. <aop:aspect id="tdAdvice" ref="txManager">
  2. <!--配置通知的类型要写在此处-->
  3. </aop:aspect>

4、使用aop:pointcut配置切入点表达式

aop:pointcut
        作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
        属性: expression:用于定义切入点表达式。
                    id:用于给切入点表达式提供一个唯一标识
  1. <aop:pointcut id="point1" expression="execution( public void com.offcn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.St
  2. ring,java.lang.Double))"/>

5、使用aop:xxx配置对应的通知类型

  • aop:before

        作用:用于指定通知类中的增强方法名称

        属性:method:用于指定通知类中的增强方法名称

                  ponitcut-ref:用于指定切入点的表达式的引用

                  poinitcut:用于指定切入点表达式
        执行时间点:切入点方法执行之前执行
<aop:before method="beginTransaction" pointcut-ref="point1"></aop:before>

  • aop:after-returning

        作用:用于配置后置通知

        属性:method:指定通知中方法的名称

                   pointct:定义切入点表达式

                   pointcut-ref:指定切入点表达式的引用

        执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行。

<aop:after-returning method="commit" pointcut-ref="point1"/>
  • aop:after-throwing

        作用:用于配置异常通知

        属性:method:指定通知中方法的名称

                   pointct:定义切入点表达式

                   pointcut-ref:指定切入点表达式的引用

        执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个。

<aop:after-throwing method="rollback" pointcut-ref="point1"/>

  • aop:after

        作用:用于配置最终通知

        属性:method:指定通知中方法的名称

                   pointct:定义切入点表达式

                   pointcut-ref:指定切入点表达式的引用

        执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。

<aop:after method="release" pointcut-ref="point1"/>

(三)切入点表达式说明

切入点表达式的语法

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

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

(四)环绕通知配置事务管理

在TransactionManager类当中添加方法

  1. /**
  2. * 环绕通知:
  3. * spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参
  4. 数。
  5. * 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
  6. * @param pjp
  7. * @return
  8. */
  9. public Object transactionAround(ProceedingJoinPoint pjp) {
  10. //定义返回值
  11. Object returnValue = null;
  12. try {
  13. //获取方法执行所需的参数
  14. Object[] args = pjp.getArgs();
  15. //前置通知:开启事务
  16. beginTransaction();
  17. //执行方法
  18. returnValue = pjp.proceed(args);
  19. //后置通知:提交事务
  20. commit();
  21. }catch(Throwable e) {
  22. //异常通知:回滚事务
  23. rollback();
  24. e.printStackTrace();
  25. }finally {
  26. //最终通知:释放资源
  27. release();
  28. }
  29. return returnValue;
  30. }

aop:around:

        作用:用于配置环绕通知

        属性:method:指定通知中方法的名称

                   pointct:定义切入点表达式

                   pointcut-ref:指定切入点表达式的引用

        说明:它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。

        注意:通常情况下,环绕通知都是独立使用的。
  1. <aop:config>
  2. <aop:aspect id="tdAdvice" ref="txManager">
  3. <aop:pointcut id="point1" expression="execution(*com.offcn.service.impl.*.*(..))"/>
  4. <!-- 配置环绕通知 -->
  5. <aop:around method="transactionAround" pointcut-ref="point1"/>
  6. </aop:aspect>
  7. </aop:config>

三、Spring基于注解的AOP配置

(一)环境搭建

1、构建maven工程添加AOP注解的相关依赖

  1. <properties>
  2. <spring.version>5.2.5.RELEASE</spring.version>
  3. </properties>
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.springframework</groupId>
  7. <artifactId>spring-context</artifactId>
  8. <version>${spring.version}</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.aspectj</groupId>
  12. <artifactId>aspectjweaver</artifactId>
  13. <version>1.8.7</version>
  14. </dependency>
  15. </dependencies>

2、导入代码

沿用上一章节资源

copy Account AccountDao AccountDaoImpl AccountService AccountServiceImpl

3、在配置文件中导入context的名称空间且配置

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:aop="http://www.springframework.org/schema/aop"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/aop
  9. http://www.springframework.org/schema/aop/spring-aop.xsd
  10. http://www.springframework.org/schema/context
  11. http://www.springframework.org/schema/context/spring-context.xsd">
  12. </beans>

4、资源使用注解配置

  1. <!-- 配置数据源 -->
  2. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  3. <!--连接数据库的必备信息-->
  4. <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  5. <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
  6. </property>
  7. <property name="user" value="root"></property>
  8. <property name="password" value="root"></property>
  9. </bean>
  10. <!--配置queryRunner-->
  11. <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
  12. </bean>

5、在配置文件中指定spring要扫描的包

  1. <!-- 告知 spring,在创建容器时要扫描的包 -->
  2. <context:component-scan base-package="com.offcn"></context:component-scan>

(二)配置步骤

1、通知类使用注解配置

  1. /**
  2. * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
  3. */
  4. @Component("txManager")
  5. public class TransactionManager {
  6. @Autowired
  7. private ConnectionUtils connectionUtils;
  8. }

2、通知类使用@Aspect注解声明为切面

  1. /**
  2. * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
  3. */
  4. @Component("txManager")
  5. @Aspect //表明当前类是一个切面类
  6. public class TransactionManager {
  7. @Autowired
  8. private ConnectionUtils connectionUtils;
  9. }

3、增强方法上使用注解配置通知

  • @Before

        作用:把当前方法看成是前置通知

        属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。

  1. //开启事务
  2. @Before("execution(* com.offcn.service.impl.*.*(..)))")
  3. public void beginTransaction(){
  4. try {
  5. System.out.println("before..........................");
  6. connectionUtils.getThreadConnection().setAutoCommit(false);
  7. }catch (Exception e){
  8. e.printStackTrace();
  9. }
  10. }

  • @AfterReturning

        作用:把当前方法看成是后置通知。

        属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用

  1. // 提交事务
  2. @AfterReturning("execution(* com.offcn.service.impl.*.*(..)))")
  3. public void commit(){
  4. try {
  5. connectionUtils.getThreadConnection().commit();
  6. }catch (Exception e){
  7. e.printStackTrace();
  8. }
  9. }

  • @AfterThrowing
        作用:把当前方法看成是异常通知。
        属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用
  1. //回滚事务
  2. @AfterThrowing("execution(* com.offcn.service.impl.*.*(..)))")
  3. public void rollback(){
  4. try {
  5. connectionUtils.getThreadConnection().rollback();
  6. }catch (Exception e){
  7. e.printStackTrace();
  8. }
  9. }

  • @After
        作用把当前方法看成是最终通知。
        
        属性value :用于指定切入点表达式,还可以指定切入点表达式的引用
  1. //释放连接
  2. @After("execution(* com.offcn.service.impl.*.*(..)))")
  3. public void release(){
  4. try {
  5. connectionUtils.removeConnection();
  6. connectionUtils.getThreadConnection().close();//还回连接池中
  7. }catch (Exception e){
  8. e.printStackTrace();
  9. }
  10. }

4、开启Spring对注解AOP的支持

  1. <!-- 开启 spring 对注解 AOP 的支持 -->
  2. <aop:aspectj-autoproxy/>

5、环绕通知注解配置

@Around

作用把当前方法看成是环绕通知。

属性value:用于指定切入点表达式,还可以指定切入点表达式的引用。

  1. // 环绕通知:
  2. @Around("execution(*com.offcn.service.impl.*.*(..))")
  3. public Object transactionAround(ProceedingJoinPoint pjp) {
  4. //定义返回值
  5. Object returnValue = null;
  6. try {
  7. //获取方法执行所需的参数
  8. Object[] args = pjp.getArgs();
  9. //前置通知:开启事务
  10. beginTransaction();
  11. //执行方法
  12. returnValue = pjp.proceed(args);
  13. //后置通知:提交事务
  14. commit();
  15. }catch(Throwable e) {
  16. //异常通知:回滚事务
  17. rollback();
  18. e.printStackTrace();
  19. }finally {
  20. //最终通知:释放资源
  21. release();
  22. }
  23. return returnValue;
  24. }

6、切入点表达式注解

  1. @Pointcut("execution(* com.offcn.service.impl.*.*(..))")
  2. private void point1() {}
  3. // 环绕通知:
  4. @Around("point1()")///注意:千万别忘了写括号
  5. public Object transactionAround(ProceedingJoinPoint pjp) {
  6. //定义返回值
  7. Object returnValue = null;
  8. try {
  9. //获取方法执行所需的参数
  10. Object[] args = pjp.getArgs();
  11. //前置通知:开启事务
  12. beginTransaction();
  13. //执行方法
  14. returnValue = pjp.proceed(args);
  15. //后置通知:提交事务
  16. commit();
  17. }catch(Throwable e) {
  18. //异常通知:回滚事务
  19. rollback();
  20. e.printStackTrace();
  21. }finally {
  22. //最终通知:释放资源
  23. release();
  24. }
  25. return returnValue;
  26. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/86844
推荐阅读
相关标签
  

闽ICP备14008679号