基本概念
若想要在通知方法获取被通知方法的参数共有两种方式:自动获取、手动指定。下面来探究下这两种方式的不同之处。
自动获取参数
在介绍 AOP 的通知类型时有提到过环绕通知,该通知类型可以通过参数 ProceedingJoinPoint 自动获取被通知方法的参数值并调用该方法。除了 ProceedingJoinPoint 外,还有 JoinPoint,JoinPoint.StaticPart 也都能自动获取被通知方法的参数。
1.JoinPoint
JoinPoint 即连接点,通过该参数可以获取连接点(被通知方法)的相关信息。
首先来看该接口的继承关系,如下图。可以发现在环绕通知中所使用的 ProceedingJoinPoint 就是继承自该接口。
再来看看该接口的定义:
public interface JoinPoint {
// 连接点的具体信息
String toString();
// 连接点的简单信息
String toShortString();
// 连接点的所有信息
String toLongString();
// AOP 代理对象
Object getThis();
// 目标对象
Object getTarget();
// 被通知方法的参数列表
Object[] getArgs();
// 连接点签名
Signature getSignature();
// 连接点方法所在类文件中的位置
SourceLocation getSourceLocation();
// 连接点类型
String getKind();
// 内部接口,暂不探究(下面会提到)
public interface StaticPart {
//... 省略部分代码
}
public interface EnclosingStaticPart extends StaticPart {}
// 返回连接点静态部分
StaticPart getStaticPart();
// getKind 方法的返回值
static String METHOD_EXECUTION = "method-execution";
static String METHOD_CALL = "method-call";
static String CONSTRUCTOR_EXECUTION = "constructor-execution";
static String CONSTRUCTOR_CALL = "constructor-call";
static String FIELD_GET = "field-get";
static String FIELD_SET = "field-set";
static String STATICINITIALIZATION = "staticinitialization";
static String PREINITIALIZATION = "preinitialization";
static String INITIALIZATION = "initialization";
static String EXCEPTION_HANDLER = "exception-handler";
static String SYNCHRONIZATION_LOCK = "lock";
static String SYNCHRONIZATION_UNLOCK = "unlock";
static String ADVICE_EXECUTION = "adviceexecution";
}
下面通过简单的例子来探究该接口的作用:
- 定义一个 Bean,用它来表示连接点(joinpoint)的一部分
public class Animals {
public void setName(String name) {
System.out.println(name);
}
}
- 定义一个 Bean,用来表示切面
public class AnimalsAspect {
public void beforeAdvice() {
System.out.println(point.toString());
System.out.println(point.toShortString());
System.out.println(point.toLongString());
System.out.println(point.getThis().toString());
System.out.println(point.getTarget().toString());
System.out.println(point.getArgs()[0]);
System.out.println(point.getSignature());
System.out.println(point.getSourceLocation());
System.out.println(point.getKind());
System.out.println(point.getStaticPart());
System.out.println("before advice..." );
}
}
- 在 xml 文件中配置
<!-- 1.容器中注入 Bean -->
<bean id="joinpoint" class="com.demo.Animals" />
<bean id="aspect" class="com.aop.AnimalsAspect"/>
<!-- 2.AOP 配置 -->
<aop:config>
<aop:aspect ref="aspect">
<aop:before method="beforeAdvice" pointcut="execution(* com.demo.Animals.setName(..))" />
</aop:aspect>
</aop:config>
- 调用验证
String location = ...
ApplicationContext factory = new FileSystemXmlApplicationContext(location);
Animals animals = (Animals) factory.getBean("joinpoint");
animals1.setName("cat");
// 输出结果:
// execution(void com.demo.Animals.setName(String))
// execution(Animals.setName(..))
// execution(public void com.demo.Animals.setName(java.lang.String))
// com.demo.Animals@4245c97b
// com.demo.Animals@4245c97b
// cat
// void com.demo.Animals.setName(String)
// org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@58e22f2b
// method-execution
// execution(void com.demo.Animals.setName(String))
// before advice...
// cat
2.ProceedingJoinPoint
该连接点接口只能用于环绕通知,并且该接口继承自 JoinPoint 接口。
public interface ProceedingJoinPoint extends JoinPoint {
void set$AroundClosure(AroundClosure arc);
// 调用无参的被通知方法
public Object proceed() throws Throwable;
// 调用带参的通知方法
public Object proceed(Object[] args) throws Throwable;
}
3.JoinPoint.StaticPart
刚连接点是 Joinpoint 的内部接口,作用功能与 JoinPoint 基本一致。
public interface StaticPart {
Signature getSignature();
String getKind();
// 连接点标识
int getId();
String toString();
String toShortString();
String toLongString();
}
手动指定参数
手动指定参数,即在配置切面时,需在切面的通知与切面的切点中明确指定参数。
下面通过一个实例来阐明:
- 定义一个 Bean,用它来表示连接点(joinpoint)的一部分
public void Animals {
// 该被通知方法存在两个方法参数
public void setName(String param1 , String param2) {
System.out.println(param1 +"-"+ param2);
}
}
- 定义一个 Bean,用来表示切面
public class AnimalsAspect {
// 关键 -> 在通知中需明确指定参数名称
public void beforeAdvice(Object param1, Object param2) {
System.out.println(param1.toString() + "-" + param2.toString());
}
}
- 在 xml 文件中配置
<!-- 1.容器中注入 Bean -->
<bean id="joinpoint" class="com.demo.Animals" />
<bean id="aspect" class="com.aop.AnimalsAspect"/>
<!-- 2.AOP 配置 -->
<aop:config>
<aop:aspect ref="aspect">
<!-- 关键 -> 在切点中也需明确指定参数名称 -->
<aop:before method="beforeAdvice" pointcut="execution(* com.demo.Animals.setName(..)) and args(param1,param2)" />
</aop:aspect>
</aop:config>
- 调用验证
String location = ...
ApplicationContext factory = new FileSystemXmlApplicationContext(location);
Animals animals = (Animals) factory.getBean("joinpoint");
animals1.setName("cat","dog");
// 输出结果:
// cat-dog
// before advice...
// cat-dog
混合使用
当同时采用自动获取参数与手动指定参数时,自动获取参数必须是第一个参数,即 JoinPoint 、ProceedingJoinPoint 等参数并需是通知方法定义的第一个参数。
- 切面通知定义如下:
public void Animals {
// JoinPoint 必须是方法定义的第一个参数
public void setName(JoinPoint point , String param1,String param2) {
System.out.println(param1 +"-"+ param2);
}
}
- 在 xml 的配置同上。