AOP技术背景
AOP(Aspect-Oriented Programming)其实是OOP(Object-Oriented Programing) 思想的补充和完善。OOP引进"抽象"、"封装"、"继承"、"多态"等概念,对万事万物进行抽象和封装,来建立一种对象的层次结构,它强调了一种完整事物的自上而下的关系。但是具体细粒度到每个事物内部的情况,OOP就显得无能为力了。比如日志功能。日志代码往往水平地散布在所有对象层次当中,却与它所散布到的对象的核心功能毫无关系。对于其他很多类似功能,如事务管理、权限控制等也是如此。这导致了大量代码的重复,而不利于各个模块的重用。 而AOP技术则恰恰相反,它利用一种称为"横切"的技术,能够剖解开封装的对象内部,并将那些影响了多个类并且与具体业务无关的公共行为 封装成一个独立的模块(称为切面)。更重要的是,它又能将这些剖开的切面复原,不留痕迹的融入核心业务逻辑中。这样,对于日后横切功能的编辑和重用都能够带来极大的方便。
AOP技术的具体实现,就是通过动态代理技术或者是在程序编译期间进行静态的"织入"方式。
AOP的相关术语
Aspect:表示切面。切入业务流程的一个独立模块。一个应用程序可以拥有任意数量的切面。
Join point:表示连接点。也就是业务流程在运行过程中需要插入切面的具体位置。
Advice:表示通知。是切面的具体实现方法。可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种。实现方法具体属于哪类通知,是在配置文件和注解中指定的。
Pointcut:表示切入点。用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上。
Target:表示目标对象。被一个或者多个切面所通知的对象。
Proxy:表示代理对象。将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象为目标对象的业务逻辑功能加上被切入的切面所形成的对象。
Weaving:表示切入,也称为织入。将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期。
常用的AOP实现方式有两种。第一种是基于xml配置文件方式的实现,第二种是基于注解方式的实现。
下面以一个具体的例子来说明AOP技术。
有一个简易的计算器,进行加减的操作,有一个需求:需要在进行算法之前和之后进行输出一句话
public inteface Calculation{
//加法
public void add(int i,int j);
//减法
public void sub(int i,int j);
}
最容易想到的就是用一个实现类实现这个接口,然后在接口调用方法前后输出一句话.
这样确实能实现这个需求,但是如果有上千个方法呢?是不是得在每个方法里增加几行代码?我们能不能在不改变原来方法的结构上也能实现相同的需求呢?这个时候AOP就能帮我们实现了。下面详细的讲解下如何使用注解的方式来实现AOP,还是同样的接口和实现类。
public class CalculationImpl impletments Calculation{
@Override
public void add() {
//实现业务逻辑
}
@Override
public void sub() {
//实现业务逻辑
}
}
定义一个专门的类,用它来作为切面。
@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....");
}
}
这样呢切面就声明完毕了,那么,这个时候我们只是声明了一个切面而已,并没有在那个地方用到这个切面?也就是说配置的切面还跟程序还没有任何的关联关系
这样的话呢,就引出了配置文件也就是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 id ="CalculationImpl" class="biz.CalculationImpl"></bean>
<!--配置AOP操作-->
<aop:config>
<!--配置切入点:通过表达式实现-->
<aop:pointcut id="CalculationImpl" expression="execution(*
com.tulun.bean.Student1.add(..))"/>
<!--配置切面:把增强用到方法上-->
<aop:aspect ref="add" >
<!--配置增强类:使用的前置增强-->
<aop:before method="before" pointcut-ref="CalculationImpl"/>
</aop:aspect>
</aop:config>
</beans>