前言
在之前的文章我们已经学习了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 方法是与的关系。