AOP 概念
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

AOP 的作用及优势
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
AOP 的实现方式
使用动态代理技术 动态代理:
AOP 的具体应用
案例中问题
下面是客户的业务层实现类。我们能看出什么问题吗?
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 30 31 32 33 34 35 36 37 38 39
| 客户的业务层实现类
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; }
@Override public void saveAccount(Account account) throws SQLException { accountDao.save(account); }
@Override public void updateAccount(Account account) throws SQLException{ accountDao.update(account); }
@Override public void deleteAccount(Integer accountId) throws SQLException{ accountDao.delete(accountId); }
@Override public Account findAccountById(Integer accountId) throws SQLException { return accountDao.findById(accountId); }
@Override public List<Account> findAllAccount() throws SQLException{ return accountDao.findAll(); } }
|
问题就是:
事务被自动控制了。换言之,我们使用了 connection 对象的 setAutoCommit(true)
此方式控制事务,如果我们每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql语句,这种方式就无法实现功能了。
问题的解决
解决办法:
让业务层来控制事务的提交和回滚。(这个我们之前已经在 web 阶段讲过了)
改造后的业务层实现类:
注:此处没有使用 spring 的 IoC.
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao = new AccountDaoImpl();
@Override public void saveAccount(Account account) { try { TransactionManager.beginTransaction(); accountDao.save(account); TransactionManager.commit(); } catch (Exception e) { TransactionManager.rollback(); e.printStackTrace(); }finally { TransactionManager.release(); } }
@Override public void updateAccount(Account account) { try { TransactionManager.beginTransaction(); accountDao.update(account); TransactionManager.commit(); } catch (Exception e) { TransactionManager.rollback(); e.printStackTrace(); }finally { TransactionManager.release(); } }
@Override public void deleteAccount(Integer accountId) { try { TransactionManager.beginTransaction(); accountDao.delete(accountId); TransactionManager.commit(); } catch (Exception e) { TransactionManager.rollback(); e.printStackTrace(); }finally { TransactionManager.release(); } }
@Override public Account findAccountById(Integer accountId) { Account account = null; try { TransactionManager.beginTransaction(); account = accountDao.findById(accountId); TransactionManager.commit(); return account; } catch (Exception e) { TransactionManager.rollback(); e.printStackTrace(); }finally { TransactionManager.release(); } return null; }
@Override public List<Account> findAllAccount() { List<Account> accounts = null; try { TransactionManager.beginTransaction(); accounts = accountDao.findAll(); TransactionManager.commit(); return accounts; } catch (Exception e) { TransactionManager.rollback(); e.printStackTrace(); }finally { TransactionManager.release(); } return null; }
@Override public void transfer(String sourceName, String targetName, Float money) { try { TransactionManager.beginTransaction(); Account source = accountDao.findByName(sourceName); Account target = accountDao.findByName(targetName); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); accountDao.update(source); int i=1/0; accountDao.update(target); TransactionManager.commit(); } catch (Exception e) { TransactionManager.rollback(); e.printStackTrace(); }finally { TransactionManager.release(); } } }
TransactionManager 类的代码:
public class TransactionManager {
private static DBAssit dbAssit = new DBAssit(C3P0Utils.getDataSource(),true);
public static void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } }
public static void commit() { try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } }
public static void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } }
public static void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } } }
|
新的问题
上一小节的代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题:
业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。
试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。
思考:
这个问题能不能解决呢?
答案是肯定的,使用下一小节中提到的技术。
动态代理
解决案例中的问题
Spring 中 AOP 的细节
说明
我们学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能。
AOP 相关术语
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
1 2
| Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
|
1 2 3
| Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
|
1 2
| Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
|
1 2 3 4 5 6 7 8 9
| Target(目标对象): 代理的目标对象。 Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。 Aspect(切面): 是切入点和通知(引介)的结合。
|
学习 spring 中的 AOP 要明确的事
a、开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
b、运行阶段(Spring 框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
## 关于代理的选择
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
基于 XML 的 AOP 配置
示例:
我们在学习 spring 的 aop 时,采用账户转账作为示例。
并且把 spring 的 ioc 也一起应用进来。
环境搭建
待写代码~
第一步:准备必要的代码
此处包含了实体类,业务层和持久层代码。我们沿用上一章节中的代码即可。
第二步:拷贝必备的 jar 包到工程的 lib 目录

第三步:创建 spring 的配置文件并导入约束

第四步:配置 spring 的 ioc


第五步:抽取公共代码制作成通知
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
public class TransactionManager {
private DBAssit dbAssit ;
public void setDbAssit(DBAssit dbAssit) { this.dbAssit = dbAssit; }
public void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } }
public void commit() { try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } }
public void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } }
public void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } } }
|
配置步骤
第一步:把通知类用 bean 标签配置起来
1 2 3 4
| <bean id="txManager" class="com.itheima.utils.TransactionManager"> <property name="dbAssit" ref="dbAssit"></property> </bean>
|
第二步:使用 aop:config 声明 aop 配置
aop:config:
作用:用于声明开始 aop 的配置
<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>
第三步:使用 aop:aspect 配置切面
aop:aspect:
作用:
用于配置切面。
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类 bean 的 id。
<aop:aspect id="txAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
</aop:aspect>
第四步:使用 aop:pointcut 配置切入点表达式
aop:pointcut:
作用:
用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识
1 2 3 4
| <aop:pointcut expression="execution( public void com.itheima.service.impl.AccountServiceImpl.transfer( java.lang.String, java.lang.String, java.lang.Float) )" id="pt1"/>
|
第五步:使用 aop:xxx 配置对应的通知类型
aop:before
作用:
用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式
执行时间点:
切入点方法执行之前执行
1
| <aop:before method="beginTransaction" pointcut-ref="pt1"/>
|
aop:after-returning
作用:
用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法正常执行之后。它和异常通知只能有一个执行
1
| <aop:after-returning method="commit" pointcut-ref="pt1"/>
|
aop:after-throwing
作用:
用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法执行产生异常后执行。它和后置通知只能执行一个
1
| <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
|
aop:after
作用:
用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
无论切入点方法执行时是否有异常,它都会在其后面执行。
1
| <aop:after method="release" pointcut-ref="pt1"/>
|
## 切入点表达式说明


