about 1 year ago

What is AOP ?

Introduction

Spring Aspect-Oriented Programming簡稱為AOP,這個模組提供了另外一種思考模式,經由實現crosscutting concerns組成Aspect,並且來達到類別與對象之間的事務管理,如日誌系統等等。

正常我們沒有使用Aspect的情況下,會將crosscutting concerns埋入商業邏輯中,這樣其實當有修改這個邏輯需求時,是有可能需要移除或者新增與修改crosscutting concerns的動作,若今天使用Spring AOP模組,我們可以將crosscutting concern完全拉出來進行撰寫,儘管後續修改商業邏輯,也不太需要理會crosscutting concern的物件。

AOP concepts(概念)

在Spring官方說明,這些AOP的術語並不是他們所進行訂製的,但也是非常的難以理解,下面將簡單介紹。

Aspect

Aspect最大的作用即是收集各商務物件中的crosscutting concerns。在Spring中除了使用XML設定方式,也建議可以使用Annotation方式進行開發,會較於簡單。

Join point

Aspect在應用程式執行時加入商務流程的點或時機稱之為Joinpoint,被呼叫的時機有可能在某個方法的前面(@before)或後面(@after),然而Joinpoint還可以讓我們取到傳入該流程的參數以及結果,也能夠攔截例外處理。

Advice

顧名思義,Advice將會處理有使用“around”、“before”和“after”等不同類型的通知,這部分後面看程式碼會較於清楚。

Pointcut

這部分可以看成,對於哪一個class的某個method進行關聯的動作,讓Advice可以知道我們要從哪個method進行通知處理。

Introduction

這不要翻譯成介紹,稱之為引入較為適當,在Java很不幸的我們無法動態的修改已編譯過的類別,但是Introduction卻可以讓你動態為這個以編譯過的類別進行新增功能等動作,確實是滿驚人的,不過我沒有試過,有興趣的大大歡迎試試看。

Target object

一個Advice被應用的對象或目標物件,如果直接使用Target Object進行撰寫橫切點等程式碼,會發現使用這種方式進行的crosscutting concerns都是需要自己設定ProxyBean,這實在是非常麻煩,但若使用Aspect則可以達到自動代理的機制。

AOP proxy

AOP框架創建的對象,用來實現Aspect。然而Spring AOP是使用動態代理的方式進行。然後代理的理解可以看看這個從代理機制初探 AOP,這邊有個簡單的小程式讓大快速理解代理機制。

Weaving

Advice被應用到物件上的過程稱為Weaving,在AOP中縫合的方式有幾個時間點:編譯時期(Compile time)、類別載入時期(Classload time)、執行時期(Runtime)。(取自於:AOP 觀念與術語)

Advice 通知的類型

@Before advice

在某連接點之前執行的通知,但不能令Join point停止執行,除非發生異常。

@After return advice

在某個接點執行成功,並且返回值得時候觸發。

@After throwing advice

在方法拋出異常退出時執行的通知。

@After(final) advice

某個接點退出的時候執行,不管接點是否異常。

@Around advice

這個功能算是滿常用的,上述的通知類型都會執行。

實戰

剛剛上面簡單的介紹了AOP,那麼現在我們這邊將要進行實作了,讓大家包括我可以更了解AOP的使用方法與情境。

目錄架構

Gradle

這邊會看到我們加入了aspects模組。

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = 1.8
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart',
                   'Implementation-Version': version
    }
}

repositories {
    mavenCentral()
}

dependencies {
    //Spring

    def springVersion = '4.1.6.RELEASE'
    compile group: 'org.springframework', name: 'spring-context', version: "${springVersion}"
    compile group: 'org.springframework', name: 'spring-aspects', version: "${springVersion}"
    compile group: 'org.springframework', name: 'spring-test', version: "${springVersion}"
    
    //Log4j 2

    def log4jVersion = '2.8.2'
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: "${log4jVersion}"
    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: "${log4jVersion}"
    compile group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: "${log4jVersion}"

    //common

    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'

    //test

    testCompile group: 'junit', name: 'junit', version: '4.+'
}

