Spring AOP 詳解以及 Spring 使用 XML 配置 AOP

一、概述

1、AOP

  • 全稱是 Aspect Oriented Programming 即:面向切面編程
  • AOP 就是把我們程序重複的代碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改源碼的基礎上,對我們的已有方法進行增強。
  • AOP:面向切面編程,aop就是在某一個類或方法執行前後打個標記,聲明在執行到這裏之前要先執行什麼,執行完這裏之後要接着執行什麼,插入新的執行方法。在Spring中,它是以JVM的動態代理技術爲基礎,然後設計一系列AOP橫切實現,比如前置通知、返回通知、異常通知等,同時Pointcut接口來匹配切入點,可以使用現有切入點來設計橫切面,也可以擴展相關方法根據需求進行切入。

2、作用:

  • 在程序運行期間,不修改源碼對已有方法進行增強。

3、優勢:

  • 減少重複代碼
  • 提高開發效率
  • 維護方便

4、AOP 的實現方式

  • 使用動態代理技術
    關於動態代理請點擊

二、AOP(面向切面編程)

1、AOP 相關術語

  • Joinpoint(連接點) : 是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只支持方法類型的連接點。
  • Pointcut(切入點) : 是指我們要對哪些 Joinpoint 進行攔截的定義。
  • Advice(通知/增強) : 所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。
    通知的類型 :前置通知,後置通知,異常通知,最終通知,環繞通知
  • Introduction(引介) : 是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期爲類動態地添加一些方法或 Field。
  • Target(目標對象) : 代理的目標對象。
  • Weaving(織入) : 是指把增強應用到目標對象來創建新的代理對象的過程。
    spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。
  • Proxy(代理): 一個類被 AOP 織入增強後,就產生一個結果代理類。
  • Aspect(切面) : 是切入點和通知(引介)的結合。

2、spring 中的 AOP 要明確的事

a、開發階段(程序員做的)

  • 編寫核心業務代碼(開發主線):大部分程序員來做,要求熟悉業務需求。
  • 把公用代碼抽取出來,製作成通知。(開發階段最後再做):AOP 編程人員來做。
  • 在配置文件中,聲明切入點與通知間的關係,即切面。:AOP 編程人員來做。

b、運行階段(Spring 框架完成的)

  • Spring 框架監控切入點方法的執行。一旦監控到切入點方法被運行,使用代理機制,動態創建目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的代碼邏輯運行。

三、XML 配置 AOP 步驟

第一步:配置要搜索的包

<context:component-scan base-package="cn.lemon"></context:component-scan>

第二步:使用 aop:config 聲明 aop 配置

  • 作用:用於聲明開始 aop 的配置
<!--aop 配置
     # proxy-target-class="true":使用CGLib代理,默認是 接口代理(JDK代理)
-->
<aop:config proxy-target-class="true">
        <!--配置的代碼都寫在此處-->
</aop:config>

第三步:使用 aop:aspect 配置切面

  • 作用:用於配置切面。
  • 屬性:id:給切面提供一個唯一標識。ref:引用配置好的通知類 bean 的 id。
<aop:aspect ref="loggerAdvice">
     <!--配置通知的類型要寫在此處-->
</aop:aspect>

第四步:使用 aop:pointcut 配置切入點表達式

  • 作用:用於配置切入點表達式。就是指定對哪些類的哪些方法進行增強。
  • 屬性:expression:用於定義切入點表達式。id:用於給切入點表達式提供一個唯一標識
<!--切入點表達式:定義那些方法織入增強-->
<aop:pointcut id="serviceMethod" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut><!--切入點-->

切入點表達式說明
表達式語法:execution([修飾符] 返回值類型 包名.類名.方法名(參數))
全匹配方式:

public void cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)

訪問修飾符可以省略

void cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)

返回值可以使用*號,表示任意返回值

* cn.lemon.service.impl.AccountServiceImpl.addAccount(cn.lemon.domain.Account)

包名可以使用*號,表示任意包,但是有幾級包,需要寫幾個*

 * *.*.*.*.AccountServiceImpl.addAccount(cn.lemon.domain.Account)

使用..來表示當前包,及其子包

 * cn..AccountServiceImpl.addAccount(cn.lemon.domain.Account)

類名可以使用*號,表示任意類

 * cn..*.addAccount(cn.lemon.domain.Account)

方法名可以使用*號,表示任意方法:

 * cn..*.*( cn.lemon.domain.Account)

參數列表可以使用*,表示參數可以是任意數據類型,但是必須有參數:

 * com..*.*(*)

參數列表可以使用..表示有無參數均可,有參數可以是任意類型:

 * com..*.*(..)

全通配方式:

 * *..*.*(..)

注意:
通常情況下,我們都是對業務層的方法進行增強,所以切入點表達式都是切到業務層實現類。

