Spring AOP面向切面編程

1铲汪、Spring的AOP面向切面編程


1.1、AOP

AOP 為 Aspect Oriented Programming 的縮寫,意思為面向切面編程柠傍,是通過**預(yù)編譯方式和運(yùn)行期動態(tài)代理**實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。
AOP 是 OOP 的延續(xù)辩稽,是軟件開發(fā)中的一個(gè)熱點(diǎn)携兵,也是Spring框架中的一個(gè)重要內(nèi)容,是**函數(shù)式編程的一種衍生范型**搂誉。利用AOP可以對業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離徐紧,從而使得業(yè)務(wù)邏輯各部分之間的**耦合度降低,提高程序的可重用性炭懊,同時(shí)提高了開發(fā)的效率**并级。

1.2、AOP作用及優(yōu)勢

作用:在程序運(yùn)行期間侮腹,在**不修改源代碼**的情況下對**目標(biāo)進(jìn)行功能增強(qiáng)**嘲碧,完成程序代碼之間的**松耦合。**
優(yōu)勢:減少重復(fù)代碼父阻,提高開發(fā)效率愈涩,并且便于維護(hù)。

1.3加矛、AOP底層實(shí)現(xiàn):通過spring動態(tài)代理技術(shù)

實(shí)際上履婉,AOP 的底層是通過 Spring 提供的的動態(tài)代理技術(shù)實(shí)現(xiàn)的。在運(yùn)行期間斟览,Spri6ng通過動態(tài)代理技術(shù)動態(tài)的生成代理對象毁腿,代理對象方法執(zhí)行時(shí)進(jìn)行增強(qiáng)功能的介入,在去調(diào)用目標(biāo)對象的方法苛茂,從而完成功能的增強(qiáng)已烤。

1.4、AOP動態(tài)代理技術(shù)(面試題)

默認(rèn)判斷類是否有接口妓羊,
**JDK代理**:基于接口的動態(tài)代理技術(shù)胯究,有接口時(shí)使用
**cglib**:基于父類的動態(tài)代理技術(shù)
[圖片上傳失敗...(image-7c5fcd-1566476285391)]

1.5、JDK的動態(tài)代理

java
//1)躁绸、目標(biāo)類接口
public interface TargetInterface {
    public void method();
}

//2)裕循、實(shí)現(xiàn)目標(biāo)接口 的 目標(biāo)類
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("目標(biāo)運(yùn)行....");
    }
}

//3)臣嚣、動態(tài)代理代碼, 配置方式增強(qiáng)原有對象费韭,底層是動態(tài)代理茧球,增強(qiáng)代理
final Target target = new Target(); //創(chuàng)建目標(biāo)對象
//創(chuàng)建代理對象
//返回值,動態(tài)生成的代理對象. jdk是基于接口的,所以返回值的類型是接口的子實(shí)現(xiàn)類
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new InvocationHandler() {//調(diào)用代理對象的任何方法,實(shí)際執(zhí)行的invoke方法
            @Override
            public Object invoke(Object proxy,
                         Method method/*正在執(zhí)行目標(biāo)的方法*/,
                         Object[] args/*正在執(zhí)行的方法的參數(shù)*/) 
            throws Throwable {
                System.out.println("前置增強(qiáng)代碼...");
                //啟用日志
                Object invoke = method.invoke(target, args);
                System.out.println("后置增強(qiáng)代碼...");
                return invoke;
            }
        }
);

//4)、調(diào)用代理對象的方法測試
//執(zhí)行目標(biāo)和實(shí)際參數(shù)
method,invoke(useDao.args);
proxy.method();

1.6星持、cglib的動態(tài)代理 沒有接口時(shí)基于父類

java
//1抢埋、目標(biāo)類
public class Target {
    public void method() {
        System.out.println("Target running....");
    }
}
//2、動態(tài)代理代碼
Target target = new Target(); //創(chuàng)建目標(biāo)對象

