1. 程式人生 > >Spring的學習(三):面向切面的Spring

Spring的學習(三):面向切面的Spring

介紹

如果要重用功能的話,最常見的面向物件技術是繼承或者委託。但是,如果在整個應用中都使用相同的基類,繼承往往會導致一個脆弱的物件體系;而使用委託可能需要對委託物件進行復雜的呼叫。

切面提供了取代繼承和委託的另一種可選方案,而且在很多場景下更清晰簡潔。在使用切面程式設計時,我們仍然在一個地方定義通用功能,但是可以通過宣告的方式定義這個功能以何種方式在何處應用,而無需修改受影響的類。橫切關注點可以被模組化為特殊的類,這些類被稱為切面。

與大多數技術一樣,AOP已經形成自己的術語。描述切面的常用術語有通知(advice)、切點(pointcut)和連線點(join point)。

通知(Advice):

通知定義了切面是什麼以及何時使用。
Spring切面可以應用5種類型的通知:

  1. 前置通知(Before):在目標方法被呼叫之前呼叫通知功能。
  2. 後置通知(After):在目標方法完成之後呼叫通知,此時不會關心方法的輸出是什麼。
  3. 返回通知(After-returning):在目標方法成功執行之後呼叫通知。
  4. 異常通知(After-throwing):在目標方法丟擲異常後呼叫通知。
  5. 環繞通知(Around):通知包裹了被通知的方法,在被通知方法呼叫之前和呼叫之後執行自定義的行為 。

切點(Poincut):

我們通常使用明確的類和方法名稱或者利用正則表示式定義所匹配的類和方法名稱來指定這些切點。

切面(Aspect):

切面是通知和切點的結合。通知和切點共同定義了切面的全部內容——它是什麼在何時和何處完成其功能。

連線點(Join point):

是在應用執行過程中能夠插入切面的一個點。這個點可以使呼叫方法時,丟擲異常時、甚至修改一個欄位的時候。
因為Spring基於動態代理,所以Spring只支援方法級別的連線點。

Spring對AOP的支援:

Spring提供了4種類型的AOP支援:

  • 基於代理的經典AOP支援(太過繁瑣)
  • 純POJO切面(藉助Spring的aop名稱空間)
  • @AspectJ註解驅動的切面(下面會詳細介紹)
  • 注入式AspectJ切面

如果你的AOP需求超過了簡單的方法呼叫(如構造器或者屬性攔截),前三種都不能幫你實現,那麼你需要考慮使用AspectJ來實現切面。

我們來介紹一下使用註解建立切面:
(1)定義切面

@Aspect
public class Audience {
	//前置通知(手機靜音)
	@Before("execution(** concert.Performance.perform(..))")
	public void silenceCellPhones(){
		System.out.println("Silencing cell phones");
	}
	
	//前置通知(坐下)
	@Before("execution(** concert.Performance.perform(..))")
	public void takeSeats(){
		System.out.println("Taking seats");
	}
	
	//返回通知(鼓掌)
	@AfterReturning("execution(** concert.Performance.perform(..))")
	public void applause(){
		System.out.println("CLAP CLAP CLAP!");
	}	
}

@Aspect定義為切面。
@Before等為定義通知。
通知註解傳遞的字串引數為切點。

詳細解釋切點字串的含義
在這裡插入圖片描述
可以定義切點就會簡化切點表示式

	//定義切點
	@Pointcut("execution(** concert.Performance.perform(..))")
	public void performance(){}
	
	@Before("performance()")
	public void silenceCellPhones(){
		System.out.println("Silencing cell phones");
	}
	@Before("performance()")
	public void takeSeats(){
		System.out.println("Taking seats");
	}
	@AfterReturning("performance()")
	public void applause(){
		System.out.println("CLAP CLAP CLAP!");
	}

使用環繞通知(環繞通知相當於before加上after)

@Pointcut("execution(** concert.Performance.perform(..))")
	public void performance(){}
	
	@Around("performance()")
	public void watchPerformance(ProceedingJoinPoint jp){
		try {
			System.out.println("Silencing cell phones");
			System.out.println("Taking seats");
			//執行前的動作
			jp.proceed();
			//執行後的動作
			System.out.println("CLAP CLAP CLAP");
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}

影響類:

public class Performance {
	public void perform(){
		System.out.println("Performing");
	}
}

(2)在JavaConfig類中啟用AspectJ註解的自動代理

@Configuration
@EnableAspectJAutoProxy
public class ConcertConfig {
	@Bean
	public Performance performance(){
		return new Performance();
	}
	@Bean
	public Audience audience(){
		return new Audience();
	}
}

使用@EnableAspectJAutoProxy開啟自動代理。
(3)測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=concert.ConcertConfig.class)
public class test {
	@Autowired
	private Performance performance;
	@Test
	public void testDemo(){
		performance.perform();
	}
}

輸出結果:
在這裡插入圖片描述

切面不光可以為物件擁有的方法新增新功能,還可以為一個物件增加一個新的方法。我們繼續使用註解的方式來實現。
1.定義介面
Person介面

public interface Person {
    void likePerson();
}

Person介面的實現類

@Component("women")
public class Women implements Person {
    @Override
    public void likePerson() {
        System.out.println("我是女生,我喜歡帥哥");
    }
}

Animal介面

public interface Animal {
    void eat(); 
}

實現類

@Component
public class FemaleAnimal implements Animal {

    @Override
    public void eat() {
        System.out.println("我是雌性,我比雄性更喜歡吃零食");
    }
}

2.切面

@Aspect
@Component
public class AspectConfig {
	//"+"表示person的所有子類
    @DeclareParents(value = "com.lzj.spring.annotation.Person+", defaultImpl = FemaleAnimal.class)
    public Animal animal;
}
value屬性指定了哪種型別的bean要引入該介面(被新增功能的類),Person增加了eat的方法。
defaultImpl屬性指定了引入功能提供實現的類。
DeclareParent註解標註的靜態屬性(擴充套件功能的類)

3.JavaConfig類

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AnnotationConfig {

}

4.測試類

public class MainTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = 
			new AnnotationConfigApplicationContext(AnnotationConfig.class);
		Person person = (Person) ctx.getBean("women");
		person.likePerson();
		Animal animal = (Animal)person;
		animal.eat();
	}
}

SpringAOP(前三種)能夠滿足許多應用的切面需求,但是與AspectJ相比,SpringAOP是一個功能比較弱的AOP解決方案。AspectJ提供了SpringAOP所不能支援的許多型別的切點。

SpringAOP的底層原理就是動態代理。

Spring的應用場景:AOP與事務、AOP與日誌、AOP與快取。