1、AOP
AOP:【動態代理】指在程序運行期間動態的將某段代碼切入到指定方法指定位置進行運行的編程方式;
Spring的AOP實現是基於 AspectJ 的,要在 Spring 中聲明 AspectJ 切面,只需要在 IOC 容器中將切面聲明爲 Bean 實例. 當在 Spring IOC 容器中初始化 AspectJ 切面之後,Spring IOC 容器就會爲那些與 AspectJ 切面相匹配的 Bean 創建代理。
AspectJ 支持 5 種類型的通知註解:
@Before: 前置通知, 在方法執行之前執行
@After: 後置通知, 在方法執行之後執行 。
@AfterRunning: 返回通知, 在方法返回結果之後執行
@AfterThrowing: 異常通知, 在方法拋出異常之後
@Around: 環繞通知, 圍繞着方法執行
1.1 AspectJ實現AOP
使用步驟:
1、導入aop模塊;Spring AOP:(spring-aspects),以及對應的maven座標;
2、定義一個業務邏輯類(MathCalculator);在業務邏輯運行的時候將日誌進行打印(方法之前、方法運行結束、在方法返回結果之後執行、方法出現異常, 圍繞着方法執行);
3、定義一個日誌切面類(LogAspects):切面類裏面的方法需要動態感知 MathCalculator.div 運行到哪裏然後執行;
4、給切面類的目標方法標註何時何地運行(通知註解);
5、將切面類和業務邏輯類(目標方法所在類)都加入到容器中;
6、必須告訴Spring哪個類是切面類(給切面類上加一個註解:@Aspect);
7、給配置類中加 @EnableAspectJAutoProxy 【開啓基於註解的aop模式】
1.1.1 導入AspectJ的註解
創建maven工程,導入Spring和aspects的座標以及junit:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencys>
1.1.2 創建業務類
package com.spring.annotation.aop.bean;
public class MathCalculator {
public int div(int a, int b){
System.out.println("業務方法執行了...");
return a/b;
}
}
1.1.3 創建切面類
注意:@Aspect 註解表明這是一個切面類;
package com.spring.annotation.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
@Aspect //表明這是一個切面
public class MyLogAspects {
@Pointcut("execution(public int com.spring.annotation.aop.bean.MathCalculator.div(..))")
public void logPointcut(){}
@Before("logPointcut()")//前置通知
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("目標方法執行前,會執行該方法,目標方法是:"+methodName+",目標方法參數是:"+list);
}
@After("logPointcut()")//後置通知,不管是否發生異常都會執行該方法
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("在執行了目標方法後,會執行該方法,目標方法是:"+methodName+",目標方法參數是:"+list);
}
//返回通知,方法正常執行後會執行該方法,可以拿到方法的返回值,與異常通知不共存
// JoinPoint一定要出現在參數表的第一位,通過returning = "result"和形參 Object result 就可以拿到返回值
@AfterReturning(value = "logPointcut()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
System.out.println("目標方法正常執行並返回了結果了,返回的結果是:"+result);
}
//異常通知,目標方法發生異常會執行該方法,可以拿到方法的異常信息,與返回通知不共存
@AfterThrowing(value = "logPointcut()",throwing = "exception")
public void afterThrowingMethod(JoinPoint joinPoint,Exception exception){
System.out.println("目標方法出現了異常,異常信息是:"+exception);
}
//環繞通知,一個通知基本上可以頂前面的四個
@Around(value = "logPointcut()")
public Object aroungMethod(ProceedingJoinPoint proceedingJoinPoint){
//定義返回對象
Object result = null;
String methodName = proceedingJoinPoint.getSignature().getName();
List<Object> args = Arrays.asList(proceedingJoinPoint.getArgs());
try{
//相當於前置通知
System.out.println("前置通知:方法執行之前...");
result=proceedingJoinPoint.proceed();//調用目標方法
//相當於後置通知,但是發生了異常,就不會執行這句話
System.out.println("後置通知:方法執行之後...");
} catch (Throwable throwable) {
throwable.printStackTrace();
//相當於異常通知
System.out.println("異常通知:目標方法發生異常了...");
throw new RuntimeException(throwable);
}
//相當於返回通知
System.out.println("返回通知:方法正常執行,返回結果...");
return result;//返回結果
}
}
1.1.4 創建Spring配置類
配置類上兩個註解要重視:
@Configuration:表明這是一個Spring的配置類,相當於xml配置的applicationContext.xml
@EnableAspectJAutoProxy:開啓AspectJ的註解支持,相當於xml配置時的:<aop:aspectj-autoproxy/>
package com.spring.annotation.aop.config;
import com.spring.annotation.aop.aspect.MyLogAspects;
import com.spring.annotation.aop.bean.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class SpringAopConfig {
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}
@Bean
public MyLogAspects myLogAspects(){
return new MyLogAspects();
}
}
1.1.5 測試
package com.spring.annotation.aop;
import com.spring.annotation.aop.bean.MathCalculator;
import com.spring.annotation.aop.config.SpringAopConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringAopTest {
@Test
public void testAop(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(SpringAopConfig.class);
applicationContext.refresh();
MathCalculator mathCalculator = (MathCalculator) applicationContext.getBean("mathCalculator");
mathCalculator.div(2,0);
}
}
執行結果:
前置通知:方法執行之前...
目標方法執行前,會執行該方法,目標方法是:div,目標方法參數是:[2, 0]
業務方法執行了...
java.lang.ArithmeticException: / by zero
1.2 AOP原理
1、@EnableAspectJAutoProxy 開啓AOP功能
2、@EnableAspectJAutoProxy 會給容器中註冊一個組件 AnnotationAwareAspectJAutoProxyCreator
3、AnnotationAwareAspectJAutoProxyCreator是一個後置處理器
4、容器的創建流程:
1、registerBeanPostProcessors()註冊後置處理器;創建AnnotationAwareAspectJAutoProxyCreator對象
2、finishBeanFactoryInitialization()初始化剩下的單實例bean
1)、創建業務邏輯組件和切面組件
2)、AnnotationAwareAspectJAutoProxyCreator攔截組件的創建過程
3)、組件創建完之後,判斷組件是否需要增強
是:切面的通知方法,包裝成增強器(Advisor);給業務邏輯組件創建一個代理對象(cglib)
5、執行目標方法:
1、代理對象執行目標方法
2、CglibAopProxy.intercept();
1)、得到目標方法的攔截器鏈(增強器包裝成攔截器MethodInterceptor)
2)、利用攔截器的鏈式機制,依次進入每一個攔截器進行執行;
3)、效果:
正常執行:前置通知-》目標方法-》後置通知-》返回通知
出現異常:前置通知-》目標方法-》後置通知-》異常通知
2、事務管理
可以參照 之前的那篇通過xml配置實現JdbcTemplate和事務管理的文章。
這裏演示如何通過註解來實現JdbcTemplate,以及進行註解事務管理,相關的數據庫環境參照:之前的那篇通過xml配置實現JdbcTemplate和事務管理的文章。
2.1 事務管理的步驟
1、添加spring-jdbc的座標依賴和數據源以及驅動依賴;
2、在配置類上添加註解:@EnableTransactionManagement 開啓基於註解的事務管理功能
3、在配置類中,向IOC容器中註冊:數據源,JdbcTemplate和事務管理器PlatformTransactionManager
4、在事務方法上添加@Transactional註解
2.1.1 添加依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
2.1.2 編寫配置類
@EnableTransactionManagement //開啓註解事務支持
PlatformTransactionManager transactionManager()
package com.spring.annotation.tx;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
@Configuration
@ComponentScan("com.spring.annotation.tx")
@EnableTransactionManagement //開啓註解事務支持
public class SpringTxConfig {
@Bean//配置數據源
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring4");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("123");
return dataSource;
}
@Bean//配置JdbcTemplate
public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
//Spring對@Configuration類會特殊處理;給容器中加組件的方法,dataSource() 多次調用都只是從容器中找組件
return new JdbcTemplate(dataSource());
}
@Bean//註冊事務管理器到容器中
public PlatformTransactionManager transactionManager() throws PropertyVetoException {
return new DataSourceTransactionManager(dataSource());
}
}
2.1.3 業務方法添加@Transactional註解
1、Dao接口和實現類
package com.spring.annotation.tx;
public interface BookShopDao {
//根據書號獲取書的單價
public int findBookPriceByIsbn(String isbn);
//更新數的庫存. 使書號對應的庫存 - 1
public void updateBookStock(String isbn);
//更新用戶的賬戶餘額: 使 username 的 balance - price
public void updateUserAccount(String username, int price);
}
package com.spring.annotation.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int findBookPriceByIsbn(String isbn) {
String sql = "select price from book where isbn = ?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return price;
}
public void updateBookStock(String isbn) {
//檢查書的庫存是否足夠, 若不夠, 則拋出異常
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if(stock == 0){
throw new RuntimeException("庫存不足!");
}
String sql = "update book_stock set stock = stock - 1 where isbn = ?";
jdbcTemplate.update(sql,isbn);
}
public void updateUserAccount(String username, int price) {
//驗證餘額是否足夠, 若不足, 則拋出異常
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
if(balance < price){
throw new RuntimeException("餘額不足!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql, price, username);
}
}
2、Service接口和實現類
package com.spring.annotation.tx;
public interface BookShopService {
//購買書本的方法
public void purchase(String username, String isbn);
}
package com.spring.annotation.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Transactional
public void purchase(String username, String isbn) {
//查詢書本單價
int price = bookShopDao.findBookPriceByIsbn(isbn);
//更新庫存
bookShopDao.updateBookStock(isbn);
//更新餘額
bookShopDao.updateUserAccount(username,price);
}
}
2.1.4 測試
package com.spring.annotation.tx;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTxTest {
@Test
public void testTx(){
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
applicationContext.register(SpringTxConfig.class);
applicationContext.refresh();
BookShopService bookShopService =
(BookShopService) applicationContext.getBean("bookShopService");
bookShopService.purchase("tom","0001");
}
}