Enhancer enhancer = new Enhancer();   //創(chuàng)建增強(qiáng)器 cglib提供
enhancer.setSuperclass(Target.class); //設(shè)置父類(目標(biāo)) target.class
enhancer.setCallback(new MethodInterceptor() { //設(shè)置回調(diào) 
    @Override
    public Object intercept(Object o, //proxy代理
                            Method method, 
                            Object[] objects, //args參數(shù)
                            MethodProxy methodProxy
                           ) throws Throwable {
        System.out.println("前置代碼增強(qiáng)....");
        advice.before();前置
        Object invoke = method.invoke(target, objects);//執(zhí)行目標(biāo)
        System.out.println("后置代碼增強(qiáng)....");
        advice.after();后置
        
        return invoke;
    }
});
//創(chuàng)建代理對象
Target proxy = (Target) enhancer.create(); 

//3督暂、調(diào)用代理對象的方法測試
//測試,當(dāng)調(diào)用接口的任何方法時(shí)揪垄,代理對象的代碼都無需修改
proxy.method();

1.7、AOP相關(guān)概念B呶獭<⑴!

spring的AOP實(shí)現(xiàn)底層就是對之前的JDK動態(tài)代理和cglib動態(tài)代理的代碼進(jìn)行封裝八回,之后我們需要關(guān)注部分代碼的編寫酷愧,通過配置文件完成對執(zhí)行目標(biāo)的方法進(jìn)行增強(qiáng)

AOP常見相關(guān)術(shù)語

  • Target(目標(biāo)對象):代理的目標(biāo)對象
  • Proxy(代理):類被AOP織入增強(qiáng)后,產(chǎn)生的結(jié)果代理類缠诅。
  • JoinPoint(連接點(diǎn)):可以被增強(qiáng)的方法(候選人)溶浴,spring只支持方法類型的連接點(diǎn)。
  • PointCut(切入點(diǎn)/切點(diǎn))真正配置被增強(qiáng)的方法(連接點(diǎn))管引。
  • Advice(通知/增強(qiáng))增強(qiáng)連接點(diǎn)的方法士败,包含增強(qiáng)的業(yè)務(wù)邏輯封裝的advice。before前置褥伴,after后置
  • Aspect(切面)切點(diǎn)和通知的結(jié)合
  • Weaving(織入)就是將切點(diǎn)和增強(qiáng)的過程谅将,通過配置文件體現(xiàn),配置的過程可以理解為織入的過程 把增強(qiáng)應(yīng)用到目標(biāo)對象 來創(chuàng)建新的代理對象的過程重慢。spring采用動態(tài)代理織入饥臂,而aspect采用編譯期織入和類裝載期織入

1.8、AOP開發(fā)明確的事項(xiàng)

1)伤锚、需要編寫的內(nèi)容
    - 編寫核心業(yè)務(wù)代碼(目標(biāo)類的目標(biāo)方法)
    - 編寫切面類擅笔,切面類中有通知(增強(qiáng)功能的方法)
    - 在配置文件中,配置織入關(guān)系屯援,即將哪些通知(增強(qiáng))與哪些連接點(diǎn)(切點(diǎn))進(jìn)行結(jié)合
2)、AOP技術(shù)實(shí)現(xiàn)的內(nèi)容
    Spring 框架監(jiān)控切入點(diǎn)方法的執(zhí)行念脯。一旦監(jiān)控到切入點(diǎn)方法被運(yùn)行狞洋,使用代理機(jī)制,動態(tài)創(chuàng)建目標(biāo)對象的代理對象绿店,根據(jù)通知類別(前置,后置等)吉懊,在代理對象的對應(yīng)位置庐橙,將通知對應(yīng)的功能進(jìn)行相應(yīng)的織入,完成完整的代碼邏輯運(yùn)行借嗽。
