AOP概述及Spring中的AOP
在之前的几篇文章中我们介绍了Spring框架中的IoC容器,这是Spring框架的核心之一。接下来我们将要介绍Spring框架中另一个核心内容——AOP,本文将介绍什么是AOP、AOP的作用以及Spring框架中AOP的理论内容。
一、AOP概述
1、什么是AOP
AOP(Aspect Oriented Programming,面向切面编程)是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2、AOP的作用和优势
作用:在程序运行期间,不修改代码就可对已有方法进行增强。
AOP有以下几个方面的优势:
- 减少重复代码
- 提高开发效率
- 维护方便
3、AOP的实现方式
可使用Java中的动态代理技术实现AOP。
二、AOP的具体应用
下面将通过一个具体案例来说明AOP的具体实现方法。
1、案例描述
假设有一个账户管理系统,该系统可实现对账户的增删改查功能。该系统分为服务层和数据访问层,使用c3p0
作为数据库连接池,使用Commons DbUtils
作为操作数据库的工具类,项目的部分代码如下:
实体类Account
package cn.frankfang.pojo;
/**
* 账户的实体类
*/
public class Account {
// 账户id
private Integer id;
// 账户名称
private String name;
// 账户余额
private Double money;
// 省略getter、setter和toString方法
}
服务层接口IAccountService
package cn.frankfang.service;
import cn.frankfang.pojo.Account;
import java.util.List;
/**
* 服务层接口
*/
public interface IAccountService {
/**
* 保存账户
*/
boolean saveAccount(Account account);
/**
* 根据id查找账户信息
* @param id
* @return
*/
Account findById(Integer id);
/**
* 获取账户列表
* @return
*/
List<Account> getAll();
/**
* 更新账户
* @param account
*/
boolean updateAccount(Account account);
/**
* 删除账户
* @param id
*/
boolean deleteAccount(Integer id);
}
服务层实现类AccountServiceImpl
package cn.frankfang.service.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.pojo.Account;
import cn.frankfang.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 服务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
// 数据访问层接口
@Autowired
private IAccountDao accountDao;
@Override
public boolean saveAccount(Account account) {
return accountDao.saveAccount(account) > 0;
}
@Override
public Account findById(Integer id) {
return accountDao.selectById(id);
}
@Override
public List<Account> getAll() {
return accountDao.getAll();
}
@Override
public boolean updateAccount(Account account) {
return accountDao.updateAccount(account) > 0;
}
@Override
public boolean deleteAccount(Integer id) {
return accountDao.deleteAccount(id) > 0;
}
}
数据访问层接口IAccountDao
package cn.frankfang.dao;
import cn.frankfang.pojo.Account;
import java.util.List;
/**
* 数据访问层接口
*/
public interface IAccountDao {
/**
* 保存账户
*/
int saveAccount(Account account);
/**
* 根据id查找账户信息
* @param id
* @return
*/
Account selectById(Integer id);
/**
* 获取账户列表
* @return
*/
List<Account> getAll();
/**
* 更新账户
* @param account
*/
int updateAccount(Account account);
/**
* 根据id删除账户
* @param id
*/
int deleteAccount(Integer id);
}
数据访问层实现类AccountDaoImpl
package cn.frankfang.dao.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.pojo.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.List;
/**
* 数据访问层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Resource
private QueryRunner runner;
@Override
public int saveAccount(Account account) {
try{
return runner.update("insert into account(id,name,money)values(?,?,?)",
account.getId(),
account.getName(),
account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account selectById(Integer id) {
try {
return runner.query("select * from account where id = ? ",
new BeanHandler<>(Account.class), id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public List<Account> getAll() {
try{
return runner.query("select * from account",
new BeanListHandler<>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int updateAccount(Account account) {
try{
return runner.update("update account set name=?,money=? where id=?",
account.getName(),
account.getMoney(),
account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int deleteAccount(Integer id) {
try{
return runner.update("delete from account where id=?", id);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Spring的配置类SpringConfiguration
package cn.frankfang.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
/**
* Spring的配置文件, 相当于spring-config.xml
*/
@Configuration
@ComponentScan("cn.frankfang")
@PropertySource("classpath:application.properties")
public class SpringConfiguration {
@Value("${database.driver}")
private String driver;
@Value("${database.url}")
private String url;
@Value("${database.username}")
private String username;
@Value("${database.password}")
private String password;
/**
* 创建一个数据源, 并存入Spring容器中
* @return
*/
@Bean("dataSource")
public DataSource dataSource () {
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Bean("runner")
@Scope("prototype")
public QueryRunner queryRunner (@Qualifier("dataSource") DataSource dataSource) {
return new QueryRunner(dataSource);
}
}
配置文件application.properties
database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://127.0.0.1:3306/test?useAffectedRows=true
database.username=root
database.password=123456
上面的业务代码其实是存在一定问题的,问题就是业务层无法控制数据库的事务。由于上面每个方法只执行了一条SQL语句,就算无法在业务层控制事务问题也不大。但如果某一个业务一次需要执行多条SQL语句,这时就存在一定的问题了。
如果我们需要添加一个转账的功能,实现该功能的代码如下:
@Override
public boolean transfer(Integer sourceAccountId, Integer targetAccountId, Double money) {
// 1.根据id查询两个账户信息
Account sourceAccount = accountDao.selectById(sourceAccountId);
Account targetAccount = accountDao.selectById(targetAccountId);
// 2.判断要转出的账户余额是否充足
if (sourceAccount.getMoney() < money) {
// 余额不足,转账失败
return false;
}
// 余额足够,继续转账
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
// 3.将修改的信息写入数据库
if (accountDao.updateAccount(sourceAccount) == 0) {
return false;
}
// 模拟转账异常
int i = 1 / 0;
// 4.将修改的信息写入数据库
if (accountDao.updateAccount(targetAccount) == 0) {
return false;
}
return true;
}
可以看到,实现该功能需要执行多条SQL语句。如果在两次将修改后的余额写入数据库之间出现了异常(在代码中模拟了一个异常),就会导致写入数据库的数据是不完整的,即破坏了事务的一致性。那么又该怎么去解决这个问题呢?
2、案例分析
要想解决这个问题,就需要让业务层来控制事务的提交和回滚,而默认情况下每次执行SQL都是一次不同的事务,因此我们需要将连接和当前线程进行绑定,所以需要写一个连接的工具类用于从数据源中获取一个连接并实现和线程的绑定。
除此之外,还需要编写一个事务管理的工具类,需要提供开启事务、提交事务、回滚事务和释放连接的方法。
3、通过传统方法实现
(1)连接工具类ConnectionUtils
package cn.frankfang.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<>();
private DataSource dataSource;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
(2)事务管理工具类TransactionManager
package cn.frankfang.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("transactionManager")
public class TransactionManager {
private ConnectionUtils connectionUtils;
@Autowired
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
(3)SpringConfiguration
配置类和AccountDaoImpl
实现类
首先对配置类做以下修改:
@Bean("runner")
@Scope("prototype")
public QueryRunner queryRunner () {
return new QueryRunner();
}
这样改的目的是为了让QueryRunner
从ConnectionUtils
工具类中获取数据源。所以接下来要在数据访问层的实现类AccountDaoImpl
修改以下内容:
package cn.frankfang.dao.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.pojo.Account;
import cn.frankfang.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 数据访问层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
@Autowired
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
@Autowired
public AccountDaoImpl(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public int saveAccount(Account account) {
try{
return runner.update(connectionUtils.getThreadConnection(),
"insert into account(id,name,money)values(?,?,?)",
account.getId(),
account.getName(),
account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account selectById(Integer id) {
try {
return runner.query(connectionUtils.getThreadConnection(),
"select * from account where id = ? ",
new BeanHandler<>(Account.class), id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public List<Account> getAll() {
try{
return runner.query(connectionUtils.getThreadConnection(),
"select * from account",
new BeanListHandler<>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int updateAccount(Account account) {
try{
return runner.update(connectionUtils.getThreadConnection(),
"update account set name=?,money=? where id=?",
account.getName(),
account.getMoney(),
account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int deleteAccount(Integer id) {
try{
return runner.update(connectionUtils.getThreadConnection(),
"delete from account where id=?", id);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
(4)服务层实现类AccountServiceImpl
package cn.frankfang.service.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.pojo.Account;
import cn.frankfang.service.IAccountService;
import cn.frankfang.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 服务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
// 数据访问层接
private IAccountDao accountDao;
// 事务管理工具类
private TransactionManager transactionManager;
@Autowired
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Autowired
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public boolean saveAccount(Account account) {
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.执行操作
boolean result = accountDao.saveAccount(account) > 0;
// 3.提交事务
transactionManager.commit();
// 4.返回结果
return result;
} catch (Exception e) {
// 5.回滚操作
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
// 6.释放连接
transactionManager.release();
}
}
@Override
public Account findById(Integer id) {
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.执行操作
Account account = accountDao.selectById(id);
// 3.提交事务
transactionManager.commit();
// 4.返回结果
return account;
} catch (Exception e) {
// 5.回滚操作
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
// 6.释放连接
transactionManager.release();
}
}
@Override
public List<Account> getAll() {
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.执行操作
List<Account> list = accountDao.getAll();
// 3.提交事务
transactionManager.commit();
// 4.返回结果
return list;
} catch (Exception e) {
// 5.回滚操作
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
// 6.释放连接
transactionManager.release();
}
}
@Override
public boolean updateAccount(Account account) {
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.执行操作
boolean result = accountDao.updateAccount(account) > 0;
// 3.提交事务
transactionManager.commit();
// 4.返回结果
return result;
} catch (Exception e) {
// 5.回滚操作
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
// 6.释放连接
transactionManager.release();
}
}
@Override
public boolean deleteAccount(Integer id) {
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.执行操作
boolean result = accountDao.deleteAccount(id) > 0;
// 3.提交事务
transactionManager.commit();
// 4.返回结果
return result;
} catch (Exception e) {
// 5.回滚操作
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
// 6.释放连接
transactionManager.release();
}
}
@Override
public boolean transfer(Integer sourceAccountId, Integer targetAccountId, Double money) {
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.执行操作
Account sourceAccount = accountDao.selectById(sourceAccountId);
Account targetAccount = accountDao.selectById(targetAccountId);
// 判断要转出的账户余额是否充足
if (sourceAccount.getMoney() < money) {
// 余额不足,转账失败
return false;
}
// 余额足够,继续转账
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
// 将修改的信息写入数据库
accountDao.updateAccount(sourceAccount);
// 模拟转账异常
int i = 1 / 0;
// 将修改的信息写入数据库
accountDao.updateAccount(targetAccount);
// 3.提交事务
transactionManager.commit();
// 4.返回结果
return true;
} catch (Exception e) {
// 5.回滚事务
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
// 6.释放连接
transactionManager.release();
}
}
}
可以看到,我们在每个方法中都加入了事务控制,这样就达到了在业务层控制事务的目的,运行程序,当出现异常时事务就会回滚,通过查询数据库可以发现数据并未被修改,这就代表上面的代码成功的实现了功能。
4、通过动态代理实现
虽然上面的方法确实可以实现功能,但写起来非常繁琐。此外,如果对事务管理的类进行修改的话,所有开始事务控制的方法都要进行修改,换句话说就是上面的代码耦合度太高了。那么有什么办法可以降低程序的耦合度吗?答案就是使用Java中的动态代理技术来降低程序的耦合度并实现相同的功能。
我们使用JDK自带的Proxy
类对IAccountService
接口进行代理,所以在SpringConfiguration
配置类中添加以下内容:
@Bean("accountService")
public IAccountService accountService (@Qualifier("accountDao") AccountDaoImpl accountDao,
@Qualifier("transactionManager") TransactionManager transactionManager) {
// 1.定义被代理的对象
final IAccountService accountService = new AccountServiceImpl(accountDao);
// 2.创建代理对象
IAccountService proxyAccountService = (IAccountService) Proxy
.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 1.开启事务
transactionManager.beginTransaction();
// 2.执行操作
result = method.invoke(accountService, args);
// 3.提交事务
transactionManager.commit();
} catch (Exception e) {
// 4.回滚事务
transactionManager.rollback();
e.printStackTrace();
} finally {
// 5.释放连接
transactionManager.release();
}
// 6.返回结果
return result;
}
});
// 3.返回代理对象
return proxyAccountService;
}
而AccountServiceImpl
则改下为如下形式:
package cn.frankfang.service.impl;
import cn.frankfang.dao.IAccountDao;
import cn.frankfang.pojo.Account;
import cn.frankfang.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 服务层实现类
*/
public class AccountServiceImpl implements IAccountService {
// 数据访问层接口
private IAccountDao accountDao;
/**
* 通过构造方法进行依赖注入
* @param accountDao
*/
public AccountServiceImpl(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public boolean saveAccount(Account account) {
return accountDao.saveAccount(account) > 0;
}
@Override
public Account findById(Integer id) {
return accountDao.selectById(id);
}
@Override
public List<Account> getAll() {
return accountDao.getAll();
}
@Override
public boolean updateAccount(Account account) {
return accountDao.updateAccount(account) > 0;
}
@Override
public boolean deleteAccount(Integer id) {
return accountDao.deleteAccount(id) > 0;
}
@Override
public boolean transfer(Integer sourceAccountId, Integer targetAccountId, Double money) {
// 1.根据id查询两个账户信息
Account sourceAccount = accountDao.selectById(sourceAccountId);
Account targetAccount = accountDao.selectById(targetAccountId);
// 2.判断要转出的账户余额是否充足
if (sourceAccount.getMoney() < money) {
// 余额不足,转账失败
return false;
}
// 余额足够,继续转账
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
// 3.将修改的信息写入数据库
if (accountDao.updateAccount(sourceAccount) == 0) {
return false;
}
// 模拟转账异常
int i = 1 / 0;
// 4.将修改的信息写入数据库
if (accountDao.updateAccount(targetAccount) == 0) {
return false;
}
return true;
}
}
运行程序,当出现异常时事务就会回滚,通过查询数据库可以发现数据并未被修改,这就代表上面的代码成功的实现了功能。
可以看到,相对于传统的方法进行事务控制,动态代理可以实现在不改变原有实现类的情况下对实现类进行增强,降低了程序的耦合度,使代码的维护升级更加容易,这就是AOP的好处。Spring框架中的AOP也是由动态代理来实现的,下面我们将介绍Spring框架中的AOP。
三、Spring中的AOP
Spring框架通过使用基于XML配置文件的方法或@AspectJ
注释形式提供了编写自定义切面的简单而强大的方法。接下来我们将介绍与Spring AOP相关的概念。
1、相关术语
下面将列出Spring AOP的相关术语:
Aspect
:切面。是切入点和通知(引介)的结合Join point
:连接点。是指那些被拦截到的点,在Spring中这些点指的是方法(Spring只支持方法类型的连接点)Advice
:通知/增强。是指拦截到连接点之后要做的事情Pointcut
:切入点。是指我们要对哪些连接点进行拦截的定义Introduction
:引介。这是一种特殊的通知,可以在不修改类代码的前提下在运行期动态地对类添加一些方法或成员变量Target object
:目标对象。是指代理的目标对象AOP proxy
:代理。是指一个类被AOP织入增强后就产生一个结果代理类Weaving
:织入。是指把增强应用到目标对象来创建新的代理对象的过程
2、功能和目标
- Spring AOP是用纯Java实现的。不需要特殊的编译过程。Spring AOP不需要控制类加载器层次结构,因此适合在servlet容器或应用服务器中使用。
- Spring AOP目前只支持方法执行连接点(建议在Spring Bean上执行方法)。虽然可以在不破坏核心Spring AOP APIs的情况下添加对字段截取的支持,但是没有实现字段截取。如果需要建议字段访问和更新连接点,请考虑使用AspectJ之类的语言。
- Spring AOP的AOP方法不同于大多数其他AOP框架。其目的并不是提供最完整的AOP实现(尽管Spring AOP非常有能力)。相反,其目的是在AOP实现和Spring IoC之间提供紧密的集成,以帮助解决企业应用程序中的常见问题。
- Spring框架的AOP功能通常与Spring IoC容器一起使用。方面是通过使用普通
bean
定义语法配置的(尽管这允许强大的“自动代理”功能)。这是与其他AOP实现的一个关键区别。使用Spring AOP不能轻松或高效地完成某些事情,例如建议非常细粒度的对象(通常是域对象)。在这种情况下,AspectJ是最佳选择。
若需获取更多关于Spring AOP的理论内容,请参阅:Aspect Oriented Programming with Spring。