天天看點

使用@AspectJ注解開發SpringAOP

現在使用@AspectJ注解的方式已經成為了主流。是以我用一個簡單的例子來了解一下他的整個過程。

選擇連接配接點

Spring是方法級别的AOP架構,我們主要是以某個類的某個方法作為連接配接點,用動态代理的理論來說,就是要攔截哪個方法織入對應AOP通知。

首先我們建一個接口:

package aop.service;

import game.bigLiZi.game.pojo.Role;

public interface RoleService {
    public void printRole(Role role);
}

           

這個接口很簡單,接下來提供一個實作類:

package aop.service.impl;

import aop.service.RoleService;
import game.bigLiZi.game.pojo.Role;
import org.springframework.stereotype.Component;

@Component
public class RoleServiceImpl implements RoleService {
    @Override
    public void printRole(Role role) {
        System.out.println("{ id:"+role.getId()+"roleName:"+role.getRoleName()+
                                "note:"+role.getNote()+"}");
    }
}

           

這個時候如果把printRole作為AOP的連接配接點,那麼用動态代理的語言就是要為類RoleServiceImpl生成代理對象,然後攔截printRole方法,于是可以産生各種AOP通知方法。

建立切面

選擇好了連接配接點就可以建立切面了,在Spring中隻要使用@Aspect注解一個類,那麼Spring IoC容器就會認為這是一個切面了。

package aop.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
public class RoleAspect {

    @Before("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void before(){
        System.out.println("before..........");
    }

    @After("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void after(){
        System.out.println("after..........");
    }

    @AfterReturning("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterReturning(){
        System.out.println("afterReturning..........");
    }

    @AfterThrowing("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing..........");
    }
}

           

這裡我們就要說一說AspectJ注解了:

  • @Before:在被代理對象的方法前調用,前置通知。
  • @Around:将被代理對象的方法封裝起來,并用環繞通知取代它。 它将覆寫原有方法,但是允許你通過反射調用原有的方法。
  • @After:在被代理對象的方法後調用, 後置通知。
  • @AfterReturning:在被代理對象的方法正常傳回後調用,傳回通知,要求被代理對象的方法執行過程中沒有發生異常。
  • @AfterThrowing:在被代理對象的方法抛出異常後調用,異常通知,要求被代理對象的方法執行過程中傳回異常。

上面那段代碼的注解使用了對應的正規表達式,這些正規表達式是切點的問題,也就是要告訴Spring AOP,需要攔截什麼對象的什麼方法。

定義切點

上面代碼在注解中定義了execution的正規表達式,Spring是通過這個正規表達式判斷是否需要攔截你的方法,這個表達式是:

對這個表達式分析一下:

  • execution:代表執行方法的時候會觸發
  • *:代表任意傳回類型的方法
  • aop.service.impl.RoleServiceImpl:代表類的全限定名
  • printRole:被攔截方法名稱
  • (…):任意的參數

上面的表達式還有些簡單,AspectJ的訓示器還有很多,下面我列舉幾個:

  • arg():限制連接配接點比對參數為指定類型的方法
  • execution:用于比對連接配接點的執行方法
  • this():限制連接配接點比對AOP代理的Bean,引用為指定類型的類
  • target:限制連接配接點比對被代理對象為指定的類型
  • within():限制連接配接點比對的包

此外,上面的正規表達式需要重複書寫多次,比較麻煩,這裡我們可以使用@Pointcut來避免這個麻煩。代碼如下:

package aop.aspect;

import org.aspectj.lang.annotation.*;

public class anotherAspect {
    @Pointcut("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void print(){
        
    }
    
    @Before("print()")
    public void before(){
        System.out.println("before..........");
    }

    @After("print()")
    public void after(){
        System.out.println("after..........");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("afterReturning..........");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("afterThrowing..........");
    }
}

           

測試AOP

連接配接點、切面以及切點都有了,這個時候就可以編寫測試代碼來測試AOP的内容。

首先要對Spring的Bean進行配置,采用Java配置,代碼如下:

package aop.config;

import aop.aspect.RoleAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("aop")
public class AopConfig {

    @Bean
    public RoleAspect getRoleAspect(){
        return new RoleAspect();
    }
}

           

測試AOP流程

package aop.main;

import aop.config.AopConfig;
import aop.service.RoleService;
import game.bigLiZi.game.pojo.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args){
        ApplicationContext ctx=new AnnotationConfigApplicationContext(AopConfig.class);
        RoleService roleService=(RoleService)ctx.getBean(RoleService.class);
        Role role=new Role();
        role.setId(1L);
        role.setRoleName("role_name_1");
        role.setNote("role_note_1");
        roleService.printRole(role);
        System.out.println("########################");
        role=null;
        roleService.printRole(role);
    }
}

           

在第二次列印之前,将role設定為null,這樣是為了測試異常傳回通知。

before..........
{ id:1roleName:role_name_1note:role_note_1}
after..........
afterReturning..........
########################
before..........
after..........
afterThrowing..........
Exception in thread "main" java.lang.NullPointerException