3)态鳖、AOP底層使用哪種代理方式
    在 spring 中,框架會根據(jù)目標(biāo)類是否實(shí)現(xiàn)了接口來決定采用哪種動態(tài)代理的方式

1.9恶导、小結(jié)知識要點(diǎn)

  • aop:面向切面編程
  • aop底層實(shí)現(xiàn):基于JDK或者基于cglib的動態(tài)代理
  • aop重要概念:
    • pointcut(切入點(diǎn)):被增強(qiáng)的方法
    • Advice(通知/增強(qiáng)):封裝增強(qiáng)業(yè)務(wù)邏輯的方法
    • Aspect(切面):切點(diǎn)+通知
    • Weaving(織入):將切點(diǎn)與通知增強(qiáng)結(jié)合的過程
  • 開發(fā)明確事項(xiàng):
    • 誰是切點(diǎn)(切點(diǎn)表達(dá)式的配置)
    • 誰是通知(切面類中的增強(qiáng)方法)
    • 將切點(diǎn)和通知增強(qiáng)進(jìn)行織入配置

2浆竭、基于XML的AOP開發(fā)


2.1、開發(fā)步驟

①導(dǎo)入 AOP 相關(guān)坐標(biāo)

②創(chuàng)建目標(biāo)接口和目標(biāo)類(內(nèi)部有切點(diǎn))

③創(chuàng)建切面類(內(nèi)部有增強(qiáng)方法)

④將目標(biāo)類和切面類的對象創(chuàng)建權(quán)交給 spring

⑤在 applicationContext.xml 中配置織入關(guān)系

⑥測試代碼

2.2惨寿、代碼演示

//1)    pom文件導(dǎo)入AOP相關(guān)坐標(biāo)
<!--導(dǎo)入spring的context坐標(biāo)邦泄,context依賴aop-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<!-- aspectj的織入 -->
<dependency>
  <groupId>org.aspectj</groupId>//第三方的aop坐標(biāo)
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

//2)、創(chuàng)建目標(biāo)接口和實(shí)現(xiàn)該接口的目標(biāo)類(內(nèi)部有切點(diǎn))
public interface TargetInterface {
    public void method();
}

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

//3)裂垦、創(chuàng)建切面類(增強(qiáng)方法)
public class MyAspect {
    //前置增強(qiáng)方法
    public void before(){
        System.out.println("前置代碼增強(qiáng).....");
    }
}

//4)顺囊、將目標(biāo)類和切面類的對象創(chuàng)建交給spring
<!--配置目標(biāo)類-->
<bean id="target" class="com.ppvir.aop.Target"></bean>
<!--配置切面類-->
<bean id="myAspect" class="com.ppvir.aop.MyAspect"></bean>

//5)、在applicationContext.xml中配置織入關(guān)系
//導(dǎo)入aop命名空間
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

        //配置切點(diǎn)表達(dá)式和前置增強(qiáng)的織入關(guān)系  
        <!--配置織入,告訴框架,哪些方法(切點(diǎn))需要進(jìn)行哪些增強(qiáng)(前置,后置)-->
        <aop:config>
            <!--引用myAspect的Bean為切面對象-->
            <!--聲明切面-->
            <aop:aspect ref="myAspect">
                <!--配置Target的method方法執(zhí)行時(shí)要進(jìn)行myAspect的before方法前置增強(qiáng)-->
                <!--切面, 切點(diǎn)+通知-->
                <aop:before method="before" pointcut="execution(public void com.ppvir.aop.Target.method())"></aop:before>
            </aop:aspect>
        </aop:config>

<beans>

//6)蕉拢、測試代碼
@RunWith(SpringJUnit4ClassRunner.class) //RunWith測試
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired//測試誰就依賴注入誰
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

2.2特碳、Xml配置AOP詳解

1)、切點(diǎn)表達(dá)式的縮略寫法

