详解Spring框架AOP(面向切面编程)
最近在学习AOP,之前一直很不明白,什么是AOP?为什么要使用AOP,它有什么作用?学完之后有一点小小的感触和自己的理解,所以在这里呢就跟大家一起分享一下
AOP(Aspect-Oriented Programming)其实是OOP(Object-Oriented Programing) 思想的补充和完善。我们知道,OOP引进"抽象"、"封装"、"继承"、"多态"等概念,对万事万物进行抽象和封装,来建立一种对象的层次结构,它强调了
一种完整事物的自上而下的关系。但是具体细粒度到每个事物内部的情况,OOP就显得无能为力了。比如日志功能。日志代码往往水平地散布在所有对象层次当
中,却与它所散布到的对象的核心功能毫无关系。对于其他很多类似功能,如事务管理、权限控制等也是如此。这导致了大量代码的重复,而不利于各个模块的重
用。 而AOP技
术则恰恰相反,它利用一种称为"横切"的技术,能够剖解开封装的对象内部,并将那些影响了多个类并且与具体业务无关的公共行为 封装成一个独立的模块(称
为切面)。更重要的是,它又能以巧夺天功的妙手将这些剖开的切面复原,不留痕迹的融入核心业务逻辑中。这样,对于日后横切功能的编辑和重用都能够带来极大
的方便。 AOP技术的具体实现,无非也就是通过动态代理技术或者是在程序编译期间进行静态的"织入"方式。下面是这方面技术的几个基本术语:
1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。
4、aspect(切面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
说了这么多,可能我们还是对AOP有点不知所措,不知道是干什么的,那么我们就以一个例子作为讲解,来理解这个抽象的概念
我们有一个简易的计算器,进行加减乘除的操作,有一个需求,1.需要在进行算法之前和之后进行输出一句话
那么对于以上操作我们可能最容易想到的就是用一个实现类实现这个接口。然后在接口调用方法前后输出一句话
这样确实能实现这个需求,但是可能有的同学就想到了,是不是重复代码了呢?如果我有上千个方法呢?是不是还得在每个方法里增加几行代码?我们能不能在不改变原来方法的结构上
也能实现相同的需求呢?这个时候AOP就能帮我们实现了。下面我们详细的讲解下如何使用注解的方式来实现AOP
还是同样的接口和实现类,只是这时候实现类中没有了输出语句,如图
上图就是最原始的方法了,也就是说我们在这个方法里面只需要关注我们方法执行的内容,并不需要关注一些方法之外的东西,比如说记录日志,方法前输出语句等等。。
那么,既然这个方法什么都不关注的话,那我们的输出语句又在哪儿写呢?这个时候我们就定义一个专门的类,用它来作为切面,代码如下所示
package advice; import java.util.Arrays; import java.util.List; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; 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.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect //声明注解 public class CalculationAnnotation { /** * 定义前置通知 * execution(* biz.UserBiz.*(..)) 表示 所有修饰符的所有返回值类型 biz.UserBiz 包下的所有方法 * 在方法执行之前执行 * */ @Before("execution(* biz.CalculationImpl.*(..))") public void before(JoinPoint join){ //获取方法名 String mathName=join.getSignature().getName(); //获取参数列表 List<Object> args = Arrays.asList(join.getArgs()); System.out.println("前置通知---->before 方法名是:"+mathName+"\t参数列表是:"+args); } /** * 后置通知 * 在方法返回后执行,无论是否发生异常 * 不能访问到返回值 * * */ @After("execution(* biz.CalculationImpl.*(..))") public void after(){ System.out.println("后置通知---->after...."); } }
@Aspect ----->表示声明这个类是一个切面,
这样呢,咱们这个切面就声明完毕了,那么,我们可以想到,这个时候我们只是声明了一个切面而已,并没有在那个地方用到这个切面对不对?也就是说我们配置的切面还跟我们程序还没有任何的关联关系
这样的话呢,就引出了我们的配置文件了也就是我们Spring的配置文件applicationContext.xml,配置如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <!-- 配置Bean --> <bean id ="CalculationImpl" class="biz.CalculationImpl"></bean> <!-- 将切面类交与Spring容器管理 --> <bean class="advice.CalculationAnnotation"></bean> <!-- 使用注解自动生成代理对象 --> <aop:aspectj-autoproxy/> </beans>
这个时候我们可以看看配置文件里到底写了什么,写这些是干啥的,有什么用。
这个大家肯定都懂是吧,这没话说,也就是将CalculationImpl类交给Spring容器来管理,如果有不懂的童鞋可以看看我的另一篇关于Spring IOC 的文章
那么这行代码呢?这行代码的意思就是将我们的CalculationAnnotation类交给Spring容器管理,因为我们在CalculationAnnotation类中不是声明了一个@Aspect切面注解吗对不对
当Spring容器初始化的时候它会找有没有这个节点,如果有的话呢,容器就会根据你的Bean配置,找看那个类中配置了@Aspect切面注解
如果找到了的话那么就根据你的注解来执行相应的代码,什么意思呢?比如说如图所示
好,那么我们就来看看执行之后结果会是怎样的呢?
这样我们是不是就完成了之前的需求呢?在执行代码前输出一行语句,如果我们想要做到日志的记录的话,是不是只需要把输出语句修改为记录日志的代码就可以了呢。而且我还没有影响任何的功能性代码
也就是对源代码并没有做任何的修改,那么既然有前置增强的话肯定也有后置增强和其他增强操作下面我就讲讲后置增强,
其实对于其他的增强类型的话呢,既然知道前置增强是怎么一回事了,那么其他四种就轻而易举了
后置增强,其实我们只需要在切面类也就是我们写前置增强的类中直接添加后置 增强代码即可,如图
只是将注解标签给进行了一道修改,其他的任何操作我们都不需要在进行修改,示例结果如图所示
这样是不是就完成了在方法前后执行与方法无关的代码呢?可能有些童鞋有疑问,为什么输出语句是在最后输出的,不应该是夹在中间吗?但是我们看测试代码,我是执行了add方法,接收了一个返回值,然后在方法的外面输出的我接收的返回值变量,那么这样的话,可不就是我们看到的结果嘛。
由于时间的关系呢,我今天就先给大家分享下前置增强和后置增强。至于返回,异常和环绕的话呢,我就下次在跟大家分享吧!希望大家能够学到点东西吧!