execution(* com.lxs.service.impl.*.*(..))

第五步:使用 aop:xxx 配置對應的通知類型

<aop:before method="before" pointcut-ref="serviceMethod"></aop:before><!--織入前置增強-->
<aop:after-returning method="afterReturning" pointcut-ref="serviceMethod" returning="result"></aop:after-returning><!--後置增強-->
<aop:around method="around" pointcut-ref="serviceMethod"></aop:around><!--環繞增強-->
<aop:after-throwing method="exception" pointcut-ref="serviceMethod" <aop:after method="after" pointcut-ref="serviceMethod"></aop:after><!--最終增強-->

在這裏插入圖片描述

四、Spring 使用 XML 配置 AOP

1、新建持久層(dao),接口以及實現類

package cn.lemon.dao;

public interface IAccountDao {
    void in(Double money);

    void out(Double money);
}
package cn.lemon.dao.impl;

import cn.lemon.dao.IAccountDao;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements IAccountDao {
    @Override
    public void in(Double money) {
        System.out.println("成功轉入" + money + "元錢");
    }

    @Override
    public void out(Double money) {
        System.out.println("成功轉出" + money + "元錢");
    }
}

2、新建業務層(service),接口以及實現類

package cn.lemon.service;

public interface IAccountService {
    void transfer(Double money);
}
package cn.lemon.service.impl;

import cn.lemon.dao.IAccountDao;
import cn.lemon.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServcieImpl implements IAccountService {
    @Autowired
    private IAccountDao iAccountDao;

    @Override
    public void transfer(Double money) {
        iAccountDao.in(money);
        iAccountDao.out(money);
    }
}

3、增強類

package cn.lemon.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

@Component
public class LoggerAdvice {
    /**
     *
     * @param joinPoint 得到業務方法的信息
     *                  getSignature() 表示簽名
     */
    public void before(JoinPoint joinPoint){
        System.out.println("開啓事務:前置增強,方法名爲:" + joinPoint.getSignature().getName());
    }
    public void afterReturning(JoinPoint joinPoint,Object result){
        System.out.println("提交事務:後置增強,方法名爲:" + joinPoint.getSignature().getName() + "返回值爲:" + result);
    }

    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("環繞前置增強.......");
        //執行業務方法
        Object result = proceedingJoinPoint.proceed();
        System.out.println("環繞後置增強。。。。。。");
        return result;
    }

    public void exception(JoinPoint joinPoint,Exception e){
        System.out.println("異常增強,異常信息爲:" + e.getMessage());
    }

    public void after(JoinPoint joinPoint){
        System.out.println("最終增強,方法名爲;" + joinPoint.getSignature().getName());
    }
}

4、AOP 的 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:context="http://www.springframework.org/schema/context"
       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/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">
    <context:component-scan base-package="cn.lemon"></context:component-scan>

    <!--aop 配置
     # proxy-target-class="true":使用CGLib代理,默認是 接口代理(JDK代理)
    -->
    <aop:config proxy-target-class="true">
        <!--切入點表達式:定義那些方法織入增強-->
        <aop:pointcut id="serviceMethod" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut><!--切入點-->
        <aop:aspect ref="loggerAdvice">
            <aop:before method="before" pointcut-ref="serviceMethod"></aop:before><!--織入前置增強-->
            <aop:after-returning method="afterReturning" pointcut-ref="serviceMethod" returning="result"></aop:after-returning><!--後置增強-->
            <aop:around method="around" pointcut-ref="serviceMethod"></aop:around><!--環繞增強-->
            <aop:after-throwing method="exception" pointcut-ref="serviceMethod" throwing="e"></aop:after-throwing><!--異常增強-->
            <aop:after method="after" pointcut-ref="serviceMethod"></aop:after><!--最終增強-->
        </aop:aspect>
    </aop:config>
</beans>

5、測試類

package cn.lemon.service.impl;

import cn.lemon.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class AccountServcieImplTest {
    @Autowired
    private IAccountService iAccountService;

    @Test
    public void transfer() {
        iAccountService.transfer(1000d);
    }
}

五、Spring 使用註解&XML 配置 AOP

寫註解文件 MyAnnotation.java

package cn.lemon.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、
 * 類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)
 * 取值(ElementType.METHOD   用於描述方法
 * RetentionPolicy.RUNTIME:註解不僅被保存到class文件中,jvm加載class文件之後,仍然存在;
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaiton {
}

在配置文件中寫上註解的切入點
在這裏插入圖片描述
在方法上加上註解

package cn.lemon.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、
 * 類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)
 * 取值(ElementType.METHOD   用於描述方法
 * RetentionPolicy.RUNTIME:註解不僅被保存到class文件中,jvm加載class文件之後,仍然存在;
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaiton {
    
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章