表達(dá)式語法:
execution([修飾符] 返回值類型 包名.類名.方法名(參數(shù)))

  • 訪問修飾符可以省略
  • 返回值類型晕换、包名午乓、類名、方法名可以使用星號* 代表任意
  • 包名與類名之間一個(gè)點(diǎn) . 代表當(dāng)前包下的類届巩,兩個(gè)點(diǎn) .. 表示當(dāng)前包及其子包下的類
  • 參數(shù)列表可以使用兩個(gè)點(diǎn) .. 表示任意個(gè)數(shù)硅瞧,任意類型的參數(shù)列表

示例:

  • execution(public void com.ppvir.aop.Target.method())
  • execution(void com.ppvir.aop.Target.*(..))
  • execution(* com.ppvir.aop..(..))
  • execution(* com.ppvir.aop...(..))
  • execution(* ...*(..))

2)、通知(增強(qiáng))的類型
通知的配置語法:
<aop:通知類型 method=“切面類中方法名” pointcut=“切點(diǎn)表達(dá)式"></aop:通知類型>

名稱 標(biāo)簽 說明
前置通知 <aop:before> 用于配置前置通知恕汇,指定增強(qiáng)的方法在切點(diǎn)方法之前執(zhí)行
后置通知 <aop:after-returning> 用于配置后置通知腕唧,指定增強(qiáng)的方法在切點(diǎn)方法之后執(zhí)行
環(huán)繞通知 <aop:around> 用于配置環(huán)繞通知,指定增強(qiáng)的方法在切點(diǎn)方法之前和之后都執(zhí)行
異常拋出通知 <aop:throwing> 用于配置異常拋出通知瘾英,指定增強(qiáng)的方法在出現(xiàn)異常時(shí)執(zhí)行
最終通知 <aop:after> 用于配置最終通知枣接,無論增強(qiáng)方式執(zhí)行是否有異常都會執(zhí)行

需要使用方法執(zhí)行前和執(zhí)行后的參數(shù),優(yōu)先使用環(huán)繞通知
環(huán)繞=前置+后置+異常+最終

3)缺谴、切點(diǎn)表達(dá)式的抽取
當(dāng)多個(gè)增強(qiáng)的切點(diǎn)表達(dá)式相同時(shí)但惶,可以將切點(diǎn)表達(dá)式進(jìn)行抽取,在增強(qiáng)中使用 pointcut-ref 屬性代替 pointcut 屬性來引用抽取后的切點(diǎn)表達(dá)式湿蛔。

<aop:config>
    <!--引用myAspect的Bean為切面對象-->
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.ppvir.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>


3膀曾、基于注解的AOP開發(fā)


3.1 基于注解的AOP開發(fā)步驟:

①創(chuàng)建目標(biāo)接口和目標(biāo)類(內(nèi)部有切點(diǎn))

②創(chuàng)建切面類(內(nèi)部有增強(qiáng)方法)

③將目標(biāo)類和切面類的對象創(chuàng)建權(quán)交給 spring

④在切面類中使用注解配置織入關(guān)系

⑤在配置文件中開啟組件掃描和 AOP 的自動代理

⑥測試

3.2 代碼演示

1)、創(chuàng)建目標(biāo)接口和目標(biāo)類(含有切點(diǎn))

public interface TargetInterface {
    public void method();
}

public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

2)阳啥、創(chuàng)建切面類(含有增強(qiáng)方法)

public class MyAspect {
    //前置增強(qiáng)方法
    public void before(){
        System.out.println("前置代碼增強(qiáng).....");
    }
}

3)添谊、將目標(biāo)類和切面類的對象創(chuàng)建交給spring

@Component("target")
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}
@Component("myAspect")
public class MyAspect {
    public void before(){
        System.out.println("前置代碼增強(qiáng).....");
    }
}

4)、在切面類中使用注解配置織入關(guān)系

@Component("myAspect")
@Aspect  //切面
public class MyAspect {
    @Before("execution(* com.ppvir.aop.*.*(..))")
    public void before(){
        System.out.println("前置代碼增強(qiáng).....");
    }
}

