2w字搞懂Spring AOP的前世今生(一)
Spring AOP概述
最近看seata源码的时候,随处可见spring aop的api,于是一边看seata,一边又把spring aop总结了一下
我们在使用Spring框架的时候,经常需要和Spring的2大特性,IOC和AOP打交道,之前写了一篇《2w字搞懂Spring Bean的一生》从源码层面分析了IOC的执行流程,本篇文章就接着分享一下AOP的底层实现,比较基础的内容本篇文章就不多做介绍了,主要侧重于底层api的设计理念
「AOP这种设计理念常见的概念如下」
「AOP的主要应用场景如下」
「Spring AOP的实现主要经历了2代」
第一代:spring1.x版本,自己实现了AOP的功能 第二代:spring2.x版本,Spring集成了AspectJ的实现
Spring AOP一代
「当我们要基于现成的实现增加横切逻辑时,首先需要找到哪些地方增强,我们就用Pointcut来进行筛选吧」
先写一个Service方便后面的演示
public interface EchoService {
String echo(String message);
}
public class DefaultEchoService implements EchoService {
@Override
public String echo(String message) {
return message;
}
}
Pointcut
Pointcut接口定义如下
public interface Pointcut {
// 通过类过滤
ClassFilter getClassFilter();
// 通过方法过滤
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
「当我们想筛选出EchoService的echo方法时,就可以定义如下的Pointcut」
public class EchoPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return EchoService.class.isAssignableFrom(clazz);
}
};
}
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "echo".equals(method.getName()) &&
method.getParameterTypes().length == 1 &&
Objects.equals(String.class, method.getParameterTypes()[0]);
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
};
}
}
看起来还是很麻烦的,因此Spring内置了很多实现,一般情况下我们用内置的实现即可,不用自己定义,上面的筛选过程就可以改为如下
// 方法名为 echo 会被拦截
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("echo");
Spring提供的部分Pointcut实现如下
Jointpoint
「通过Pointcut筛选出来的要增加横切逻辑的地方就是Jointpoint。」 在AOP理念中,很多地方可以增加横切逻辑,如方法执行,字段设置等。但是「Spring只支持方法执行这一种Joinpoint」,因为这种类型的Jointpoint基本上已经满足80%的场景了
Joinpoint类型中 「方法调用优于方法执行」
因为Spring中只支持方法执行这一种Joinpoint,所以我们可以从Joinpoint实现类中获取增强的方法信息
Advice
当筛选出Jointpoint时,我们就需要在这些Jointpoint上增加横切逻辑,这些横切逻辑被称为Advice
在Spring中实现横切逻辑的方式有两类
- 实现Advice接口
- 实现IntroductionInfo接口
实现Advice接口的方式我们最常用,后面会详细分析。实现IntroductionInfo接口的方式基本不会用,这里演示一下具体的用法,方便理解整个AOP API的设计理念
「IntroductionInfo主要是通过给目标类实现特定接口来增加新功能」
public interface SayName {
String getName();
}
public class DefaultSayName implements SayName {
@Override
public String getName() {
return "I am service";
}
}
public static void main(String[] args) {
SayName sayName = new DefaultSayName();
EchoService echoService = new DefaultEchoService();
// IntroductionInfo接口的内置实现
DelegatingIntroductionInterceptor interceptor =
new DelegatingIntroductionInterceptor(sayName);
Advisor advisor = new DefaultIntroductionAdvisor(interceptor, SayName.class);
ProxyFactory proxyFactory = new ProxyFactory(echoService);
proxyFactory.addAdvisor(advisor);
// hello world
EchoService proxyService = (EchoService) proxyFactory.getProxy();
System.out.println(proxyService.echo("hello world"));
// I am service
SayName proxySayName = (SayName) proxyFactory.getProxy();
System.out.println(proxySayName.getName());
}
可能你对这个例子中的Advisor和ProxyFactory比较陌生,不知道起了啥作用,不着急,我们后面会详细分析这2个类的作用
「实现Advice接口的方式,应该是Spring AOP一代中最常见的使用方式了」
「对HashMap的put方法增加执行前的横切逻辑」, 打印放入HashMap的key和value的值
public static void main(String[] args) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*put.*");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.printf("当前存放的key为 %s,值为 %s", args[0], args[1]);
}
});
ProxyFactory proxyFactory = new ProxyFactory(new HashMap());
proxyFactory.addAdvisor(advisor);
Map<String, String> proxyMap = (Map<String, String>) proxyFactory.getProxy();
// 当前存放的key为 a,值为 a
proxyMap.put("a", "a");
}
文章转自公众号:Java识堂