慢慢來(lái)比較快舀凛,虛心學(xué)技術(shù)
前言:DI (依賴(lài)注入)有助于應(yīng)用對(duì)象之間的解耦句携,而 AOP(面向切面編程) 可以實(shí)現(xiàn)橫切關(guān)注點(diǎn)與它們所影響的對(duì)象之間的解耦
一、什么是面向切面編程
Ⅰ呈昔、橫切關(guān)注點(diǎn):在軟件開(kāi)發(fā)中达椰,散布于應(yīng)用中多處的功能被稱(chēng)為橫切關(guān)注點(diǎn)( cross-cutting concern )【比如說(shuō)日志,安全和事務(wù)管理等】屎篱。通常來(lái)講服赎,這些橫切關(guān)注點(diǎn)從概念上是與應(yīng)用的業(yè)務(wù)邏輯相分離的(但是往往會(huì)直接嵌入到應(yīng)用的業(yè)務(wù)邏輯之中)。把這些橫切關(guān)注點(diǎn)與業(yè)務(wù)邏輯相分離正是面向切面編程( AOP )所要解決的問(wèn)題交播。
Ⅱ重虑、切面:橫切關(guān)注點(diǎn)可以被模塊化為特殊的類(lèi),這些類(lèi)被稱(chēng)為切面( aspect )秦士。
Ⅲ缺厉、AOP術(shù)語(yǔ)
①通知( Advice ):切面的工作被稱(chēng)為通知。通知描述切面的工作隧土,同時(shí)決定切面何時(shí)工作【定義了切面工作做什么芽死,什么時(shí)候做】
- 前置通知( Before ):在目標(biāo)方法被調(diào)用之前調(diào)用通知功能;
- 后置通知( After ):在目標(biāo)方法完成之后調(diào)用通知次洼,此時(shí)不會(huì)關(guān)心方法的輸出是什么关贵;
- 返回通知( After-returning ):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知;
- 異常通知( After-throwing ):在目標(biāo)方法拋出異常后調(diào)用通知卖毁;
- 環(huán)繞通知( Around ):通知包裹了被通知的方法揖曾,在被通知的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義的行為
②連接點(diǎn)( Join point ):觸發(fā)切面工作的點(diǎn),比如方法執(zhí)行亥啦,異常拋出等行為
③切點(diǎn)( Poincut ):決定切面工作的地方炭剪,比如某個(gè)方法等【定義了切面工作在哪里做】
④切面( Aspect ):通知和切點(diǎn)的結(jié)合【面】
⑤引入( Introduction ):允許我們向現(xiàn)有的類(lèi)添加新方法或?qū)傩?/p>
⑥織入( Weaving ):把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程【切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中】
通知包含了需要用于多個(gè)應(yīng)用對(duì)象的橫切行為;連接點(diǎn)是程序執(zhí)行過(guò)程中能夠應(yīng)用通知的所有點(diǎn)翔脱;切點(diǎn)定義了通知被應(yīng)用的具體位置(在哪些連接點(diǎn))奴拦。其中關(guān)鍵的概念是切點(diǎn)定義了哪些連接點(diǎn)會(huì)得到通知
Ⅳ、Spring對(duì)AOP的支持【 Spring AOP 構(gòu)建在動(dòng)態(tài)代理基礎(chǔ)之上届吁,因此错妖, Spring 對(duì) AOP 的支持局限于方法攔截【毋澹】
- 基于代理的經(jīng)典 Spring AOP 暂氯;
- 純 POJO 切面;
- @AspectJ 注解驅(qū)動(dòng)的切面亮蛔;
- 注入式 AspectJ 切面(適用于 Spring 各版本)痴施。
二、面向切面編程實(shí)現(xiàn)
1、定義切點(diǎn):
Spring支持通過(guò)AspectJ的切點(diǎn)表達(dá)式語(yǔ)言來(lái)定義 Spring 切面辣吃,同時(shí)增加通過(guò)bean的id指定bean的寫(xiě)法动遭。
如:execution( com.my.spring.bean..(..))* 指定com.my.spring.bean包下所有類(lèi)的所有方法作為切點(diǎn)
其結(jié)構(gòu)解析如下:
AspectJ切點(diǎn)表達(dá)式的指示器不只有execution:
- arg() :限制連接點(diǎn)匹配參數(shù)為指定類(lèi)型的執(zhí)行方法
- execution() :用于匹配是連接點(diǎn)的執(zhí)行方法
- this() :限制連接點(diǎn)匹配 AOP 代理的 bean 引用為指定類(lèi)型的類(lèi)
- target :限制連接點(diǎn)匹配目標(biāo)對(duì)象為指定類(lèi)型的類(lèi)
- within() :限制連接點(diǎn)匹配指定的類(lèi)型
各指示器之間可以通過(guò)&&(與),||(或)神得,!(非)連接符進(jìn)行連接實(shí)現(xiàn)多條件查詢(xún)定義節(jié)點(diǎn)
如:execution(* com.my.spring.bean.* . *(..))&&arg(java.lang.Integer)
2.示例Demo
Spring AOP的實(shí)現(xiàn)依賴(lài)于spring-aop包和aspectjweaver包厘惦,需在pom文件引入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
通過(guò)實(shí)例demo引入概念:
①定義一個(gè)基礎(chǔ)接口類(lèi)BaseInterface和實(shí)現(xiàn)類(lèi)BaseBean
public interface BaseInterface {
/**
* 新增歌曲
*
* @param author 作者
* @param songTitle 歌曲名
*
* @return java.lang.Integer 返回當(dāng)前歌曲總數(shù)
*
* @author xxx 2019/3/4
* @version 1.0
**/
Integer addSong(String author,String songTitle);
/**
* 刪除歌曲
*
* @param author 作者
* @param songTitle 歌曲名
*
* @return java.lang.Integer 返回當(dāng)前歌曲總數(shù)
*
* @author xxx 2019/3/4
* @version 1.0
**/
Integer delSong(String author,String songTitle);
}
@Component
public class BaseBean implements BaseInterface{
private String author;
private String songTitle;
private Integer count=0;
@Override
public Integer addSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("新增了一首歌:"+author+"-"+songTitle);
count++;
return count;
}
@Override
public Integer delSong(String author,String songTitle){
this.author = author;
this.songTitle = songTitle;
System.out.println("刪除了一首歌:"+author+"-"+songTitle);
count--;
return count;
}
}
②創(chuàng)建一個(gè)切面類(lèi)
@Aspect
@Component
public class BaseBeanAspect {
private Logger logger = LoggerFactory.getLogger(BaseBeanAspect.class);
/**
* 方法執(zhí)行前的通知
*/
@Before("execution(* com.my.spring.bean.*.*(..))")
public void beforeInvoke(){
logger.debug("方法執(zhí)行前");
}
/**
* 方法執(zhí)行后的通知
*/
@After("execution(* com.my.spring.bean.*.*(..))")
public void afterInvoke(){
logger.debug("方法執(zhí)行后");
}
/**
* 方法執(zhí)行返回后的通知
*/
@AfterReturning("execution(* com.my.spring.bean.*.*(..))")
public void afterReturning(){
logger.debug("==================方法執(zhí)行完成");
}
/**
* 方法拋出異常的通知
*/
@AfterThrowing("execution(* com.my.spring.bean.*.*(..))")
public void afterThrowing(){
logger.debug("==================方法執(zhí)行報(bào)錯(cuò)");
}
}
③創(chuàng)建自動(dòng)化裝配的配置類(lèi)
@Configuration
@EnableAspectJAutoProxy//開(kāi)啟自動(dòng)代理開(kāi)關(guān),啟用切面
@ComponentScan(basePackages = {"com.my.spring"})
public class ComponentConfig {
}
④測(cè)試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ComponentConfig.class})
public class AppTest {
@Autowired
private BaseInterface baseInterface;
@Test
public void testBean(){
baseInterface.addSong("myBean","mySong");
baseInterface.delSong("myBean","mySong");
}
}
⑤測(cè)試結(jié)果
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行前
新增了一首歌:myBean-mySong
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行后
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法執(zhí)行完成
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行前
刪除了一首歌:myBean-mySong
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行后
2019-03-04 14:32:55.019 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法執(zhí)行完成
3循头、注解分析
前代碼是使用注解實(shí)現(xiàn)SpringAOP的簡(jiǎn)單示例,我們了解以下其中用到的注解和實(shí)現(xiàn):
Ⅰ炎疆、定義切面:@Aspect------------標(biāo)識(shí)當(dāng)前類(lèi)是一個(gè)切面類(lèi)(僅僅只是標(biāo)識(shí)卡骂,我們可以看到該注解源碼并沒(méi)有使用@Component注解,所以使用該注解的bean依舊只是一個(gè)普通的POJO形入,使用時(shí)依舊需要顯式或自動(dòng)裝配)
Ⅱ全跨、定義切點(diǎn):@PointCut
我們看到上述代碼中在多個(gè)地方使用切點(diǎn)使用的是重復(fù)性的表達(dá)式,其實(shí)通過(guò)@PointCut注解定義切點(diǎn)亿遂,同時(shí)通過(guò)指定空方法名引入切點(diǎn)到各個(gè)通知即可
@Pointcut("execution(* com.my.spring.bean.*.*(..))")
public void pointCut(){//被用于標(biāo)識(shí)的空方法
}
@Before("pointCut()")//以切點(diǎn)方法名引入
public void beforeInvoke(){
logger.debug("方法執(zhí)行前");
}
Ⅱ浓若、定義通知:
@Beafore(切點(diǎn))-----------------切點(diǎn)方法執(zhí)行前的通知
@After(切點(diǎn))-------------------------切點(diǎn)方法執(zhí)行后的通知
@AfterReturning(切點(diǎn))-----------切點(diǎn)方法執(zhí)行返回后的通知
@AfterThrowing(切點(diǎn))------------切點(diǎn)方法拋異常后的通知
Ⅲ、開(kāi)啟自動(dòng)代理:@EnableAspectJAutoProxy--------在配置類(lèi)中使用蛇数,如果不啟用的話挪钓,編寫(xiě)的切面將不生效
Ⅳ、定義環(huán)繞通知:@Around("pointCut()")
環(huán)繞通知是從方法執(zhí)行前一直包裹直到方法執(zhí)行完成后的一個(gè)通知耳舅,用的比較多碌上,其中被定義的方法需要引入?yún)?shù)ProceedingJoinPoint,ProceedingJoinPoint對(duì)象封裝了當(dāng)前運(yùn)行對(duì)象的具體信息,簡(jiǎn)單實(shí)現(xiàn)如下:
@Around("pointCut()")
public void aroundInvoke(ProceedingJoinPoint jp){
try {
logger.debug("=====================環(huán)繞執(zhí)行方法開(kāi)始");
Signature signature = jp.getSignature();
String methodName = signature.getName();
MethodSignature methodSignature = (MethodSignature) signature;
logger.debug("方法名:{}",methodName);
List<Object> args = Arrays.asList(jp.getArgs());
logger.debug("參數(shù)列表:{}",args);
Class<?> returnType = methodSignature.getMethod().getReturnType();
logger.debug("方法返回類(lèi)型:{}",returnType);
Object proceed = jp.proceed();
logger.debug("======================環(huán)繞執(zhí)行方法結(jié)束浦徊,方法執(zhí)行結(jié)果:{}",proceed);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
其中馏予,ProceedingJoinPoint對(duì)象源碼分析如下:
JoinPoint
java.lang.Object[] getArgs()//獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜恚?
Signature getSignature() //獲取連接點(diǎn)的方法簽名對(duì)象;
java.lang.Object getTarget() //獲取連接點(diǎn)所在的目標(biāo)對(duì)象盔性;
java.lang.Object getThis() //獲取代理對(duì)象本身霞丧;
ProceedingJoinPoint繼承JoinPoint子接口,它新增了兩個(gè)用于執(zhí)行連接點(diǎn)方法的方法
java.lang.Object proceed() throws java.lang.Throwable:通過(guò)反射執(zhí)行目標(biāo)對(duì)象的連接點(diǎn)處的方法冕香;
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通過(guò)反射執(zhí)行目標(biāo)對(duì)象連接點(diǎn)處的方法蛹尝,不過(guò)使用新的入?yún)⑻鎿Q原來(lái)的入?yún)ⅰ?
4、XML實(shí)現(xiàn)切面編程
我們知道使用注解實(shí)現(xiàn)切面編程是很方便直接的悉尾,但是有時(shí)候我們并不一定擁有通知類(lèi)的源碼箩言,也就無(wú)法給對(duì)應(yīng)的方法添加注解,這時(shí)候就需要使用XML配置實(shí)現(xiàn)了焕襟。XML配置實(shí)現(xiàn)與注解實(shí)現(xiàn)十分類(lèi)似陨收,我們可以看一下基本的實(shí)現(xiàn)節(jié)點(diǎn):
①定義切面:<aop:aspect ref="切面類(lèi)在xml文件中對(duì)應(yīng)bean的id">
②定義切點(diǎn):<aop:pointcut id="切點(diǎn)id" expression="切點(diǎn)表達(dá)式"/>
③定義通知:
<aop:beafore method="通知方法名" pointcut-ref="切點(diǎn)id"/>-------------定義方法執(zhí)行前的通知
<aop:after method="通知方法名" pointcut-ref="切點(diǎn)id"/>-------------定義方法執(zhí)行后的通知
<aop:afterReturning method="通知方法名" pointcut-ref="切點(diǎn)id"/>-------------定義方法執(zhí)行返回后的通知
<aop:afterThrowing method="通知方法名" pointcut-ref="切點(diǎn)id"/>-------------定義方法執(zhí)行拋出異常后的通知
<aop:around method="通知方法名" pointcut-ref="切點(diǎn)id"/>-------------定義方法環(huán)繞通知
④開(kāi)啟自動(dòng)代理:<aop:aspectj-autoproxy/>
⑤表示aop配置:<aop:config></aop:config>
除了開(kāi)啟自動(dòng)代理,aop的所有節(jié)點(diǎn)都需要包含在<aop:config></aop:config>節(jié)點(diǎn)中,如下demo演示:
applicaiton.xml
<?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"
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">
<!--開(kāi)啟自動(dòng)代理-->
<aop:aspectj-autoproxy/>
<!--裝配基本類(lèi)-->
<bean class="com.my.spring.bean.BaseBean" id="baseBean" name="baseBean"/>
<!--裝配切面類(lèi)-->
<bean class="com.my.spring.aspect.BaseBeanAspect" id="baseBeanAspect"/>
<!--aop配置-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="baseBeanAspect">
<!--定義切點(diǎn)-->
<aop:pointcut id="pointCut" expression="execution(* com.my.spring.bean.*.*(..))"/>
<!--定義前置通知-->
<aop:before method="beforeInvoke" pointcut-ref="pointCut"/>
<!--定義后置通知-->
<aop:after method="afterInvoke" pointcut-ref="pointCut"/>
<!--定義方法執(zhí)行返回后通知-->
<aop:after-returning method="afterReturning" pointcut-ref="pointCut"/>
<!--定義方法異常后通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut"/>
<!--定義方法環(huán)繞通知通知-->
<aop:around method="aroundInvoke" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
將BaseBean和BaseBeanAspect類(lèi)中的注解去除务漩,編寫(xiě)測(cè)試類(lèi):
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application.xml"})//將配置文件作為裝配環(huán)境
public class AppXMLTest {
@Autowired
private BaseInterface baseInterface;
@Test
public void testBean(){
baseInterface.addSong("Mr D","The World!!");
baseInterface.delSong("Mr D","The World!!");
}
}
測(cè)試結(jié)果:
2019-03-04 22:07:37.901 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行前
2019-03-04 22:07:37.903 DEBUG com.my.spring.aspect.BaseBeanAspect - =====================環(huán)繞執(zhí)行方法開(kāi)始
2019-03-04 22:07:37.907 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法名:addSong
2019-03-04 22:07:37.910 DEBUG com.my.spring.aspect.BaseBeanAspect - 參數(shù)列表:[Mr D, The World!!]
2019-03-04 22:07:37.910 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法返回類(lèi)型:class java.lang.Integer
2019-03-04 22:07:37.910 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行前
新增了一首歌:Mr D-The World!!
2019-03-04 22:07:37.911 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法執(zhí)行完成
2019-03-04 22:07:37.911 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行后
2019-03-04 22:07:37.911 DEBUG com.my.spring.aspect.BaseBeanAspect - ======================環(huán)繞執(zhí)行方法結(jié)束拄衰,方法執(zhí)行結(jié)果:1
2019-03-04 22:07:37.912 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法執(zhí)行完成
2019-03-04 22:07:37.912 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行后
2019-03-04 22:07:37.915 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行前
2019-03-04 22:07:37.915 DEBUG com.my.spring.aspect.BaseBeanAspect - =====================環(huán)繞執(zhí)行方法開(kāi)始
2019-03-04 22:07:37.916 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法名:delSong
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 參數(shù)列表:[Mr D, The World!!]
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法返回類(lèi)型:class java.lang.Integer
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行前
刪除了一首歌:Mr D-The World!!
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法執(zhí)行完成
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行后
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - ======================環(huán)繞執(zhí)行方法結(jié)束,方法執(zhí)行結(jié)果:0
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - ==================方法執(zhí)行完成
2019-03-04 22:07:37.917 DEBUG com.my.spring.aspect.BaseBeanAspect - 方法執(zhí)行后