前言
在之前的文章我們已經學習了NameMatchMethodPointcut、JdkRegexpMethodPointcut、DynamicMethodMatcherPointcut、AspectJExpressionPointcut、AnnotationMatchingPointcut 等切入點的使用,詳情可參考 Spring5框架之AOP-Pointcut底層實現這篇文章。下面我們將繼續介紹ControlFlowPointcut、ComposablePointcut兩種更靈活的切入點使用。
ControlFlowPointcut
該切點一般可以指定在固定的類中固定方法去執行其他類的方法才使通知生效的場景下使用,這麼說有些生硬下面就以一個簡單的示例演示其使用如下所示:
- 新增前置通知
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
public class SimpleBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("\n" + "before......advice....start");
System.out.println("執行的方法是:" + method.getName());
System.out.println("執行的參數是:" + Arrays.asList(args));
System.out.println("執行的對象是:" + target);
System.out.println("before......advice...end");
}
}
- 新增接口及其實現
public interface AccountService {
void updateBalanceAndExpress();
}
@Service
public class AccountServiceImpl implements AccountService {
@Override
public void updateBalanceAndExpress() {
System.out.println("updateBalanceAndExpress 執行成功");
}
}
- 新增測試方法如下所示:
@Test
public void testControlFlowPointcut() {
AccountService accountService = applicationContext.getBean(AccountService.class);
ControlFlowPointcut pointcut = new ControlFlowPointcut(controlFlowTest.class,"execute");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,new SimpleBeforeAdvice());
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(accountService);
AccountService proxy = (AccountService) proxyFactory.getProxy();
System.out.println("第一次執行updateBalanceAndExpress 開始------------------------");
proxy.updateBalanceAndExpress();
System.out.println("第一次執行updateBalanceAndExpress 結束------------------------");
System.out.println("\n"+"第二次執行updateBalanceAndExpress 開始------------------------");
execute(proxy);
System.out.println("第二次執行updateBalanceAndExpress 結束------------------------");
}
public void execute(AccountService accountService) {
accountService.updateBalanceAndExpress();
}
執行結果如下所示:
第一次執行updateBalanceAndExpress 開始------------------------
updateBalanceAndExpress 執行成功
第一次執行updateBalanceAndExpress 結束------------------------
第二次執行updateBalanceAndExpress 開始------------------------
before......advice....start
執行的方法是:updateBalanceAndExpress
執行的參數是:[]
執行的對象是:com.codegeek.aop.day3.pointcut.controlflow.impl.AccountServiceImpl@1c7696c6
before......advice...end
updateBalanceAndExpress 執行成功
第二次執行updateBalanceAndExpress 結束------------------------
從上面的測試程序方法中可以看到我們AccountService
的updateBalanceAndExpress被調用了兩次,一次在testControlFlowPointcut方法中調用,另一次在execute方法中進行調用。前置通知只有在execute方法中得到了調用,在另一個方法中並沒有執行。在實際業務中可以根據這一特性在特定情形下執行通知如:下單時候若是客戶是VIP用戶就執行特定通知(增加積分、發送優惠券等等),普通用戶就無須執行通知。
ComposablePointcut
之前使用的point接口的實現基本上都爲一個advisor配置了一個切入點,在大多數情況下都夠用,但是有時候會有將多個切入點組合在一起,以確定在目標類的目標方法執行相應的通知的需求。這個時候就可以使用 ComposablePointcut
這個類完成這個需求的實現。
ComposablePointcut 支持兩種方法:union()與intersection() 兩者方法都具有多個參數的重載類型以接收ClassFilter、MethodMatcher 參數,其中union接收這些對象是或的關係,而intersection是與的關係。下面將以簡單的例子演示其使用:
- 新增ProductService 及其實現如下所示
public interface ProductService {
void addProduct(Product product);
void deleteProduct(Product product);
List<Product> findAllProduct();
List<Product> findProductByProductName(String productName);
List<Product> findProductByProductAddress(String address);
List<Product> findProductByProductCompany(String company);
}
@Service
@Slf4j
public class ProductServiceImpl implements ProductService {
private static List<Product> PRODUCT_LIST = new ArrayList<>();
@Override
public void addProduct(Product product) {
PRODUCT_LIST.add(product);
log.info("添加產品成功,產品信息:{}",product);
}
@Override
public void deleteProduct(Product product) {
PRODUCT_LIST.remove(product);
log.info("刪除產品成功,產品信息:{}",product);
}
@Override
public List<Product> findAllProduct() {
return PRODUCT_LIST;
}
@Override
public List<Product> findProductByProductName(String productName) {
return PRODUCT_LIST.stream().filter(e-> e.getProductName().equals(productName)).collect(Collectors.toList());
}
@Override
public List<Product> findProductByProductAddress(String address) {
return PRODUCT_LIST.stream().filter(e-> e.getProductAddress().equals(address)).collect(Collectors.toList());
}
@Override
public List<Product> findProductByProductCompany(String company) {
return PRODUCT_LIST.stream().filter(e-> e.getProduceCompany().equals(company)).collect(Collectors.toList());
}
}
- 新增StaticMethodMatcher實現如下所示:
public class AddMethodMatcher extends StaticMethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
String methodName = method.getName();
return methodName.startsWith("add");
}
}
class DeleteMethodMatcher extends StaticMethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
String name = method.getName();
return name.contains("delete") || name.endsWith("Name") ;
}
}
class FindMethodMatcher extends StaticMethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().contains("find");
}
}
- 新增Product對象如下所示:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
private String productName;
private String productAddress;
private BigDecimal productPrice;
private String category;
private String produceCompany;
}
- 測試ComposablePointcut的一個例子如下:
@Test
public void testComposable() {
// 創建ComposablePointcut 控制流對象並添加AddMethodMatcher實例
ComposablePointcut pointcut = new ComposablePointcut(ClassFilter.TRUE, new AddMethodMatcher());
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,new SimpleBeforeAdvice());
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(applicationContext.getBean(ProductService.class));
ProductService proxy = (ProductService) proxyFactory.getProxy();
// union FindMethodMatcher
pointcut.union(new FindMethodMatcher());
// 添加DeleteMethodMatcher
// pointcut.intersection(new DeleteMethodMatcher());
proxy.addProduct(new Product("iphone11","河南鄭州",new BigDecimal(5700),"電子產品","apple"));
proxy.addProduct(new Product("小米","廣東深圳",new BigDecimal(3200),"電子產品","xiaomi"));
System.out.println(proxy.findAllProduct());
System.out.println(proxy.findProductByProductCompany("apple"));
System.out.println(proxy.findProductByProductName("iphone11"));
}
運行結果如下所示:
[INFO] [ProductServiceImpl.java:20] 添加產品成功,產品信息:Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)
2020-06-08 22:23:38 [main] [INFO] [ProductServiceImpl.java:20] 添加產品成功,產品信息:Product(productName=小米, productAddress=廣東深圳, productPrice=3200, category=電子產品, produceCompany=xiaomi)
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple), Product(productName=小米, productAddress=廣東深圳, productPrice=3200, category=電子產品, produceCompany=xiaomi)]
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)]
before......advice....start
執行的方法是:addProduct
執行的參數是:[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)]
執行的對象是:com.codegeek.aop.day3.pointcut.composable.service.ProductServiceImpl@63376bed
before......advice...end
2020-06-08 22:14:59 [main] [INFO] [ProductServiceImpl.java:20] 添加產品成功,產品信息:Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)
before......advice....start
執行的方法是:addProduct
執行的參數是:[Product(productName=小米, productAddress=廣東深圳, productPrice=3200, category=電子產品, produceCompany=xiaomi)]
執行的對象是:com.codegeek.aop.day3.pointcut.composable.service.ProductServiceImpl@63376bed
before......advice...end
2020-06-08 22:14:59 [main] [INFO] [ProductServiceImpl.java:20] 添加產品成功,產品信息:Product(productName=小米, productAddress=廣東深圳, productPrice=3200, category=電子產品, produceCompany=xiaomi)
before......advice....start
執行的方法是:findAllProduct
執行的參數是:[]
執行的對象是:com.codegeek.aop.day3.pointcut.composable.service.ProductServiceImpl@63376bed
before......advice...end
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple), Product(productName=小米, productAddress=廣東深圳, productPrice=3200, category=電子產品, produceCompany=xiaomi)]
before......advice....start
執行的方法是:findProductByProductCompany
執行的參數是:[apple]
執行的對象是:com.codegeek.aop.day3.pointcut.composable.service.ProductServiceImpl@63376bed
before......advice...end
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)]
before......advice....start
執行的方法是:findProductByProductName
執行的參數是:[iphone11]
執行的對象是:com.codegeek.aop.day3.pointcut.composable.service.ProductServiceImpl@63376bed
before......advice...end
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)]
因爲我們創建的ComposablePointcut對象傳入了一個AddMethodMatcher實例對象,可以看到傳入的對象匹配以add開頭的方法就會返回true。所以當測試方法中調用了addProduct,切面的前置通知就會執行,然後我們又調用了union
方法將FindMethodMatcher也添加到切點中,故我們看到了上面接着把所有調用find方法對應的前置通知都進行了執行。接下來我們在測試intersection方法如下所示:
再次運行測試方法結果如下所示:
[INFO] [ProductServiceImpl.java:20] 添加產品成功,產品信息:Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)
2020-06-08 22:23:38 [main] [INFO] [ProductServiceImpl.java:20] 添加產品成功,產品信息:Product(productName=小米, productAddress=廣東深圳, productPrice=3200, category=電子產品, produceCompany=xiaomi)
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple), Product(productName=小米, productAddress=廣東深圳, productPrice=3200, category=電子產品, produceCompany=xiaomi)]
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)]
before......advice....start
執行的方法是:findProductByProductName
執行的參數是:[iphone11]
執行的對象是:com.codegeek.aop.day3.pointcut.composable.service.ProductServiceImpl@63376bed
before......advice...end
[Product(productName=iphone11, productAddress=河南鄭州, productPrice=5700, category=電子產品, produceCompany=apple)]
我們發現在DeleteMethodMatcher 匹配包含delete
或以Name
結尾的方法,所以上面之前以包含find但是沒有以Name
結尾的方法沒有匹配上所以這些方法沒有執行通知。這是因爲intersection 方法是與的關係。