环绕通知


基于注解的 AOP 配置
环境搭建
第一步:准备必要的代码和 jar包
拷贝上一小节的工程即可
第二步:在配置文件中导入 context 的名称空间

第三步:把资源使用注解配置


第四步:在配置文件中指定 spring 要扫描的包

配置步骤
第一步:把通知类也使用注解配置
1 2 3 4 5 6 7 8 9
|
@Component("txManager") public class TransactionManager { @Autowired private DBAssit dbAssit ; }
|
第二步:在通知类上使用@Aspect 注解声明为切面
作用:
把当前类声明为切面类。
1 2 3 4 5 6 7 8 9 10 11
|
@Component("txManager") @Aspect public class TransactionManager {
@Autowired private DBAssit dbAssit ; }
|
第三步:在增强的方法上使用注解配置通知
@Before
作用:
把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
1 2 3 4 5 6 7 8 9
| @Before("execution(* com.itheima.service.impl.*.*(..))") public void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } }
|
@AfterReturning
作用:
把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
1 2 3 4 5 6 7 8 9 10
| @AfterReturning("execution(* com.itheima.service.impl.*.*(..))") public void commit() {
try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } }
|
@AfterThrowing
作用:
把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用```
1 2 3 4 5 6 7 8 9
| @AfterThrowing("execution(* com.itheima.service.impl.*.*(..))") public void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } }
|
@After
作用:
把当前方法看成是最终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
1 2 3 4 5 6 7 8 9
| @After("execution(* com.itheima.service.impl.*.*(..))") public void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } }
|
第四步:在 spring 配置文件中开启 spring 对注解 AOP 的支持
1 2
| <aop:aspectj-autoproxy/>
|
环绕通知注解配置
@Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
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
|
@Around("execution(* com.itheima.service.impl.*.*(..))") public Object transactionAround(ProceedingJoinPoint pjp) { Object rtValue = null; try { Object[] args = pjp.getArgs(); beginTransaction(); rtValue = pjp.proceed(args); commit(); }catch(Throwable e) { rollback(); e.printStackTrace(); }finally { release(); } return rtValue; }
|
切入点表达式注解
@Pointcut
作用:
指定切入点表达式
属性:
value:指定表达式的内容
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 30 31 32
| @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1() {}
引用方式:
@Around("pt1()") public Object transactionAround(ProceedingJoinPoint pjp) { Object rtValue = null; try { Object[] args = pjp.getArgs(); beginTransaction(); rtValue = pjp.proceed(args); commit(); }catch(Throwable e) { rollback(); e.printStackTrace(); }finally { release(); } return rtValue; }
|
不使用 XML 的配置方式
1 2 3 4 5
| @Configuration @ComponentScan(basePackages="com.itheima") @EnableAspectJAutoProxy public class SpringConfiguration { }
|