Spring AOP 實現(xiàn)之注解配置
1. 前言
大家好,本小節(jié),我們學(xué)習(xí) Spring 框架 AOP 的另外一種實現(xiàn)形式 —— 注解模式。在上小節(jié)中我們使用 XML 配置文件的方式已經(jīng)體驗過了 Spring 的 AOP。
那么對于注解來實現(xiàn),大致步驟思路相同,只需要將 xml 配置的 AOP 替換成注解即可。如何實現(xiàn)呢? ok,大家隨我來進(jìn)入本小節(jié)課程的學(xué)習(xí)。
2. 實例演示
2.1 工程搭建介紹
數(shù)據(jù)庫表結(jié)構(gòu):
建表 SQL 語句如下:
CREATE TABLE `account` (
`id` int(11) NOT NULL auto_increment COMMENT 'id',
`accountNum` varchar(20) default NULL COMMENT '賬號',
`money` int(8) default NULL COMMENT '余額',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
工程代碼介紹:
- 實體類: 跟數(shù)據(jù)庫表對應(yīng)的 Java 類 Account ;
- 操作實體類: Dao 和 Dao 的接口實現(xiàn)類 ;
- 調(diào)用持久層的業(yè)務(wù)類: Service 和 Service 的實現(xiàn)類 ;
- 事務(wù)管理器類: TransactionManager 提供事務(wù)的一系列操作 ;
- 測試代碼類: 初始化 Spring 調(diào)用類中的方法測試 。
使用注解介紹:
- @Aspect: 此注解用于表明某個類為切面類,而切面類的作用我們之前也解釋過,用于整合切入點和通知;
- @Pointcut: 此注解用于聲明一個切入點,表明哪些類的哪些方法需要被增強(qiáng);
- @Before: 此注解一看就明白,表示一個前置通知。即在業(yè)務(wù)方法執(zhí)行之前做的事情;
- @AfterReturning: 此注解表示一個后置通知。即在業(yè)務(wù)方法執(zhí)行之后做的事情;
- @After:此注解一個最終通知。即在業(yè)務(wù)方法執(zhí)行之前做的事情;
- @AfterThrowing:此注解表示一個異常通知。即在業(yè)務(wù)代碼執(zhí)行過程中出現(xiàn)異常做的事情。
2.2 代碼實現(xiàn)
1. 創(chuàng)建 maven 工程:
pom 文件的 jar 包坐標(biāo)如下:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
2. 連接數(shù)據(jù)庫的工具類:
@Component
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 獲取當(dāng)前線程上的連接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先從ThreadLocal上獲取
Connection conn = tl.get();
//2.判斷當(dāng)前線程上是否有連接
if (conn == null) {
//3.從數(shù)據(jù)源中獲取一個連接,并且存入ThreadLocal中
conn = dataSource.getConnection();
conn.setAutoCommit(false);
tl.set(conn);
}
//4.返回當(dāng)前線程上的連接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把連接和線程解綁
*/
public void removeConnection(){
tl.remove();
}
}
3. 實體類 Account:
public class Account implements Serializable {
//數(shù)據(jù)id
private Integer id;
//賬號編碼
private String accountNum;
//賬號金額
private Float money;
//省略get 和set 方法
}
4. 持久層 dao 和 dao 的 實現(xiàn)類:
//dao的接口
public interface IAccountDao {
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 根據(jù)編號查詢賬戶
*/
Account findAccountByNum(String accountNum);
}
//dao的實現(xiàn)類
@Repository
public class AccountDaoImpl implements IAccountDao {
//dbutil的查詢工具類
@Autowired
private QueryRunner runner;
//連接的工具類
@Autowired
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//修改賬號
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set accountNum=?,money=? where id=?",account.getAccountNum(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
//根據(jù)賬號查詢
public Account findAccountByNum(String accountNum) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where accountNum = ? ",new BeanListHandler<Account>(Account.class),accountNum);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("結(jié)果集不唯一,數(shù)據(jù)有問題");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
代碼解釋: AccountDaoImpl 類上面的注解 @Repository 表示使用注解實例化此類,并交給 Spring 的容器管理。
5. 業(yè)務(wù)類 Service 和 Service 的實現(xiàn)類:
//業(yè)務(wù)接口
public interface IAccountService {
/**
* 轉(zhuǎn)賬
* @param sourceAccount 轉(zhuǎn)出賬戶名稱
* @param targetAccount 轉(zhuǎn)入賬戶名稱
* @param money 轉(zhuǎn)賬金額
*/
void transfer(String sourceAccount, String targetAccount, Integer money);
}
//業(yè)務(wù)實現(xiàn)類
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
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("轉(zhuǎn)賬完畢");
}
}
代碼解釋:AccountServiceImpl 類上面的注解 @Service 表示使用注解實例化此類,并交給 Spring 的容器管理。
6. 事務(wù)管理器類
@Component
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Pointcut("execution(* com.offcn.service.impl.*.*(..))")
private void pt1() {}
/**
* 開啟事務(wù)
*/
@Before("pt1()")
public void beginTransaction(){
try {
System.out.println("開啟事務(wù)");
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事務(wù)
*/
@AfterReturning("pt1()")
public void commit(){
try {
System.out.println("提交事務(wù)");
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滾事務(wù)
*/
@AfterThrowing("pt1()")
public void rollback(){
try {
System.out.println("回滾事務(wù)");
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 釋放連接
*/
@After("pt1()")
public void release(){
try {
System.out.println("釋放連接");
connectionUtils.getThreadConnection().close();//還回連接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
代碼解釋:
此類通過注解 @Componet 實例化,并且交由 Spring 容器管理,@Aspect 表明它是一個切面類。而下面的注解 @Pointcut 和其余的方法上的各個通知注解,在上面也已經(jīng)介紹過,這里不做贅述了。
主要專注點在于每個注解的通知方法內(nèi)部引入切入點的表達(dá)式方式。
7. 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置數(shù)據(jù)源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--連接數(shù)據(jù)庫的必備信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/transmoney"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 注解掃描工程下的包路徑-->
<context:component-scan base-package="com.offcn"></context:component-scan>
<!-- 注解代理模式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
配置文件說明:
- dataSource: 采用 c3p0 數(shù)據(jù)源,大家一定要注意數(shù)據(jù)庫的名稱與賬號名和密碼;
- queryRunner: dbutils 第三方框架提供用于執(zhí)行 sql 語句,操作數(shù)據(jù)庫的一個工具類;
- context:component-scan: 此注解表示注解方式初始化容器掃描的包路徑;
- aop:aspectj-autoproxy: 此注解表示開啟代理模式
8. 測試類代碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("622200009999","622200001111",100);
}
}
測試結(jié)果:
執(zhí)行代碼后結(jié)果:
可以看到,我們通過注解方式配置 Spring 的 AOP 相關(guān)配置,同樣實現(xiàn)了對于數(shù)據(jù)的操作。
3. 小結(jié)
本小節(jié)學(xué)習(xí)了 Spring 中 AOP 的注解實現(xiàn),那么哪些要求大家掌握的呢?
-
AOP 使用的相關(guān)注解有哪些;
-
AOP 每種注解的使用方式以及作用;
-
AOP 切入點的表達(dá)式寫法。