5)察迟、在 配置文件中開啟組件掃描和AOP的自動代理

<!--組件掃描-->
<context:component-scan base-package="com.ppvir.aop"/>

<!--aop的自動代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

6)斩狱、測試代碼

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

3.2耳高、注解配置AOP詳解

1)、注解通知的類型
通知的配置語法:@通知注解(“切點(diǎn)表達(dá)式”)

名稱 注解 說明
前置通知 @Before 用于配置前置通知所踊,指定增強(qiáng)的方法在切點(diǎn)方法之前執(zhí)行
后置通知 @AfterReturning 用于配置后置通知泌枪,指定增強(qiáng)的方法在切點(diǎn)方法之后執(zhí)行
環(huán)繞通知 @Around 用于配置環(huán)繞通知,指定增強(qiáng)的方法在切點(diǎn)方法之前和之后都執(zhí)行
異常拋出通知 @AfterThrowing 用于配置異常拋出通知秕岛,指定增強(qiáng)的方法在出現(xiàn)異常時(shí)執(zhí)行
最終通知 @After 用于配置最終通知碌燕,無論增強(qiáng)方式執(zhí)行是否有異常都會執(zhí)行
  1. 切點(diǎn)表達(dá)式的抽取

同 xml配置aop 一樣,我們可以將切點(diǎn)表達(dá)式抽取瓣蛀。抽取方式是在切面內(nèi)定義方法陆蟆,在該方法上使用@Pointcut注解定義切點(diǎn)表達(dá)式,然后在在增強(qiáng)注解中進(jìn)行引用惋增。
具體如下:

@@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("MyAspect.myPoint()")
    public void before(){
        System.out.println("前置代碼增強(qiáng).....");
    }
    @Pointcut("execution(* com.ppvir.aop.*.*(..))")
    public void myPoint(){}
}

3.3叠殷、小結(jié)知識要點(diǎn)

  • 注解aop開發(fā)步驟;
    1.使用@Aspect標(biāo)注切面類
    2.使用@通知注解標(biāo)注通知方法
    3.在配置文件中配置aop自動代理<aop:aspectj-autoproxy/>
  • 通知注解類型
    詳3.2表格
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诈皿,一起剝皮案震驚了整個(gè)濱河市林束,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稽亏,老刑警劉巖壶冒,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異截歉,居然都是意外死亡胖腾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門瘪松,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咸作,“玉大人,你說我怎么就攤上這事宵睦〖欠#” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵壳嚎,是天一觀的道長桐智。 經(jīng)常有香客問我,道長烟馅,這世上最難降的妖魔是什么说庭? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮郑趁,結(jié)果婚禮上口渔,老公的妹妹穿的比我還像新娘。我一直安慰自己穿撮,他們只是感情好缺脉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悦穿,像睡著了一般攻礼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栗柒,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天礁扮,我揣著相機(jī)與錄音,去河邊找鬼瞬沦。 笑死太伊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逛钻。 我是一名探鬼主播僚焦,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼曙痘!你這毒婦竟也來了芳悲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤边坤,失蹤者是張志新(化名)和其女友劉穎名扛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茧痒,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肮韧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旺订。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弄企。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖耸峭,靈堂內(nèi)的尸體忽然破棺而出桩蓉,到底是詐尸還是另有隱情,我是刑警寧澤劳闹,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布院究,位于F島的核電站,受9級特大地震影響本涕,放射性物質(zhì)發(fā)生泄漏业汰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一菩颖、第九天 我趴在偏房一處隱蔽的房頂上張望样漆。 院中可真熱鬧,春花似錦晦闰、人聲如沸放祟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跪妥。三九已至鞋喇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眉撵,已是汗流浹背侦香。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纽疟,地道東北人罐韩。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像污朽,于是被迫代替她去往敵國和親散吵。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容