大部分Java程序员都很了解Spring的IOC和AOP,但是对其内涵却缺乏系统性的了解。今天我们就一起来学习一下AOP。 AOP即面向切面编程,其通过预编译方式和运行期动态代理实现程序功能的统一维护。它是对于传统面向对象编程(OOP)的补充,目前已经发展成为了一种较为成熟的编程技术。下面我们就来详细了解一下AOP的概念和特点。 一、AOP的缘起 对于OOP来说,按照业务对象对系统进行抽象和定义,是大家非常熟悉的一种编程方式。但是对于事务控制、权限控制、日志控制等一些非业务逻辑的控制,如果将它们分散到各个业务模块中进行定义,那么当某个业务模块关闭或者需要修改的时候,势必会对非业务逻辑 控制的代码产生影响。这样的话,不仅增加了程序开发人员的工作量,还有可能引入新的Bug。 因此,AOP为此问题提供了解决方案,在不影响原来功能的代码的基础上,采用横向抽取的方式,将分散在各个业务模块中实现非业务操作(如权限验证、事务控制、日志记录)的重复代码提取出来,定义为一些独立的个体。在程序编译或者运行时,通过动态代理的方式将这些非业务操作的独立个体加入到主业务流程中。这样的话,即使以后根据需要移除这些非业务操作,也不会影响主业务流程。 二、什么是Spring AOP AOP的主要编程对向是切面。要想理解切面的概念,就要先理解什么是面。从日常生活中来看,常常听说“以点带面”这句话,点表示个体,面表示群体。也就是说,一个群体都要做的事情,就是“面”。例如,大家都要进一道门,门上设置了指纹锁,大家都要开锁才能进门,那么“刷指纹进门”这个动作就是一个涉及“面”的动作。那么,如何将这个概念抽象到编程里来呢? 现在的软件开发多采用基于OOP的分层机制,如最常用的MVC,就分为业务实体层、业务逻辑层、控制层、表示层等。项目的每个层是一个对象或多个对象。例如一个学生管理系统,可能分为成绩管理、学生管理、用户管理等模块。那么如果这些模块都需要记录日志,应该怎么办呢? 如果按照OOP的思路,那么就需要先建立一个日志类,类中提供记录日志的方法。然后在控制层或业务逻辑层进行调用。但是这样的话,会存在一些问题,如日志记录与主业务流程有没有关系,而且按照这种方式会产生大量重复的代码,难以修改和维护。那么解决方案就是统一处理,横切一刀。在控制层和业务逻辑层间横切一刀,切出一个面。通过切面在各个模块的方法中加入日志记录功能,从而实现日志记录的统一处理。这就是AOP的应用,AOP是OOP的补充,可以实现对目标程序功能的增强。同时,使用AOP可以使开发人员在编写业务逻辑时能够专注于核心业务,而不用过多的关心其它非业务逻辑功能的实现。 三、AOP的常用术语 在AOP中,除了切面以外,还有一些专用的术语。包括: 1、切面(Aspect):切面=处理+切入点,通常指封装后的用户横向切入主业务流程中的非业务逻辑实现类,如事务管理、日志记录等。 2、处理(Advice):即增强处理,具体指在定义好的切入点处要执行的增强处理代码,如执行日志记录操作的代码。 3、切入点(Pointcut):指切面与目标业务程序的交叉点,一般切入点指得是类或方法。如果某个处理要应用于所有以delete开头的方法,则所有满足这一规则的方法都是切入点。 4、目标对象(Target):处理被应用的对象。 5、织入(Weaving):指有了切面和待切入的目标对象的切入点以后,通过生成代理对象的方式将切面代码插入目标的过程。 6、代理对象(Proxy):指将处理应用到目标对象后,被动态创建的对象。代理指为别人的业务提供增值服务。 四、AOP的处理类型 AOP的处理类型有: 1、前置处理:Before 2、环绕处理:Around 3、后置处理:After 4、后置返回处理:AfterReturning 5、后置异常处理:AfterThrowing 其执行顺序为: 1、环绕处理执行开始 2、前置处理执行开始 3、前置处理执行完毕 4、进入目标方法 5、环绕处理执行完毕 6、后置处理执行开始 7、后置处理执行完毕 8、后置返回处理执行开始 9、后置返回处理执行完毕 其架构如图所示: 五、AOP的实现 我们下面以一个例子来看AOP是如何实现的。比如我们要实现“所有的get请求在被调动前打印出一句话”。 那么我们首先要确定一个Pointcut,即切入点,所有的get请求,这个切入点可以通过Spring的GetMapping这个注解来找到。 那么我们先实现一个AOP切面类,实现起来很简单,只需要加个@Aspect注解即可,通常还要与@Component一起使用,表示这个类将由Spring来管理。我们将在这个类里实现Advice。 代码如下: package com.jingudi.framework.log.log.aspect; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class LogAdvice { // 定义一个切点:所有被GetMapping注解修饰的方法会织入advice @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)") private void logAdvicePointcut(){} @Before("logAdvicePointcut()") public void logAdvice(){ // 这里只是一个示例,你可以写任何处理逻辑 System.out.println("get请求的advice触发了"); } } 然后我们随便创建一个带@GetMapping注解的接口,就可以了。代码如下: @ApiOperation("查询用户列表") @GetMapping public PageResult<UserEntity> getUserList(QueryUserVo vo) { return userService.getUserList(vo); } 执行效果如下: 这是一个很简单的例子,我们应用了Spring里面的GetMapping注解来寻找切入点。通常在实际应用中,我们可以自定义一个注解,然后利用这个注解来注释需要切入的地方。就可以了。而且在自定义注解中还可以定义一些变量,从而实现更多的功能。 |
|