歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Spring學習之AOP總結帖

Spring學習之AOP總結帖

日期:2017/3/1 9:15:02   编辑:Linux編程

AOP(面向方面編程),也可稱為面向切面編程,是一種編程范式,提供從另一個角度來考慮程序結構從而完善面向對象編程(OOP)。

在進行 OOP 開發時,都是基於對組件(比如類)進行開發,然後對組件進行組合,OOP 最大問題就是無法解耦組件進行開發,比如我們上邊舉例,而 AOP 就是為了克服這個問題而出現的,它來進行這種耦合的分離。AOP 為開發者提供一種進行橫切關注點(比如日志關注點)分離並織入的機制,把橫切關注點分離,然後通過某種技術織入到系統中,從而無耦合的完成了我們的功能。

1 AOP概述

  AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充。AOP 的主要編程對象是切面(aspect), 而切面模塊化橫切關注點。在應用 AOP 編程時, 仍然需要定義公共功能, 但可以明確的定義這個功能在哪裡, 以什麼方式應用, 並且不必修改受影響的類. 這樣一來橫切關注點就被模塊化到特殊的對象(切面)裡。AOP更多概念。

AOP 的好處:

  • 每個事物邏輯位於一個位置, 代碼不分散, 便於維護和升級
  • 業務模塊更簡潔, 只包含核心業務代碼

2 Spring AOP

  在 Spring2.0 以上版本中,可以使用基於 AspectJ 注解或基於 XML 配置的 AOP,AspectJava是Java社區中最完整最流程的AOP框架。

2.1 在Spring中使用AspectJava注解支持

  要在 Spring 應用中使用 AspectJ 注解, 必須在 classpath 下包含 AspectJ 類庫: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar。然後將 aop Schema 添加到 <beans> 根元素中。在 Spring IOC 容器中啟用 AspectJ 注解支持,只要在 Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy>即可,當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時, 會自動為與 AspectJ 切面匹配的 Bean 創建代理。

  在AspectJ注解中,切面只是一個帶有@Aspect注解的Java類,通知是標注有某種注解的簡單Java方法,AspectJ支持以下5種類型的通知注解:

  • @Before: 前置通知, 在方法執行之前執行
  • @After: 後置通知, 在方法執行之後執行
  • @AfterRunning: 返回通知, 在方法返回結果之後執行
  • @AfterThrowing: 異常通知, 在方法拋出異常之後
  • @Around: 環繞通知, 圍繞著方法執行

2.2 利用方法簽名編寫 AspectJ 切入點表達式

  最典型的切入點表達式時根據方法的簽名來匹配各種方法:

  • execution(public void com.aop.HelloImpl.hi()):匹配HelloImpl中返回值為void且無參數的hi方法
  • execution(public void com.aop.HelloImpl.*()):匹配HelloImpl中返回值為void且無參數的所有public方法
  • execution(public void com.aop.HelloImpl.*(..)):匹配HelloImpl中返回值為void的所有public方法
  • execution(public * com.aop.HelloImpl.*(..)):匹配HelloImpl中所有public方法

  在AspectJ中,切入點表達式可以使用操作符&&、||、!結合起來。

// 聲明該方法為前置通知
@Before("execution(public void com.aop.HelloImpl.hi()) || execution(public void com.aop.HelloImpl.hihi(String))")
public void beforeMethod(JoinPoint point) {
    String methodName = point.getSignature().getName();
    System.out.println("HelloAspect beforeMethod : " + methodName);
}

2.3 使用AOP程序示例,使用了全部的5種通知

  首先定義一個Hello接口,代碼如下:

public interface Hello {
    public void hi();
}

  然後定義一個Hello實現類HelloImpl,代碼如下:

@Component
public class HelloImpl implements Hello {
    @Override
    public void hi() {
        System.out.println("HelloImp hi()...");
    }
}

  然後定義一個Hello的切面類HelloAspect,然後如下所示,注意:Hello接口、HelloImpl類、HelloAspect類都是在com.aop包下的。

@Aspect
@Component
public class HelloAspect {

  // 聲明該方法為前置通知
  @Before("execution(public void com.aop.HelloImpl.hi()) || execution(public void com.aop.HelloImpl.hihi(String))")
  public void beforeMethod(JoinPoint point) {
      String methodName = point.getSignature().getName();
      System.out.println("HelloAspect beforeMethod : " + methodName);
  }

    // 聲明該方法為後置通知,無論該方法是否發生異常
    @After("execution(public void com.aop.HelloImpl.*())")
    public void endMethod(JoinPoint point) {
        String methodName = point.getSignature().getName();
        System.out.println("HelloAspect afterMethod : " + methodName);
    }

    // 返回通知,正常返回時的通知,不包括異常,可以訪問到方法的方繪制
    @AfterReturning(value = "execution(public void com.aop.HelloImpl.*())", returning = "result")
    public void afterReturning(JoinPoint point, Object result) {
        String methodName = point.getSignature().getName();
        System.out.println("HelloAspect afterReturning : " + methodName + ", result: " + result);
    }

    // 異常通知
    @AfterThrowing(value = "execution(public void com.aop.HelloImpl.*())", throwing = "ex")
    public void afterThrowing(JoinPoint point) {
        String methodName = point.getSignature().getName();
        System.out.println("HelloAspect afterReturning : " + methodName);
    }

