面向切面的Spring(一) AOP術語、AspectJ表示式說明,execution表示式含有註解寫法
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指示器 | 描述 |
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........
相關文章
- Spring AOP AspectJ 切面表示式高階用法Spring
- AOP - 切點表示式
- 說說 Spring 表示式語言(SpEL)中的各種表示式型別Spring型別
- Spring AOP的AspectJ註解Spring
- 說說 Spring 支援的 AspectJ 切點函式Spring函式
- 【轉載】正規表示式解釋說明
- lambda表示式的寫法1
- 正規表示式-語法大全
- Spring EL表示式使用詳解Spring
- 正規表示式的基本語法
- 說說正規表示式
- 正規表示式教程之操作符及說明詳解
- Lambda表示式基本語法與應用
- java 正規表示式語法學習Java
- GaussDB SQL基本語法示例-CASE表示式SQL
- spring-AOP(二)實現原理之AspectJ註解方式Spring
- 通過js正規表示式例項學習正規表示式基本語法JS
- 表示式與語句
- js正規表示式基本語法學習JS
- Java正規表示式的語法與示例Java
- ABAP 740裡的新語法 - LET表示式
- 【棧】【字串語法】牛牛與字尾表示式字串
- Python語法進階(2)- 正規表示式Python
- Spring Aop中解析spel表示式,實現更靈活的功能Spring
- 正規表示式單行、多行模式簡介(使用說明)模式
- JSP 表示式語言概述JS
- iOS正規表示式細說iOS
- Lambda表示式詳解
- Python中lambda表示式的語法與應用Python
- python正規表示式(簡明版)Python
- 一次性搞懂JavaScript正規表示式之語法JavaScript
- Spring Security 表示式(Expressions) - hasRole示例SpringExpress
- 表示式
- JDK1.8新特性:Lambda表示式語法和內建函式式介面JDK函式
- 正規表示式(一)
- 中綴表示式轉字尾表示式
- C# Lambda表示式詳解,及Lambda表示式樹的建立C#
- Python中表示式與語句Python