Spring核心技术——AOP原理解析

265 阅读4分钟

引言

AOP,作为Spring的两大特性之一,想必所有人都会背这一段概念:面向切面编程,通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。何为切面,所谓切面,其实就可以理解为接口或者方法的整体,我们想对某个方法进行逻辑上的补充,但又不想改动整个方法体,于是就可以利用这种切面的思想,在方法体的上下环境中进行逻辑填充,这是设计模式当中代理模式的思想,同时也是AOP的底层机制。本文就来详细介绍AOP到底做了什么,以及我们该怎么使用这一神器。

AOP原理

讲到AOP的底层机制,不得不提到代理模式,我们常接触到的代理模式主要有三种:静态代理、动态代理以及Cglib代理。笔者以静态代理为例,详细阐述代理模式的特点。

首先,创建一个目标接口:

/**
 * 目标接口
 */
public interface IUserDao {
    void save();
}

然后,要创建一个目标对象,来实现目标接口的方法:

public class UserDao implements IUserDao {

    @Override
    public void save() {
        System.out.println("保存数据");
    }

}

如果现在需要对 save() 方法进行增强,比方说,在原方法体上方打印日志提示已经开始处理数据,在原方法体下方打印日志提示已经处理数据完毕。一般而言,可以直接修改save方法体,达到效果,但如果一旦有了新的需求,又需要修改原始代码,这样无疑增加了开发的不可控性。代理模式的解决方案是再创建一个代理对象,然后将目标对象作为参数传入代理对象,在代理对象中的代理方法中实现逻辑增强。

public class UserDaoProxy implements IUserDao {

    public IUserDao target;

    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }

    @Override
    public void save() {
        System.out.println("开始保存数据!");
        target.save(); // 目标对象方法
        System.out.println("保存数据完毕!");
    }

}

以上就是代理模式的一个简单实现,就如上述所言,代理模式最大的优势就是解耦。但静态代理有一个问题,我们需要手动创建代理类并编写增强代码。动态代理便解决了这个问题,利用java.lang.reflect.Proxy 下的 Proxy.newProxyInstance() 方法在运行时创建代理对象。

以上就是AOP的底层机制——代理模式的应用,Spring默认使用JDK的动态代理。

AOP应用

在介绍 AOP 应用之前,还需要普及一些基本概念(参考细说Spring——AOP详解(AOP概览)):

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

下面本文开始正式介绍如何应用 AOP :

  • 首先,引入web和aop依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.4</version>
        </dependency>
        <!-- aop 依赖, aspectj -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    

    注意,后续还会用到lombok的注解,看个人喜好,可以在这里加上lombok的依赖

  • 然后,为了简单起见,直接创建Service层和Controller层
    @Service
    @Slf4j
    public class UserService {
    
    	public void save() {
        	log.info("saving data!");
    	}
    }
    
    @Controller
    @RequestMapping("demo/")
    public class DemoController {
    
        final UserService userService;
    
        public DemoController(UserService userService) {
            this.userService = userService;
        }
        
        /**
         * 模拟 AOP
         */
        @RequestMapping("/aop")
        @ResponseBody
        public void aopTest() {
            userService.save();
        }
    }
    
  • 构建切面类
    @Aspect
    @Component
    @Slf4j
    public class UserServiceAspect {
    
        /**
         * 环绕切面
         */
        @Around("execution(* com.computer.fundamentals.networkprogram.service.UserService.*(..))")
        public void saveHandle(ProceedingJoinPoint proceedingJoinPoint) {
            log.info("start");
    
            Object result;
            try {
                result = proceedingJoinPoint.proceed();
            } catch (Throwable throwable) {
                log.info("server error");
            }
    
            log.info("end");
        }
    
    }
    

经过测试,日志界面会打印出如下结果:

2021-11-01 10:07:11.887  INFO 12052 --- [nio-8080-exec-1] c.c.f.n.service.aop.UserServiceAspect    : start
2021-11-01 10:07:11.894  INFO 12052 --- [nio-8080-exec-1] c.c.f.n.service.UserService              : saving data!
2021-11-01 10:07:11.894  INFO 12052 --- [nio-8080-exec-1] c.c.f.n.service.aop.UserServiceAspect    : end

这里采用的是环绕切面,在方法体的上下文补充逻辑,事实上还有许多种切面,有需要的同学可以自行查阅了解,这里仅仅是抛砖引玉。

结语

看到这里,各位同学是不是觉得 AOP 确实是一大神器,它可以在不影响原方法体的情况下添加许多有用的东西,如日志记录、运行时间测算等等,而且它实际上就是代理模式的一种具体应用,熟悉了这一设计模式,那么理解 AOP 也就不那么难了。