核心内容拆解 AOP
# AOP
AOP 的诞生可以追溯到上世纪 90 年代初期,它最早由 Gregor Kiczales 等人提出,并在 1997 年发表了经典的论文 Aspect-Oriented Programming。后来,AspectJ 成为了 Java 生态中使用最广泛的 AOP 框架之一。
AOP 的目的是为了解决在 OOP(面向对象编程)中难以处理的横切关注点问题,即将系统业务逻辑代码与其他非业务功能(如日志记录、性能统计、安全控制等)分离开来。AOP 通过把这些非业务功能独立出来,在需要时动态地植入到系统中,从而实现对业务逻辑的无侵入式增强。
AOP 的核心在于其能够将业务逻辑与非业务功能分离开来,从而降低了代码的耦合度,并且支持在运行时动态地植入和移除切面。这样一来,就可以实现更加灵活、可维护和可扩展的系统。
AOP 的具体表现包括切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)和引入(Introduction)等概念。其中,切面是指横跨多个对象的通用功能,连接点是程序执行过程中能够插入切面的点,通知则是定义了切面在连接点处所执行的操作,切点则是一个谓词表达式,用于匹配连接点,引入则是为某个对象添加新的接口实现。具体如下代码:
public void test_proxy_method() {
// 目标对象(可以替换成任何的目标对象)
Object targetObj = new UserService();
// AOP 代理
IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() {
// 方法匹配器
MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))");
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (methodMatcher.matches(method, targetObj.getClass())) {
// 方法拦截器
MethodInterceptor methodInterceptor = invocation -> {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("方法名称:" + invocation.getMethod().getName());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
};
// 反射调用
return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
}
return method.invoke(targetObj, args);
}
});
String result = proxy.queryUserInfo();
System.out.println("测试结果:" + result);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这段代码使用了 JDK 动态代理实现 AOP 的功能,没有使用 Spring 提供的方法、类和注解等。
- 连接点:连接点是在目标对象上匹配的特定点,这里的连接点是 IUserService 接口中的所有方法,由于使用了 targetObj.getClass ().getInterfaces () 获取目标对象所实现的接口,因此只拦截了 IUserService 接口中的方法。
- 切面:切面是一个模块化的横切关注点,在这里我们可以视为没有显式定义的切面。而是直接在 InvocationHandler.invoke () 中实现了拦截和增强逻辑,包括方法匹配器、方法拦截器和反射调用等。
- 切点:切点是一种谓词表达式,用于匹配连接点。这里使用了 AspectJ 表达式 "execution (* cn.bugstack.springframework.test.bean.IUserService.*(..))",它匹配了 IUserService 接口中的所有方法。
- 通知:通知类型包括前置通知、后置通知、环绕通知、抛出通知和最终通知。在这里使用了环绕通知,即在方法执行之前和之后添加了监控逻辑。
- 引入:引介通常是一个特殊的通知类型,它允许在运行时为类动态地添加新接口实现。这里没有使用引介。
# 封装
在 Spring 中,核心逻辑是离不开上面的代理例子的,只是相对应做了些封装,我们先用类图来简单说明下封装关系:
用测试例子来说明每步的核心
/**
* 切点表达式,来验证切点
* @throws NoSuchMethodException
*/
@Test
public void test_aop() throws NoSuchMethodException {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.UserService.*(..))");
Class<UserService> clazz = UserService.class;
Method method = clazz.getDeclaredMethod("queryUserInfo");
System.out.println("切点是否包含该类:" + pointcut.matches(clazz));
System.out.println("切点是否包含该类该方法:" + pointcut.matches(method, clazz));
}
/**
* 切面 和 动态代理
*/
@Test
public void test_dynamic() {
// 目标对象
IUserService userService = new UserService();
// 组装代理信息,切面
AdvisedSupport advisedSupport = new AdvisedSupport();
// 设置代理目标对象
advisedSupport.setTargetSource(new TargetSource(userService));
// 设置拦截器
advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
// 匹配代理对象
advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"));
// 代理对象(JdkDynamicAopProxy)
IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("测试结果:" + proxy_jdk.queryUserInfo());
// 代理对象(Cglib2AopProxy)
IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
// 测试调用
System.out.println("测试结果:" + proxy_cglib.register("花花"));
}
public class UserServiceInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("方法名称:" + invocation.getMethod());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 把封装的融入到 Spring 中
右侧部分就是描述了整个融合到 Spring 中的类,会在 Bean 创建的过程中 初始化方法之后 这个生命周期内先找到是否提供了 DefaultAdvisorAutoProxyCreator 类的支持,因为他描述了具体代理类的过程。
为什么会在初始化方法之后才进行代理,是因为代理类也需要的属性也需要被填充,所以等填充完毕后在代理
核心方法,描述了整个类被代理的过程
protected Object wrapIfNecessary(Object bean, String beanName) {
// 判断Bean是否是Advice,Pointcut,Advisor的子类或者两类相同可以相互转(类层面),用户定义的类都是 false
if (isInfrastructureClass(bean.getClass())) return bean;
// 得到注册的AspectJExpressionPointcutAdvisor
Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();
for (AspectJExpressionPointcutAdvisor advisor : advisors) {
ClassFilter classFilter = advisor.getPointcut().getClassFilter();
// 用表达式 过滤匹配类
if (!classFilter.matches(bean.getClass())) continue;
// 封装
AdvisedSupport advisedSupport = new AdvisedSupport();
TargetSource targetSource = new TargetSource(bean);
advisedSupport.setTargetSource(targetSource);
advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
advisedSupport.setProxyTargetClass(true);
// 返回代理对象
return new ProxyFactory(advisedSupport).getProxy();
}
return bean;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<beans>
<!-- 目标类 -->
<bean id="userService" class="cn.bugstack.springframework.test.bean.UserService"/>
<!-- 代理类 -->
<bean id="beforeAdvice" class="cn.bugstack.springframework.test.bean.UserServiceBeforeAdvice"/>
<!-- 组件类,至关重要 -->
<bean class="cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<!--
这里是 advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice()); 设置拦截器,
可以是前置拦截,后置拦截,或者环绕拦截
-->
<bean id="methodInterceptor" class="cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
<property name="advice" ref="beforeAdvice"/>
</bean>
<!-- 切面表达式 -->
<bean id="pointcutAdvisor" class="cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="expression" value="execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"/>
<property name="advice" ref="methodInterceptor"/>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20