SpringBoot AOP的使用

AOP:面向切面編程,相對於OOP面向對象編程 Spring的AOP的存在目的是爲了解耦。AOP可讓一組類共享相同的行爲。在OOP中只能繼承和實現接口,且類繼承只能單繼承,阻礙更多行爲添加到一組類上,AOP彌補了OOP的不足。html

還有就是爲了清晰的邏輯,讓業務邏輯關注業務自己,不用去關心其它的事情,好比事務。java

Spring的AOP是經過JDK的動態代理和CGLIB實現的。git

1、AOP的術語:

aop 有一堆術語,很是難以理解,簡單說一下github

  • 通知(有的地方叫加強)(Advice)web

    須要完成的工做叫作通知,就是你寫的業務邏輯中須要好比事務、日誌等先定義好,而後須要的地方再去用spring

  • 鏈接點(Join point)編程

    就是spring中容許使用通知的地方,基本上每一個方法先後拋異常時均可以是鏈接點springboot

  • 切點(Poincut)app

    其實就是篩選出的鏈接點,一個類中的全部方法都是鏈接點,但又不全須要,會篩選出某些做爲鏈接點作爲切點。若是說通知定義了切面的動做或者執行時機的話,切點則定義了執行的地點ide

  • 切面(Aspect)

    其實就是通知和切點的結合,通知和切點共同定義了切面的所有內容,它是幹什麼的,何時在哪執行

  • 引入(Introduction)

    在不改變一個現有類代碼的狀況下,爲該類添加屬性和方法,能夠在無需修改現有類的前提下,讓它們具備新的行爲和狀態。其實就是把切面(也就是新方法屬性:通知定義的)用到目標類中去

  • 目標(target)

    被通知的對象。也就是須要加入額外代碼的對象,也就是真正的業務邏輯被組織織入切面。

  • 織入(Weaving)

    把切面加入程序代碼的過程。切面在指定的鏈接點被織入到目標對象中,在目標對象的生命週期裏有多個點能夠進行織入:

    • 編譯期:切面在目標類編譯時被織入,這種方式須要特殊的編譯器
    • 類加載期:切面在目標類加載到JVM時被織入,這種方式須要特殊的類加載器,它能夠在目標類被引入應用以前加強該目標類的字節碼
    • 運行期:切面在應用運行的某個時刻被織入,通常狀況下,在織入切面時,AOP容器會爲目標對象動態建立一個代理對象,Spring AOP就是以這種方式織入切面的。

例:

public class UserService{
    void save(){}
    List list(){}
    ....
}

在UserService中的save()方法前須要開啓事務,在方法後關閉事務,在拋異常時回滾事務。

那麼,UserService中的全部方法都是鏈接點(JoinPoint),save()方法就是切點(Poincut)。須要在save()方法先後執行的方法就是通知(Advice),切點和通知合起來就是一個切面(Aspect)。save()方法就是目標(target)。把想要執行的代碼動態的加入到save()方法先後就是織入(Weaving)。

有的地方把通知稱做加強是有道理的,在業務方法先後加上其它方法,其實就是對該方法的加強。

2、經常使用AOP通知(加強)類型

  • before(前置通知): 在方法開始執行前執行
  • after(後置通知): 在方法執行後執行
  • afterReturning(返回後通知): 在方法返回後執行
  • afterThrowing(異常通知): 在拋出異常時執行
  • around(環繞通知): 在方法執行前和執行後都會執行

3、執行順序

around > before > around > after > afterReturning

4、先說一下SpringAop很是霸道又用的很是少的功能 --引入(Introduction)

  1. 配置類
@Aspect
@Component
public class IntroductionAop {

    @DeclareParents(value = "com.jiuxian..service..*", defaultImpl = DoSthServiceImpl.class)
    public DoSthService doSthService;

}
  1. service代碼
public interface DoSthService {

    void doSth();
}

@Service
public class DoSthServiceImpl implements DoSthService {