    /**
     * 環繞通知需攜帶 ProceedingJoinPoint 類型的參數。
     * 環繞通知類似於動態代理的全過程, ProceedingJoinPoint 類型的參數可以決定是否執行目標方法。
     * 環繞通知必須有返回值,且返回值是目標方法的返回值
     */
    @Around("execution(public void com.aop.HelloImpl.*())")
    public Object aroundMethod(ProceedingJoinPoint point) {
        Object result = null;

        // 環繞通知(前通知)
        System.out.println("HelloAspect aroundMethod start...");
        try {
            // 前置通知
            result = point.proceed(); // 目標方法執行
        } catch (Throwable throwable) {
            // 異常通知
            throwable.printStackTrace();
        }
        // 環繞通知(後通知)
        System.out.println("HelloAspect aroundMethod end...");
        // 後置通知
        // 返回通知

        return result;
    }
}

  配置aopContext.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="com.aop"/>

    <!-- 使用AspjectJ自動生成代理對象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

  測試類AopTest類:

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aopContext.xml");

        Hello hello = context.getBean(Hello.class);

        hello.hi();
    }
}

  最後的輸出結果為:

  現在問題來了,各種通知的順序是如何呢?其實從輸出結果中也可以看出來。同一個類中的各種通知執行順序是:

Around之前通知 >= Before通知 >= 目標方法執行 >= Around之後通知 >= After通知 >= AfterReturn通知

2.4 指定切面的優先級

  在同一個連接點上應用不止一個切面時, 除非明確指定, 否則它們的優先級是不確定的。切面的優先級可以通過實現 Ordered 接口或利用 @Order 注解指定。實現 Ordered 接口, getOrder() 方法的返回值越小, 優先級越高.若使用 @Order 注解, 序號出現在注解中。

@Aspect
@Order(0)
@Component
public class HelloAspect2 { }

@Aspect
@Order(1)
@Component
public class HelloAspect { }

2.5 重用切入點定義

  在編寫 AspectJ 切面時, 可以直接在通知注解中書寫切入點表達式. 但同一個切點表達式可能會在多個通知中重復出現。在 AspectJ 切面中, 可以通過 @Pointcut 注解將一個切入點聲明成簡單的方法. 切入點的方法體通常是空的, 因為將切入點定義與應用程序邏輯混在一起是不合理的。切入點方法的訪問控制符同時也控制著這個切入點的可見性. 如果切入點要在多個切面中共用, 最好將它們集中在一個公共的類中. 在這種情況下, 它們必須被聲明為 public. 在引入這個切入點時, 必須將類名也包括在內. 如果類沒有與這個切面放在同一個包中, 還必須包含包名。其他通知可以通過方法名稱引入該切入點。

@Aspect
@Component
public class HelloAspect {

    @Pointcut("execution(public void com.aop.HelloImpl.hi())")
    private void operate() { }

    // 聲明該方法為前置通知
    @Before("operate()")
    public void beforeMethod(JoinPoint point) {
        String methodName = point.getSignature().getName();
        System.out.println("HelloAspect beforeMethod : " + methodName);
    }
} 

3 基於XML的配置聲明切面

  除了使用 AspectJ 注解聲明切面, Spring 也支持在 Bean 配置文件中聲明切面. 這種聲明是通過 aop schema 中的 XML 元素完成的。正常情況下, 基於注解的聲明要優先於基於 XML 的聲明. 通過 AspectJ 注解, 切面可以與 AspectJ 兼容, 而基於 XML 的配置則是 Spring 專有的. 由於 AspectJ 得到越來越多的 AOP 框架支持, 所以以注解風格編寫的切面將會有更多重用的機會。

3.1 聲明切面

  XML中聲明切面時,需要在<beans>根元素中導入aop schema,在Bean配置文件中,所有的Spring AOP配置都必須定義在<aop:config>元素內部,對於每個切面來說,都要創建一個<aop:aspect>元素來為具體的切面實現引用後端Bean實例。

<aop:config>
    <aop:aspect ref="helloAspect">
        <aop:before pointcut="execution(public void com.aop.HelloImpl.*())" method="beforeMethod"/>
    </aop:aspect>
    <aop:aspect ref="helloAspect2">
        <aop:before pointcut="execution(public void com.aop.HelloImpl.*())" method="beforeMethod"/>
    </aop:aspect>
</aop:config>

3.2 聲明切入點和通知

  切入點使用<aop:pointcut>元素聲明,切入點必須定義在<aop:sapect>元素下,或者直接定義在<aop:config>元素下。

  • 定義在 <aop:aspect> 元素下: 只對當前切面有效
  • 定義在 <aop:config> 元素下: 對所有切面都有效

  通知元素使用<pointcut-ref>來引用切入點,或直接把<pointcut>嵌入到切入點表達式中,method屬性指定切面類中通知方法的名稱。

<aop:config>
    <aop:pointcut id="pointcut" expression="execution(public void com.aop.HelloImpl.*())"/>
    <aop:aspect ref="helloAspect">
        <aop:before pointcut-ref="pointcut" method="beforeMethod"/>
    </aop:aspect>
    <aop:aspect ref="helloAspect2">
        <aop:before pointcut-ref="pointcut" method="beforeMethod"/>
    </aop:aspect>
</aop:config>

Spring中如何配置Hibernate事務 http://www.linuxidc.com/Linux/2013-12/93681.htm

Struts2整合Spring方法及原理 http://www.linuxidc.com/Linux/2013-12/93692.htm

基於 Spring 設計並實現 RESTful Web Services http://www.linuxidc.com/Linux/2013-10/91974.htm

Spring-3.2.4 + Quartz-2.2.0集成實例 http://www.linuxidc.com/Linux/2013-10/91524.htm

使用 Spring 進行單元測試 http://www.linuxidc.com/Linux/2013-09/89913.htm

運用Spring注解實現Netty服務器端UDP應用程序 http://www.linuxidc.com/Linux/2013-09/89780.htm

Spring 3.x 企業應用開發實戰 PDF完整高清掃描版+源代碼 http://www.linuxidc.com/Linux/2013-10/91357.htm

Spring 的詳細介紹:請點這裡
Spring 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved