面向切面的Spring(一) AOP術語、AspectJ表示式說明,execution表示式含有註解寫法

z1340954953發表於2018-05-31

AOP術語

如果要重用通用功能的話,常見的物件導向技術是繼承或委託,但是如果在整個應用中都是用相同的基類,繼承往往會導致一個脆弱的體系,而使用委託則有比較複雜的呼叫

切面,就是將關注的功能模組化為特殊的類,這類就是切面。

通知Advice: 切面中具體要做的事情,分為前置、後置、返回、異常、環繞通知。

連線點(Joint point): 執行過程中能夠插入切面的一個點。

切點(Pointcur):如果說通知定義了切面具體做的事情,那麼切點具體定義了在何處。切點匹配到所要織入的一個或多個連線點。

切面(Aspect): 在哪裡,做什麼事情,就是切點+通知

引入(Introduction): 在不改變現有的類的情況下,給現有的類新增新方法和屬性。

織入(Weaving): 將切面應用到目標物件並建立新的代理物件的過程。建立代理物件的方式,jdk代理和cglib代理

,一般在三種情況下進行織入:

* 編譯期 和 類載入期實現:需要特定的編譯器和類載入器

* 執行期:這種是Spring AOP實現方式。

Spring提供了4種型別的AOP支援

* 基於代理的經典Spring AOP

* 純pojo切面

* @AspectJ註解驅動的切面

* 注入式Aspect切面(適合Spring各版本)

前面三種都是Spring AOP實現的變體,Spring AOP構建在動態代理基礎上,Spring對AOP的支援僅限於方法攔截

通過切點來選擇連線點

切點用於準確定位在什麼地方應用切面的通知。在Spring AOP中,要使用AspectJ的切點表示式語言來定義切點。

 

AspectJ切點表示式定義Spring切面
AspectJ指示器 描述
arg() 限制連線點匹配引數為指定型別的執行方法 
@args() 限制連線點匹配引數有指定註解標註的執行方法
execution() 用於匹配是連線點的執行方法
this() 限制連線點匹配AOP代理的bean引用為指定型別的類
target() 限制連線點匹配目標物件為指定型別的類
@target 限制連線點匹配特定的執行物件,這些物件對應的類要具有執行型別的註解
within() 限制連線點匹配指定的型別
@within() 限制連線點匹配指定註解所標註的型別
@annotation 限定匹配帶有指定註解的連線點

這上面的Spring支援的指示器中,只有execution指示器是實際執行匹配的(編寫切點時候最主要指示器),其他的指示器都是用來限制匹配的

編寫切點

package com.bing.proxy;

public interface UserService {
	void sayAdd();
}

這個介面存在多個實現類,假設編寫UserService在sayAdd()方法執行時候觸發通知,使用AspectJ表示式來定義切點:

execution語法結構: 

execution(方法修飾符  方法返回值  方法所屬類 匹配方法名 (方法中的形參表)方法申明丟擲的異常)

 

例如:execution(* com.sample.service.impl..*.*(..)) 

 

第一個* 號表示不關心方法的返回值型別

..表示:當前包和子包

第三個*號表示所有類

.* 則是表示所有方法

(..)則是表示不關心方法的入參是什麼

需要注意的是,第一個* 和後面的包之間存在空格

execution常見的表示式寫法

常見的execution表示式,表示第幾個引數是什麼型別

以String為例

1. 第一個引數型別是String

execution(* com.example..*.*(String ,..))

需要注意的是 包前面的* 和包名稱之間有個空格

String 型別和後面的逗號有個空格,可以這樣理解,一般引數的型別和名稱之間都會存在空格的,

所以execution表示式這裡存在空格也正常

2. 第二個引數型別是String或者註解

execution(* com.example..*.*(*,String ,..)) 

execution(* com.example..*.*(*,@cn.example.validate.PasswordValidated(*) ,..))

和上面一樣,引數的型別和逗號存在一個空格,以此類推,第三個引數型別

只要*,*,String ,..  後面不管是第幾個引數是某個型別都是一樣的

3,方法引數型別中含有註解或者某個型別

execution(* com.example..*.*(..,String ,..))

execution(* com.example..*.*(..,@cn.example.validate.PasswordValidated(*) ,..))

只要換成..就好了

 

 

 

 

note:如果希望去使用多個AspectJ指示器定義切點,多個指示器之間可以使用&&,||,!標識邏輯操作

例如:execution(* com.bing.proxy.UserService.sayAdd(..))&&within(com.bing.proxy.*)

表示UserService的sayAdd方法執行時候觸發通知,並且是連線點是在com.bing.proxy包下的類

在切點中選中Bean

Spring中引入一個指示器bean(),裡面是beanID來標識bean

例如:execution(* com.bing.proxy.UserService.sayAdd(..))&&bean('userService')

表示類UserService的sayAdd方法執行時候觸發通知,並且限制BeanID是userService

定義切面

package com.bing.proxy;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Audience {
	@Pointcut("execution(* com.bing.proxy.UserService.sayAdd(..))")
	public void performance(){}
	@Before("performance()")
	public void silencePhones(){
		System.out.println("silence cell phones .... ");
	}
	@Before("performance()")
	public void takeState(){
		System.out.println("take state  .... ");
	}
	@AfterReturning("performance()")
	public void appluase(){
		System.out.println("clap clap clap! ..");
	}
}

@Pointcut註解設定的值是一個切點表示式,對應的方法內容不重要。主要指的是切入的方法位置

@Before @After 表示方法是通知

