Spring AOP源码学习:基本概念

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 本文的内容以 AspectJ 来进行介绍。

前言


之前用十几篇文章介绍了 Spring IoC的源码,作为与 IoC 齐名的 AOP 自然也不能错过。同样的,接下去将会通过几篇文章来解析 Spring AOP 的源码。

如何将 Spring 源码导入 IDEA,请参考:Spring IoC源码学习:总览

 

注:本文的内容以 AspectJ 来进行介绍。

 

关于AOP


百度百科:AOP Aspect Oriented Programming,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

 

例子


我们有两个接口,一个用于进行加法计算,一个用于进行减法计算,为了避免计算出现问题,我们需要对每次接口调用的入参进行日志记录,于是我们有了以下的第一版实现。

image.png


看起来还不错,简单明了。但是这个方案有个问题,就是后续每次新增一个接口,就需要拷贝一次记录入参的代码。对于一个懒人,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个方法,于是我们有了以下第二版实现。这里有点切面的味道了。 

image.png

这个方案看起来更好了,但是同还是存在问题,虽然不用每次都拷贝代码了,但是,每个接口总得要调用这个方法吧,有办法让调用也省掉吗。我们设想一下,我们可以通过策略识别出所有要加入日志记录的接口,然后在接口调用时,将日志记录注入到接口调用的地方(切点),这就是 AOP 的核心思想。按这个思想,我们有了第三版的实现。

image.png

这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。 红框处,就是面向切面编程的思想。

 

AOP 的常见概念


通过上面的例子,大家应该对 AOP 有了初步的认识,下面介绍下 AOP 涉及的相关概念。


Joinpoint(连接点):在系统运行之前,AOP 的功能模块都需要织入到具体的功能模块中。要进行这种织入过程,我们需要知道在系统的哪些执行点上进行织入过程,这些将要在其之上进行织入操作的系统执行点就称之为 Joinpoint,最常见的 Joinpoint 就是方法调用。


Pointcut(切点):用于指定一组 Joinpoint,代表要在这一组Joinpoint 中织入我们的逻辑,它定义了相应 Advice 将要发生的地方。通常使用正则表达式来表示。对于上面的例子,Pointcut 就是表示所有要加入日志记录的接口的一个表达式。例如:“execution(* com.joonwhee.open.demo.service..*.*(..))”


Advice(通知/增强)Advice 定义了将会织入到 Joinpoint 的具体逻辑,通过 @Before@After@Around 来区别在 JointPoint 之前、之后还是环绕执行的代码。


Aspect(切面)Aspect 是对系统中的横切关注点逻辑进行模块化封装的 AOP 概念实体。类似于 Java 中的类声明,在 Aspect 中可以包含多个 Pointcut 以及相关的 Advice 定义。


Weaving(织入):织入指的是将 Advice 连接到 Pointcut 指定的 Joinpoint 处的过程,也称为:将 Advice 织入到 Pointcut 指定的 Joinpoint 处。


Target(目标对象):符合 Pointcut 所指定的条件,被织入 Advice 的对象。

 

对于上面的例子来说:


·       加法接口减法接口每次被调用时所处的程序执行点都是一个 Jointpoint

·       Pointcut 就是用于指定加法接口减法接口的一个表达式,当然这个表达式还可以指定很多其他的接口,表达式常见的格式为:“execution(* com.joonwhee.open.demo.service..*.*(..))”

·       Aspect 是定义 AdvicePointcut 的地方

·       Advice 就是我们要在加法接口减法接口织入的日志记录逻辑

·       Weaving 就是指将日记记录逻辑加到加法接口减法接口的过程

·       Target 就是定义了加法接口减法接口的对象实例

 

整体如下图所示:

image.png


简单的使用


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * @author joonwhee
 * @date 2019/3/3
 */
@Component
@Aspect
public class AopAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(AopAspect.class);
    @Pointcut("execution(* com.joonwhee.open.demo.service..*.*(..))")
    public void pointcut() {
    }
    @Before("pointcut()")
    public void before() {
        LOGGER.info("before advice");
    }
    @After("pointcut()")
    public void after() {
        LOGGER.info("after advice");
    }
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
        System.out.println("around advice start");
        try {
            Object result = proceedingJoinPoint.proceed();
            System.out.println("result: " + result);
            System.out.println("around advice end");
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

 

实现机制


Spring AOP 底层实现机制目前有两种:JDK 动态代理、CGLIB  动态字节码生成。在阅读源码前对这两种机制的使用有个认识,有利于更好的理解源码。

 

JDK 动态代理


public class MyInvocationHandler implements InvocationHandler {
    private Object origin;
    public MyInvocationHandler(Object origin) {
        this.origin = origin;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke start");
        Object result = method.invoke(origin, args);
        System.out.println("invoke end");
        return result;
    }
}
public class JdkProxyTest {
    public static void main(String[] args) {
        UserService proxy = (UserService) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(),
                new Class[]{UserService.class}, new MyInvocationHandler(new UserServiceImpl()));
        proxy.doSomething();
    }
}

 

CGLIB 代理

public class CglibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("intercept start");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("intercept end");
        return result;
    }
}
public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CglibObject.class);
        enhancer.setCallback(new CglibInterceptor());
        CglibObject proxy = (CglibObject) enhancer.create();
        proxy.doSomething();
    }
}
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
21天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
21天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
21天前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
8天前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
5天前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
15 4
|
11天前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
39 10
|
11天前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
10天前
|
XML 存储 Java
Spring-源码深入分析(二)
Spring-源码深入分析(二)
|
10天前
|
XML 设计模式 Java
Spring-源码深入分析(一)
Spring-源码深入分析(一)
|
2月前
|
小程序 前端开发 Java
SpringBoot+uniapp+uview打造H5+小程序+APP入门学习的聊天小项目
JavaDog Chat v1.0.0 是一款基于 SpringBoot、MybatisPlus 和 uniapp 的简易聊天软件,兼容 H5、小程序和 APP,提供丰富的注释和简洁代码,适合初学者。主要功能包括登录注册、消息发送、好友管理及群组交流。
62 0
SpringBoot+uniapp+uview打造H5+小程序+APP入门学习的聊天小项目
下一篇
无影云桌面