在面向切面编程中,切面是核心概念,正如在面向接口编程中,接口是核心概念一样,这里的接口和切面其实都是一个 Java 类,或者说具体表现形式就是个 Java 类。除了切面,切点和通知也是重要概念,我实在不懂为啥叫通知…这里所谓的通知其实就是回调,或者说监听器。而切点则类似 hook,或者触发点,当满足切点的事件发生时会回调各个通知。
首先,本文是上篇 spring aop - spring 面向切面编程的执行顺序 的续篇,我们接着上回继续说,下面呢,是一个标准的面向切面编程的例子:
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect @Slf4j @Component @Order(12) public class LearnAopAspect { @Pointcut("@within(DataSource)") public void dataSource() { } @Before("dataSource()") public void changeDataSource(JoinPoint joinPoint) { log.info("===Before==="); } @After("dataSource()") public void clearDataSource(JoinPoint joinPoint) { log.info("===After==="); } @AfterReturning("dataSource()") public void afterReturn(JoinPoint joinPoint) { log.info("===AfterReturning==="); } @AfterThrowing("dataSource()") public void afterThrow(JoinPoint joinPoint) { log.info("===AfterThrowing==="); } @Around("dataSource()") public Object around(ProceedingJoinPoint joinPoint) { try { log.info("===Around==="); Object object = joinPoint.proceed(); log.info("===Around finish, result is {}", object); return "edit by aop and origin result is : " + object; } catch (Throwable e) { e.printStackTrace(); } return null; } }
@Aspect 是切面标记类,即声明这个类是个切面,同时,@Component 也是必须的,否则这个切面不会被调用,之后的 @Pointcut("@within(DataSource)") 定义了一个切点,这也是本文的核心,即切点表达式,这个方法的名字即切点的名字,这里是 dataSource(),后面的 @Before("dataSource()")、@After("dataSource()")、@AfterReturning("dataSource()") 等都是所谓的通知。
面向切面编程里,必须先定义一个切点,之后通过 @Before 等注解声明通知,当相关事件发生时,该通知会被调用,或者说被 标注的方法会被调用,除此之外,还有两个概念,即:JoinPoint、以及其子类 ProceedingJoinPoint,你可以称之为 连接点,通过连接点可以获取到被注释的方法以及类,还有入参等信息。
假设有个类:DataSource.java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 注解,用于类和方法,指定使用的数据库,优先使用 方法上的注解,没有时从类上获取 * name 对应 application.properties 里 设置 的 dynamic.datasoure.name */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String name(); }
然后你在某个方法或者类里添加了 @DataSource(name="test") 的注解,如下:
@RestController @RequestMapping("/test") @Slf4j @DataSource(name = "test") public class Test { @RequestMapping("/test") public String test() { log.info("test method ..."); return "success"; } }
那么,你可以通过如下代码读取到这里传入的 test,比如:
private DataSource findDataSource(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); DataSource dataSource = method.getAnnotation(DataSource.class); if (dataSource == null) { try { dataSource = AnnotationUtils.findAnnotation(signature.getMethod().getDeclaringClass(), DataSource.class); } catch (Exception e) { e.printStackTrace(); } } return dataSource; }
好了,现在大概已经说了 切面该如何定义,同时,还定义了一个切点,以及一些回调方法,同时给了一个例子。至于回调方法,除了 @Around 外,都很好理解,而 @Around 则是把实际调用的方法包裹起来,请看如下代码:
@Around("dataSource()") public Object around(ProceedingJoinPoint joinPoint) { try { log.info("===Around==="); Object object = joinPoint.proceed(); log.info("===Around finish, result is {}", object); return "edit by aop and origin result is : " + object; } catch (Throwable e) { e.printStackTrace(); } return null; }
这里,调用 joinPoint.proceed(); 其实就是等于执行实际调用的方法,而且可以拿到其返回值,然后还可以修改返回值,修改后再返回…其余,比如@Before、@After 则很好理解了,本文不再赘述,我们来看看切点的表达式吧。除了上文中用到的 @Pointcut("@within(DataSource)") 外,还有很多别的标记,具体如下,比如 execution、@execution、this、target 等。
spring aop 支持的切入点标记
根据 spring 官网文档:aop-pointcuts-designators,完整的面向对象编程支持 call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this 以及 @withincode 等,但是以上标记 spring aop 并不支持,如果使用了,将会抛出 IllegalArgumentException 异常。spring aop 支持以下切入点标记:
- execution - for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP
- within - limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)
- this - limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type
- target - limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type
- args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types
- @target - limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type
- @args - limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)
- @within - limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)
- @annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation
以上内容引用自 spring 官方文档,目前我也一知半解,而网上的博客,无力吐槽!!!后面,详细解释其含义。这里主要说下 execution:
@Pointcut("execution(public * top.kpromise..mapper..*.*(..))") public void dataSource() { }
这里,第一个* 代表任意的返回值,top.kpromise 后面的.. 表示当前包及其子包,mapper 后面的 .. 同样表示当前包及其子包,之后的 * 表示任意类名,.*(..) 表示任何方法,括号里的 .. 表示任意参数,现在你应该明白,上面这个 execution 表达式的含义是:top.kpromise 及其子包下的 mapper 及其子包下的任意方法。