@Aspect表示該類是一個切面,如何使得切面生效?

*JavaConfig中

需要在配置類裡面加上註解@EnableAspectJAutoProxy,啟動AspectJ代理,測試如下:

1. 建立UseService的一個實現類:

package com.bing.proxy;

public class UserServiceImpl implements UserService {
	@Override
	public void sayAdd() {
		System.out.println("----add-----");
	}

}

2. 建立JavaConfig的配置類

package com.erong.service;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Primary;

import com.bing.proxy.Audience;
import com.bing.proxy.UserService;
import com.bing.proxy.UserServiceImpl;
import com.erong.interface_.CompactDisc;
import com.erong.service.profile.MagicExistCondition;

@Configuration
@EnableAspectJAutoProxy //啟用AspectJ自動代理
public class CDConfig {
	@Bean
	public Audience audience(){//建立切面的Bean
		return new Audience();
	}
	@Bean
	public UserService userService(){
		return new UserServiceImpl();
	}
}

3. 建立Junit測試類

package springDemo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.bing.config.ExpressiveConfig;
import com.bing.proxy.Audience;
import com.bing.proxy.UserService;
import com.erong.interface_.CompactDisc;
import com.erong.service.CDConfig;
import com.erong.service.CDPlayer;
import com.erong.service.CDPlayerConfig;
import com.erong.service.HelloWorld;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={CDConfig.class})
public class JavaConfigTest {
	@Autowired
	private UserService u;
	@Test
	public void test(){
		u.sayAdd();
	}
}

輸出如下:

silence cell phones .... 
take state  .... 
----add-----
clap clap clap! ..

,至此完成了JavaConfig中切面的測試

* XML中生效AspectJ代理

   <!-- spring基於註解的配置 -->
   <context:annotation-config/>
   <!-- 啟動AspectJ自動代理 -->
   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
   <bean class="com.bing.proxy.Audience"></bean>
   <bean class="com.bing.proxy.UserServiceImpl"></bean>

切面類程式碼,不變

測試類修改如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:aspectJ-test.xml")
public class CDPlayerTest {
	@Autowired
	private UserService us;
	@Test
	public void test(){
		us.sayAdd();
	}
}

另外關於環繞通知,需要註解@Around並在通知方法裡面加上引數ProceedingJoinPoint ,給上面的Audience類加上方法

@Around("performance()")
public void round(ProceedingJoinPoint jp){
	try {
		System.out.println("silence cell phones .... ");
		System.out.println("take state  .... ");
		jp.proceed();
		System.out.println("clap clap clap! ..");
	} catch (Throwable e) {
		e.printStackTrace();
	}
}

注意,如果不呼叫proceed方法,被通知方法將不會執行

處理通知中的引數

package com.bing.proxy;

import java.util.HashMap;
import java.util.Map;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class TrackCounter {
	private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();
	@Pointcut("execution(* com.bing.interface_.CompactDisc.playTrack(int)&&args(i))")
	public void trackPlay(int i){}
	@Before("trackPlay(i)")
	public void countTrack(int i){
		int currentCount = getPlayCount(i);
		trackCounts.put(i, currentCount+1);
	}
	public int getPlayCount(int count){
		return trackCounts.containsKey(count)?trackCounts.get(count):0;
	}
}

這裡,將切點定位的方法的引數,傳入到通知中,記錄切點定位的方法不同引數時候的值。

關於AspectJ指示器:execution(* com.bing.interface_.CompactDisc.playTrack(int)&&args(i))

args中的引數名稱和切點方法簽名中的引數匹配

總的來說,Spring AOP 提供了給現有的方法增加額外的功能,另外,可以給現有的bean增加新的方法

* 給現有的物件新增新的方法

存在介面:

public interface Encoreable {
	void performEncore();
}

現在將這個介面應用到UserService實現中。可能存在不可能修改的實現類,直接實現介面UserService不是可行的解決方案。

藉助Spring的引入功能,註解@DelareParent可以實現修改現有的物件(可以是所有介面的實現類),並加上新方法

需要引入介面的實現類的介面及實現類

public interface UserService {
	void sayAdd();
}
package com.bing.proxy;

public class UserServiceImpl implements UserService {
	@Override
	public void sayAdd() {
		System.out.println("----add-----");
	}
}

1. 建立Encoreable實現類:

public class DefaultEncoreable implements Encoreable{
	@Override
	public void performEncore() {
		System.out.println("encoreable........");
	}
}

2. 建立切面類

@Aspect
public class EncoreableIntroduce {
	@DeclareParents(value="com.bing.proxy.UserService+",
			defaultImpl=com.erong.service.DefaultEncoreable.class)
	public static Encoreable encoreable;
}

value屬性:哪個型別的bean需要引入Encoreable介面,+號表示UserService的所有子型別

defaultImpl屬性:指的是引入介面提供實現的類,其實就是預設引入物件的實現方法

3. xml:

   <!-- spring基於註解的配置 -->
   <context:annotation-config/>
   <!-- 啟動AspectJ自動代理 -->
   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
   <bean class="com.bing.aspect.EncoreableIntroduce"></bean>
   <bean class="com.bing.proxy.UserServiceImpl"></bean>

4. 測試類:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value="classpath:aspectJ-test.xml")
public class CDPlayerTest {
	@Autowired
	private UserService us;
	@Test
	public void test(){
		us.sayAdd();
		Encoreable ea = (Encoreable) us;
		ea.performEncore();
	}
}

從輸出,可以看出實現類已經成功引入介面

----add-----
encoreable........

 

 

 

 

相關文章