    @Override
    public void doSth() {
        System.out.println("do sth ....");
    }
    
}

public interface UserService {

    void testIntroduction();
}

@Service
public class UserServiceImpl implements UserService {

    @Override
    public void testIntroduction() {
        System.out.println("do testIntroduction");
    }
}
  1. 測試代碼
@Test
public void testIntroduction() {
    userService.testIntroduction();
    //Aop 讓UserService方法擁有 DoSthService的方法
    DoSthService doSthService = (DoSthService) userService;
    doSthService.doSth();
}
  1. 結果
do testIntroduction
do sth ....

5、五種通知(加強)代碼實現

  1. 配置類

(1) 對方法

@Aspect
@Component
public class TransactionAop {

    @Pointcut("execution(* com.jiuxian..service.*.*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void beginTransaction() {
        System.out.println("before beginTransaction");
    }

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

    @AfterReturning("pointcut()", returning = "returnObject")
    public void afterReturning(JoinPoint joinPoint, Object returnObject) {
        System.out.println("afterReturning");
    }

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

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("around");
            return joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            throw e;
        } finally {
            System.out.println("around");
        }
    }
}

(2) 對註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
    String value() default "";
}
@Aspect
@Component
public class AnnotationAop {

    @Pointcut(value = "@annotation(log)", argNames = "log")
    public void pointcut(Log log) {
    }

    @Around(value = "pointcut(log)", argNames = "joinPoint,log")
    public Object around(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        try {
            System.out.println(log.value());
            System.out.println("around");
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        } finally {
            System.out.println("around");
        }
    }
}

  @Before("@annotation(com.jiuxian.annotation.Log)")
  public void before(JoinPoint joinPoint) {
	  MethodSignature signature = (MethodSignature) joinPoint.getSignature();
	  Method method = signature.getMethod();
	  Log log = method.getAnnotation(Log.class);
	  System.out.println("註解式攔截 " + log.value());
  }
  1. service 方法實現
public interface UserService {

    String save(String user);

    void testAnnotationAop();
}


@Service
public class UserServiceImpl implements UserService {

    @Override
    public String save(String user) {
        System.out.println("保存用戶信息");
        if ("a".equals(user)) {
            throw new RuntimeException();
        }
        return user;
    }

    @Log(value = "test")
    @Override
    public void testAnnotationAop() {
        System.out.println("testAnnotationAop");
    }
}
  1. 測試類
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAopApplicationTests {

    @Resource
    private UserService userService;

    @Test
    public void testAop1() {
        userService.save("張三");
        Assert.assertTrue(true);
    }

    @Test
    public void testAop2() {
        userService.save("a");
    }
    
    @Test
    public void testAop3() {
        userService.testAnnotationAop();
    }
}
  1. 結果
  • 執行testAop1時
around
before beginTransaction
保存用戶信息
around
after commit
afterReturning :: 張三
  • 執行testAop2時
around
before beginTransaction
保存用戶信息
around
after commit
afterThrowing  rollback
  • 執行testAop3時
test
around
testAnnotationAop
around
  1. pom文件
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

6、最經常使用的execution解釋

例: execution(* com.jiuxian..service.*.*(..))

  • execution 表達式的主體
  • 第一個* 表明任意的返回值
  • com.jiuxian aop所橫切的包名
  • 包後面.. 表示當前包及其子包
  • 第二個* 表示類名,表明全部類
  • .*(..) 表示任何方法,括號表明參數 .. 表示任意參數

例: execution(* com.jiuxian..service.*Service.add*(String))

表示: com.jiuxian 包及其子包下的service包下,類名以Service結尾,方法以add開頭,參數類型爲String的方法的切點。

7、特別的用法

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {} 

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

可使用 &&, ||, ! 運算符來定義切點

8、更多詳細介紹請參閱官網

SpringAOP官網介紹

9、本文示例代碼

GitHub 源碼

以上代碼基於Springboot 2.0

相關文章
相關標籤/搜索