test {
    systemProperties 'property': 'value'
}

uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

ContextConfig.java

這個類別就是這麼簡單,這部分其實就是取代了傳統xml的部分,今天其實也不用特地創個類別寫,但是為了整體的程式碼乾淨以及後續的擴充,通常我會特別拉出來創建,這邊做的事情有幾件事情需要了解。

  1. @Configuration 代表這是一個SpringConfig,若使用ApplicationContext讀取該類別,將會類似我們上一章的xml所設定的事情。
  2. @ComponentScan 這部分就可以指定我們所要Scan的Package,這邊後面會稍微在講解。
  3. @EnableAspectJAutoProxy 開啟讓Aspect自動代理。
@Configuration
@ComponentScan("com.spring.example.*")
@EnableAspectJAutoProxy
public class ContextConfig {

}

User.java

public class User {
    
    private String userName;
    
    private String nickName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "User [userName=" + userName + ", nickName=" + nickName + "]";
    }
    
}

UserDao.java

剛剛上面有ComponentScan的注入,這邊大家可以看到我在類別上面使用了@Component,這部分其實就是跟我們在xml使用所創建出來的bean是一樣的。

@Component
public class UserDao {
    
    public User getUserById(int id) {
        System.out.println("Method getUserById() called");
        return new User();
    }

    public int setUser(User user) {
        System.out.println("Method setUser() called");
        return 0;
    }

    public int delUserById(int id) {
        return 0;
    }
    
}

UserAspect.java

今天的主角出現了,文章開頭就介紹許多關於AOP的術語與觀念,這邊有看到@Before、@After,這兩個就是Advice通知類型,在這個Advice後面大家可以看到execution以及指定類別、方法,這邊就是Pointcut。

@Aspect
@Component
public class UserAspect {

    @Before("execution(* com.spring.example.dao.UserDao.getUserById(..))")
    public void logBeforeV1(JoinPoint joinPoint) {
        System.out.println("UserAspect.logBeforeV1() : " + joinPoint.getSignature().getName());
    }

    @Before("execution(* com.spring.example.dao.UserDao.*(..))")
    public void logBeforeV2(JoinPoint joinPoint) {
        System.out.println("UserAspect.logBeforeV2() : " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.spring.example.dao.UserDao.getUserById(..))")
    public void logAfterV1(JoinPoint joinPoint) {
        System.out.println("UserAspect.logAfterV1() : " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.spring.example.dao.UserDao.*(..))")
    public void logAfterV2(JoinPoint joinPoint) {
        System.out.println("UserAspect.logAfterV2() : " + joinPoint.getSignature().getName());
    }

}

AopTest

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ContextConfig.class})
public class AopTest extends AbstractJUnit4SpringContextTests{

    @Resource
    private UserDao userDao;
    
    @Test
    public void aopTest() {
        
        userDao.getUserById(1);
        userDao.setUser(new User());
        
    }
    
}

Result

當大家執行AopTest時,就可以在Console看到這幾行。

UserAspect.logBeforeV1() : getUserById
UserAspect.logBeforeV2() : getUserById
Method getUserById() called
UserAspect.logAfterV1() : getUserById
UserAspect.logAfterV2() : getUserById
UserAspect.logBeforeV2() : setUser
Method setUser() called
UserAspect.logAfterV2() : setUser

結語

AOP是一個強大的模組,使用它可以讓crosscutting concerns動作解耦,當產品上線後,維運就是必要的,若能夠讓這些動作切開來,令商業邏輯的流程乾淨,是非常棒的一件事情,再加上使用Annotation方式進行設定,大家應該會發現比傳統的xml設定清楚、乾淨吧!

希望這邊簡單的文章對大家有所幫助!!

Reference

AOP 觀念與術語
Aspect Oriented Programming with Spring
Spring 实践:AOP

← [Spring MVC] CH 1. Hello Spring (IOC、DI) [JQuery] SlickGrid套件在Bootstrap3所遇到的問題 →
 
comments powered by Disqus