SpringAOP
Spring的核心特性就是IOC和AOP,之前整理了SpringIOC,这篇文章就来写一下SpringAOP(Aspect Oriented Programming),即:面向切面编程
面向切面编程是指通过预编译和运行期动态代理的方式实现在不修改源代码的情况下给程序动态统一添加功能的一种技术
在学习SpringAOP之前我们需要了解什么是代理模式
文中提到的基于接口的JDK动态代理与基于子类的CGLib动态代理两种动态代理的方式都是实现SpringAOP的基础
在spring中,虽然引入了AspectJ的语法,但是他本质上使用的是动态代理的方式,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
如果目标对象有接口,优先使用JDK 动态代理,如果目标对象没有接口,则使用CGLib动态代理
简单转账功能
1、新建Maven项目名为“spring-aop-llf“,设置好Maven版本、配置文件以及Maven仓库
1.1 准备数据
drop database if exists spring_aop;
create database spring_aop;
use spring_aop;
create table account (
id int(11) auto_increment primary key,
accountNum varchar(20) default NULL,
money int(8) default 0
);
insert into account (accountNum, money) values
(“622200001”,1000),(“622200002”,1000);
2、导包
2.1导入Spring基础包
2.2导入操作数据库、连接数据库、测试需要的包
<!-- bean definitions here -->
<context:component-scan base-package="dao"/>
<context:component-scan base-package="services"/>
<context:component-scan base-package="utils"/>
3.2配置数据源
代码实现
数据库连接工具类:ConnectionUtils
package utils;
@Component
public class ConnectionUtils {
private ThreadLocal tl = new ThreadLocal();
@Autowired
private ComboPooledDataSource dataSource;
/**
* 获得当前线程绑定的连接
*
* @return
*/
public Connection getThreadConnection() {
try {
// 看线程是否绑了连接
Connection conn = tl.get();
if (conn == null) {
// 从数据源获取一个连接
conn = dataSource.getConnection();
// 和线程局部变量 绑定
tl.set(conn);
}
// 返回线程连接
return tl.get();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 把连接和当前线程进行解绑
*/
public void remove() {
tl.remove();
}
} ·
Account模块实体类Account
package entity;
public class Account {
private Integer id;
private String accountNum;
private Integer money;
// 省略getter&setter方法
}
创建Account模块Dao层AccountDao
package dao;
public interface AccountDao {
/**
* 更新
*
* @param account
*/
void updateAccount(Account account);
/**
* 根据编号查询账户
*
* @param accountNum
* @return 如果没有结果就返回null,如果结果集超过一个就抛异常,如果有唯一的一个结果就返回
*/
Account findAccountByNum(String accountNum);
}
创建Account模块Dao层实现类AccountDaoImp
package dao.impl;
@Repository(“accountDao”)
public class AccountDaoImpl implements AccountDao {
// 数据库查询工具类
@Autowired
private QueryRunner runner;
// 数据库连接工具类
@Autowired
private ConnectionUtils connectionUtils;
/**
* 更新
*
* @param account
*/
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),
"update account set accountNum=?,money=? where id=?",
account.getAccountNum(), account.getMoney(), account.getId());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 根据编号查询账户
*
* @param accountNum
* @return 如果没有结果就返回null,如果结果集超过一个就抛异常,如果有唯一的一个结果就返回
*/
public Account findAccountByNum(String accountNum) {
List<Account> accounts = null;
try {
accounts = runner.query(connectionUtils.getThreadConnection(),
"select * from account where accountNum = ? ",
new BeanListHandler<Account>(Account.class),
accountNum);
} catch (SQLException e) {
throw new RuntimeException(e);
}
if (accounts == null || accounts.size() == 0) {
// 如果没有结果就返回null
return null;
} else if (accounts.size() > 1) {
// 如果结果集超过一个就抛异常
throw new RuntimeException("结果集不唯一,数据有问题");
} else {
// 如果有唯一的一个结果就返回
return accounts.get(0);
}
}
}
创建Account模块Service层AccountService
package services;
public interface AccountService {
/**
* 转账
*
* @param sourceAccount 转出账户
* @param targetAccount 转入账户
* @param money 转账金额
*/
void transfer(String sourceAccount, String targetAccount, Integer money);
}
创建Account模块Service层实现类AccountServiceImpl
package services.impl;
@Service(“accountService”)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账
*
* @param sourceAccount 转出账户
* @param targetAccount 转入账户
* @param money 转账金额
*/
public void transfer(String sourceAccount, String targetAccount, Integer money) {
// 查询原始账户
Account source = accountDao.findAccountByNum(sourceAccount);
// 查询目标账户
Account target = accountDao.findAccountByNum(targetAccount);
// 原始账号减钱
source.setMoney(source.getMoney() - money);
// 目标账号加钱
target.setMoney(target.getMoney() + money);
// 更新原始账号
accountDao.updateAccount(source);
// 更新目标账号
accountDao.updateAccount(target);
System.out.println("转账完毕");
}
}
创建Account模块测试类AccountTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = “classpath:applicationContext.xml”)
public class AccountTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer("622200001", "622200002", 100);
}
}
引入AOP(XML)
相关概念
使用Spring的AOP替代代理类。先回顾下AOP的概念
AOP是一种编程设计模式,是一种编程技术,使用AOP后通过修改配置即可实现增加或者去除某些附加功能
学习AOP中的常用术语:
Join point(连接点)
所谓连接点是指那些可以被拦截到的点
在Spring中这些点指的是方法,可以看作正在访问的,或者等待访问的那些需要被增强功能的方法
Spring只支持方法类型的连接点
Pointcut(切入点)
切入点是一个规则,定义了我们要对哪些Joinpoint进行拦截
因为在一个程序中会存在很多的类,每个类又存在很多的方法,Pointcut来标记哪些方法会应用AOP对该方法做功能增强
Advice(通知)
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。也就是对方法做的增强功能。通知分为如下几类:
前置通知:在连接点之前运行的通知类型,它不会阻止流程进行到连接点,只是在到达连接点之前运行该通知内的行为
后置通知:在连接点正常完成后要运行的通知,正常的连接点逻辑执行完,会运行该通知
最终通知:无论连接点执行后的结果如何,正常还是异常,都会执行的通知
异常通知:如果连接点执行因抛出异常而退出,则执行此通知
环绕通知:环绕通知可以在方法调用之前和之后执行自定义行为
Target(目标)
Target指的是代理的目标对象,更通俗的解释就是:AOP对连接点方法做增强,底层是代理模式生成连接点所在类的代理对象,那么连接点所在的类,就是被代理的类称呼为Target
Aspect(切面)
切面本质是一个类,只不过是个功能类,作为整合AOP的切入点和通知。
一般来讲,需要在Spring的配置文件中配置,或者通过注解来配置
Weaving(织入)
织入是一种动作的描述,在程序运行时将增强的功能代码也就是通知,根据通知的类型(前缀后缀等…)放到对应的位置,生成代理对象
Proxy(代理)
一个类被AOP织入增强后,产生的结果就是代理类