SpringAOP編程的作用是:對spring管理的所有類,在不改變類代碼的情況下,對類中的方法進行增強
1.什麼是AOP
AOP (Aspect Oriented Programing) 稱爲:面向切面編程,它是一種編程思想。
AOP思想: 基於代理思想,對原來目標對象,創建代理對象,在不修改原對象代碼情況下,通過代理對象,調用增強功能的代碼
AOP應用場景:
- 場景一: 記錄日誌
- 場景二: 監控方法運行時間 (監控性能)
- 場景三: 權限控制
- 場景四: 緩存優化 (第一次調用查詢數據庫,將查詢結果放入內存對象, 第二次調用, 直接從內存對象返回,不需要查詢數據庫 )
- 場景五: 事務管理 (調用方法前開啓事務, 調用方法後提交或者回滾、關閉事務 )
2.Spring AOP編程兩種方式
- Spring 1.2 開始支持
AOP編程 (傳統SpringAOP 編程)
,編程非常複雜 ---- 更好學習Spring 內置傳統AOP代碼 - Spring 2.0 之後支持
第三方 AOP框架(AspectJ )
,實現另一種 AOP編程 – 推薦
3.AOP編程相關術語
- Aspect(切面)
是通知和切入點的結合,通知和切入點共同定義了關於切面的全部內容—它的功能、在何時和何地完成其功能 - joinpoint(連接點)
所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因爲spring只支持方法類型的連接點. - Pointcut(切入點)
所謂切入點是指我們要對哪些joinpoint進行攔截的定義.
通知定義了切面的”什麼”和”何時”,切入點就定義了”何地”. - Advice(通知)
所謂通知是指攔截到joinpoint之後所要做的事情就是通知.通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能) - Target(目標對象)
代理的目標對象 - 織入
是指把切面應用到目標對象來創建新的代理對象的過程.切面在指定的連接點織入到目標對象 - Introduction(引入)
在不修改類代碼的前提下, Introduction可以在運行期爲類動態地添加一些方法或Field.
4.Spring AOP方式
參考spring第三天
傳統的AOP編程- xml方式
- 註解方式
4.1 傳統的AOP編程(瞭解即可)
面向切面編程開發步驟(動態織入)
- 1、確定目標對象(target—>bean)
- 2、編寫Advice通知方法 (增強代碼)
- 3、配置切入點和切面
傳統SpringAOP的Advice 必須實現對應的接口!
Spring按照通知Advice在目標類方法的連接點位置,可以分爲5類
- 1.前置通知 org.springframework.aop.MethodBeforeAdvice
在目標方法執行前實施增強 - 2.後置通知 org.springframework.aop.AfterReturningAdvice
在目標方法執行後實施增強 - 3.環繞通知 org.aopalliance.intercept.MethodInterceptor
在目標方法執行前後實施增強 - 4.異常拋出通知 org.springframework.aop.ThrowsAdvice
在方法拋出異常後實施增強 - 5.引介通知 org.springframework.aop.IntroductionInterceptor
在目標類中添加一些新的方法和屬性
步驟1.編寫傳統AOP的通知類(切面要做的事情)
這裏使用環繞通知
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;
//傳統的aop的advice通知,增強類,必須實現org.aopalliance.intercept.MethodInterceptor接口(注意和cglib代理接口區分開)
public class TimeLogInterceptor implements MethodInterceptor {
//log4j記錄器
private static Logger LOG=Logger.getLogger(TimeLogInterceptor.class);
//回調方法
//參數:目標方法回調函數的包裝類,獲取調用方法的相關屬性、方法名、調用該方法的對象(即封裝方法、目標對象,方法的參數)
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//業務:記錄目標的方法的運行時間
//方法調用之前記錄時間
long beginTime = System.currentTimeMillis();
//目標對象原來的方法的調用,返回目標對象方法的返回值。
Object object = methodInvocation.proceed();//類似於invoke
//方法調用之後記錄時間
long endTime = System.currentTimeMillis();
//計算運行時間
long runTime=endTime-beginTime;
//寫日誌:
/**
* 1:記錄日誌到數據庫(優勢:便於查詢;劣勢:要佔用數據庫空間,日誌一般都非常龐大)
* 2:記錄日誌到log4j(優勢:文件存儲,可以記錄非常大的日誌數據,而且還有日誌級別的特點;劣勢:不便於查詢)
*/
LOG.info("方法名爲:"+methodInvocation.getMethod().getName()+"的運行時間爲:"+runTime+"毫秒");
return object;
}
}
要增強的target對象,對於spring來說,目標:就是bean對象
public class CustomerServiceImpl implements ICustomerService {
@Override
public void save() {
System.out.println("客戶保存了。。。。。");
}
@Override
public int find() {
System.out.println("客戶查詢數量了。。。。。");
return 100;
}
}
public class ProductService {
public void save() {
System.out.println("商品保存了。。。。。");
}
public int find() {
System.out.println("商品查詢數量了。。。。。");
return 99;
}
}
使用SpringTest進行測試
//springjunit集成測試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要測試bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//測試
@Test
public void test(){
//基於接口
customerService.save();
customerService.find();
//基於類的
productService.save();
productService.find();
}
}
但是發現,此時並沒有執行TimeLogInterceptor 類的invoke()方法,也就是說,並沒有計算執行Service類的時間,那怎麼辦呢?我們往下看,需要在spring容器中配置spring的aop。
這是因爲我們並沒有配置切入點和切面
我們只編寫了通知類,知道了要對目標對象進行如何增強,但通知類和目標對象還需要通過xml建立關係
步驟2.配置切入點和切面
目的:讓哪個類(切面)、哪個方法(切入點),進行怎樣的增強(通知)。
<!-- 基於接口類 -->
<bean id="customerService" class="cn.itcast.advice.CustomerServiceImpl"/>
<!-- 基於一般類(沒有實現接口的類) -->
<bean id="productService" class="cn.itcast.advice.ProductService"/>
<!--
2.配置增強:原則bean能增強bean
Advice:通知,增強
-->
<bean id="timeLogAdvice" class="cn.itcast.advice.TimeLogInterceptor"/>
<!-- 配置切入點和切面 :aop:config-->
<aop:config>
<!--
配置切入點:即你要攔截的哪些 連接點(方法)
* expression:表達式:匹配方法的,語法:使用aspectj的語法,相對簡單
* 表達式:bean(bean的名字),你要對哪些bean中的所有方法增強
* bean(customerService):要對customerservice的bean中的所有方法進行增強
-->
<!--
<aop:pointcut expression="bean(customerService)" id="myPointcut"/>
* expression=bean(*Service):在spring容器中,所有以Service單詞結尾的bean的都能被攔截
* id="myPointcut":爲切入點定義唯一標識
-->
<!--
<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
* 配置切面:通知(增強的方法)關聯切入點(目標對象調用的方法)
* 告訴:你要對哪些方法(pointcut),進行怎強的增強 (advice)
-->
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
</aop:config>
切入點表達式的語法整理如下(aspectj的語法):
- bean(bean Id/bean name)
例如 bean(customerService) 增強spring容器中定義id屬性/name屬性爲customerService的bean中所有方法 - execution(<訪問修飾符>?<返回類型>空格<方法名>(<參數>)<異常>?)
例如:
execution(* cn.itcast.spring.a_jdkproxy.CustomerServiceImpl.(…)) 增強bean對象所有方法
execution( cn.itcast.spring….(…)) 增強spring包和子包所有bean所有方法
提示:最靈活的
4.2 AspectJ 切面編程(xml方式)
普通的pojo即可。(不需要實現接口)
AspectJ提供不同的通知類型:
- Before 前置通知,相當於BeforeAdvice
- AfterReturning 後置通知,相當於AfterReturningAdvice
- Around 環繞通知,相當於MethodInterceptor
- AfterThrowing拋出通知,相當於ThrowAdvice
- After 最終final通知,不管是否異常,該通知都會執行
- DeclareParents 引介通知,相當於IntroductionInterceptor (不要求掌握)
第一步:確定目標對象,即確定bean對象:
<!-- 1.確定了要增強的target對象 -->
<!-- 對於spring來說,目標對象:就是bean對象 -->
<!-- 基於接口類 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基於一般類 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
第二步:編寫Before 前置通知Advice增強 :
不用實現接口
//aspectj的advice通知增強類,無需實現任何接口
public class MyAspect {
//前置通知
//普通的方法。方法名隨便,但也不能太隨便,一會要配置
public void firstbefore(){
System.out.println("------------第一個個前置通知執行了。。。");
}
}
第三步:配置切入點和切面(讓切入點關聯通知) :
<!-- 2.配置advice通知增強 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<!-- 切入點:攔截哪些bean的方法 -->
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!--
切面:要對哪些方法進行怎樣的增強
aop:aspect:aspejctj的方式!
ref:配置通知
-->
<aop:aspect ref="myAspectAdvice">
<!-- 第一個前置通知 :在訪問目標對象方法之前,先執行通知的方法
method:advice類中的方法名,
pointcut-ref="myPointcut":注入切入點
目的是讓通知關聯切入點
-->
<aop:before method="firstbefore" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
和傳統的aop配置相比,更靈活,advice不需要實現接口,簡單的pojo就可以了;一個通知可以增強多個連接點,一個連接點可以被多次增強。
和傳統aop的對比發現,aspectj更靈活,一個類中可以寫N個增強方法,但傳統的只能是一個類對應一個方法。
4.3 @Aspectj註解配置切面編程
1.創建目標對象
//實現類
/**
* @Service("customerService")
* 相當於spring容器中定義:
* <bean id="customerService" class="cn.itcast.spring.a_aspectj.CustomerServiceImpl">
*/
@Service("customerService")
public class CustomerServiceImpl implements CustomerService{
public void save() {
System.out.println("客戶保存了。。。。。");
}
public int find() {
System.out.println("客戶查詢數量了。。。。。");
return 100;
}
}
//沒有接口的類
/**
* @Service("productService")
* 相當於spring容器中定義:
* <bean id="productService" class="cn.itcast.spring.a_aspectj.ProductService">
*/
@Service("productService")
public class ProductService {
public void save() {
System.out.println("商品保存了。。。。。");
}
public int find() {
System.out.println("商品查詢數量了。。。。。");
return 99;
}
}
使用bean註解的掃描(自動開啓註解功能)
<!-- 1。確定目標 -->
<!-- 掃描bean組件 -->
<context:component-scan base-package="cn.itcast.spring"/>
2.編寫通知,配置切面
編寫通知類,在通知類 添加@Aspect 註解,代表這是一個切面類,並將切面類交給spring管理(能被spring掃描到@Component)
。
@Component(“myAspect”):將增強的類交給spring管理,纔可以增強
@Aspect:將該類標識爲切面類(這裏面有方法進行增強),相當於<aop:aspect ref=”myAspect”>
//advice通知類增強類
@Component("myAspect")//相當於<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相當於<aop:aspect ref="myAspect">
public class MyAspect {
}
在切面的類,通知方法上添加
- @Before 前置通知,相當於BeforeAdvice
- @AfterReturning 後置通知,相當於AfterReturningAdvice
- @Around 環繞通知,相當於MethodInterceptor
- @AfterThrowing拋出通知,相當於ThrowAdvice
- @After 最終final通知,不管是否異常,該通知都會執行
- @DeclareParents 引介通知,相當於IntroductionInterceptor (不要求掌握)
在spring容器中開啓AspectJ 註解自動代理機制
使用<aop:aspectj-autoproxy/>
作用:能自動掃描帶有@Aspect的bean,將其作爲增強aop的配置,有點相當於:<aop:config>
<!-- 1。確定目標 -->
<!-- 掃描bean組件 -->
<context:component-scan base-package="cn.itcast.spring"/>
<!-- 2:編寫通知 -->
<!-- 3:配置aop的aspectj的自動代理:
自動掃描bean組件中,含有@Aspect的bean,將其作爲aop管理,開啓動態代理 -->
<aop:aspectj-autoproxy/>
前置通知
//advice通知類增強類
@Component("myAspect")//相當於<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相當於<aop:aspect ref="myAspect">
public class MyAspect {
//前置通知
//相當於:<aop:before method="before" pointcut="bean(*Service)"/>
//@Before("bean(*Service)"):參數值:自動支持切入點表達式或切入點名字
@Before("bean(*Service)")
public void before(JoinPoint joinPoint){
System.out.println("=======前置通知。。。。。");
}
}