jdk 和 cglib 在 Spring 中的统一
Spring 中对切点、通知、切面的抽象如下
-
切点:接口 Pointcut,典型实现 AspectJExpressionPointcut
-
通知:典型接口为 MethodInterceptor 代表环绕通知
-
切面:Advisor,包含一个 Advice 通知,PointcutAdvisor 包含一个 Advice 通知和一个 Pointcut
两个切面概念
aspect =
通知1(advice) + 切点1(pointcut)
通知2(advice) + 切点2(pointcut)
通知3(advice) + 切点3(pointcut)
...
advisor = 更细粒度的切面,包含一个通知和切点
模拟advisor底层实现
1.引入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2.备好类(其中target1实现了I1接口)
interface I1 {
void foo();
void bar();
}
static class Target1 implements I1 {
public void foo() {
System.out.println("target1 foo");
}
public void bar() {
System.out.println("target1 bar");
}
}
static class Target2 {
public void foo() {
System.out.println("target2 foo");
}
public void bar() {
System.out.println("target2 bar");
}
}
3.创建切面
//创建切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
//创建通知
MethodInterceptor methodInterceptor = invocation -> {
System.out.println("before.....");
Object result = invocation.proceed();
System.out.println("after.....");
return result;
};
//创建切面
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, methodInterceptor);
//创建代理
Target1 target = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvisor(advisor);
factory.setInterfaces(target.getClass().getInterfaces());
factory.setProxyTargetClass(false);
I1 proxy = (I1) factory.getProxy();
System.out.println(proxy.getClass());
proxy.foo();
proxy.bar();
解释:
首先我们需要准备切点用pointcut子接口来创建,创建好了要设置他的切点表达式,用来指定,
其次我们需要创建通知,利用methodintercepter创建,他与之前我们创建代理是的名称一样,但包不一样,这个通知实际上是一个环绕通知。
准备好前两个了,我们就需要开始准备切面, 利用DefaultPointcutAdvisor创建该切面
最后创建代理对象用来增强,我们使用一个代理工厂来创建代理,他会根据条件自动选择代理模式
先创建出目标类对象,把切面添加到工厂等等,获取代理对象,最后调用方法
可以看到代理工厂选择了jdk代理,
创建代理有三种情况:
a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现
b. proxyTargetClass = false, 目标没有实现接口, 用 cglib 实现
c. proxyTargetClass = true, 总是使用 cglib 实现
我们演示第二种,只需把目标类改成target2 ,以及生成的代理对象也改成target2
可以看到代理对象选择了cglib实现
-
AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
-
AopProxy 通过 getProxy 创建代理对象
-
图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
-
调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor
学到了什么
a. Spring 的代理选择规则
b. 底层的切点实现
c. 底层的通知实现
d. ProxyFactory 是用来创建代理的核心实现, 用 AopProxyFactory 选择具体代理实现
- JdkDynamicAopProxy
- ObjenesisCglibAopProxy
切点匹配
1.准备类(在某些方法和类上添加注解,模拟切点如何匹配注解)
static class T1 {
@Transactional
public void foo() {
}
public void bar() {
}
}
@Transactional
static class T2 {
public void foo() {
}
}
@Transactional
interface I3 {
void foo();
}
static class T3 implements I3 {
public void foo() {
}
}
2.通过AspectJExpressionPointcut创建切点,并设置切点表达,一开始我们设置成根据全类名
3.利用matches方法判断是否匹配成功
代码如下:
AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();
pt1.setExpression("execution(* bar())");
System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class));
可以看到bar方法匹配成功
根据注解来设置切点表达,判断是否匹配
AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class));
可以看到添加了注解的foo方法成功匹配
自定义切点匹配
利用StaticMethodMatcherPointcut来创建切点,重写里面的方法实现自定匹配规则(MergedAnnotations)
StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 检查方法上是否加了 Transactional 注解
MergedAnnotations annotations = MergedAnnotations.from(method);
if (annotations.isPresent(Transactional.class)) {
return true;
}
// 查看类上是否加了 Transactional 注解
annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
if (annotations.isPresent(Transactional.class)) {
return true;
}
return false;
}
};
System.out.println(pt3.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class));
System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class));
System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class));
通过设置MergedAnnotations.SearchStrategy.TYPE_HIERARCHY,可以实现实现的接口上添加了注解也能匹配
可以看到只有t1类中bar方法没有添加注解,所以没有匹配成功
学到了什么
a. 底层切点实现是如何匹配的: 调用了 aspectj 的匹配方法
b. 比较关键的是它实现了 MethodMatcher 接口, 用来执行方法的匹配