Spring详解(三):AOP⾯向切⾯的编程
AOP即⾯向切⾯编程,它通过预编译⽅式和运⾏期动态代理实现程序功能的统⼀维护的⼀种技术。AOP是OOP的延续,是软件开发中的⼀个热点,也是Spring框架中的⼀个重要内容,是函数式编程的⼀种衍⽣范型。利⽤AOP可以对业务逻辑的各个部分进⾏隔离,从⽽使得业务逻辑各部分之间的耦合度降低,提⾼程序的可重⽤性,同时提⾼了开发的效率。常⽤于⽇志记录,性能统计,安全控制,事务处理,异常处理等等。
中⽂名称:⾯向切⾯的编程
英⽂名称:Aspect Oriented Programming
正常程序执⾏流程都纵向执⾏流程:
1. 在原有纵向执⾏流程中添加⼀个横切⾯
2. 不需要修改原有程序代码,体现出程序的⾼扩展性
特点:
⾼扩展性
原有功能相当于释放了部分逻辑,让职责更加明确
⽤⼀句话来说⾯向切⾯的编程就是指:在原有纵向执⾏流程中,针对某⼀个或者某⼀些⽅法添加通知(前置通知和后置通知以及环绕通知),形成横切⾯过程就叫做⾯向切⾯编程。
1. AOP ⾯向切⾯编程术语
描述切⾯的常⽤术语有通知(advice)、切点(pointcut)和连接点(join point)等。下图展⽰了这些概念是如何关联在⼀起的。
1.1 通知 Advice
在AOP术语中,切⾯的⼯作被称为通知。 通知定义了切⾯是什么以及何时使⽤。除了描述切⾯要完成的⼯作,通知还解决了何时执⾏这个⼯作的问题。Spring切⾯可以应⽤5种类型的通知:
前置通知(Before):在⽬标⽅法被调⽤之前调⽤通知功能;
spring aop应用场景后置通知(After):在⽬标⽅法完成之后调⽤通知,此时不会关⼼⽅法的输出是什么;
返回通知(After-returning):在⽬标⽅法成功执⾏之后调⽤通知;
异常通知(After-throwing):在⽬标⽅法抛出异常后调⽤通知;
环绕通知(Around):通知包裹了被通知的⽅法,在被通知的⽅法调⽤之前和调⽤之后执⾏⾃定义的⾏为。
1.2 连接点(Join point)
我们的应⽤可能有数以千计的时机应⽤通知。这些时机被称为连接点。连接点是在应⽤执⾏过程中能够插⼊切⾯的⼀个点。这个点可以是调⽤⽅法时、抛出异常时、甚⾄修改⼀个字段时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的⾏为。
1.3 切点(Pointcut)
如果说通知定义了切⾯的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织
⼊的⼀个或多个连接点。我们通常使⽤明确的类和⽅法名称,或是利⽤正则表达式定义所匹配的类和⽅法名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运⾏时的决策(⽐如⽅法的参数值)来决定是否应⽤通知。
1.4 切⾯(Aspect)
切⾯是通知和切点的结合。通知和切点共同定义了切⾯的全部内容——它是什么,在何时和何处完成其功能。
1.5 引⼊(Introduction)
引⼊允许我们向现有的类添加新⽅法或属性。例如,我们可以创建⼀个Auditable通知类,该类记录了对象最后⼀次修改时的状态。这很简单,只需⼀个⽅法,setLastModified(Date),和⼀个实例变量来保存这个状态。然后,这个新⽅法和实例变量就可以被引⼊到现有的类中,从⽽可以在⽆需修改这些现有的类的情况下,让它们具有新的⾏为和状态。
1.6 织⼊(Weaving)
织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程。切⾯在指定的连接点被织⼊到⽬标对象中。在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:
编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就是以这种⽅式织⼊切⾯的。
类加载期:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器(ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ 5的加载时织⼊(load-timeweaving,L TW)就⽀持以这种⽅式织⼊切⾯。
运⾏期:切⾯在应⽤运⾏的某个时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象动态地创建⼀个代理对象。Spring AOP就是以这种⽅式织⼊切⾯的。
2. Spring 对AOP的⽀持
Spring 提供了4种类型的AOP⽀持:
1. 基于代理的经典Spring AOP(不推荐使⽤)
2. 借助Spring AOP 命名空间,纯POJO切⾯
3. @Aspect注解驱动的切⾯
4. 注⼊Aspect切⾯
经典的Spring AOP⼗分笨重和过于复杂,它之间使⽤ProxyFactory Bean让⼈感觉厌烦。所以我们不推荐使⽤基于代理的经典的Spring AOP。
借助Spring的AOP命令空间,我们可以将存POJO转化为切⾯。实际上,这只是提供了满⾜切点条件时所要⽤的调⽤的⽅法。遗憾的是,这种技术需要XML的配置,但这的确是声明式地将对象转化为切⾯的简单⽅法。本质上还是使⽤Spring基于代理的AOP。
Spring借鉴了AspectJ的切⾯,已提供注解驱动的AOP。本质上它还是基于代理的AOP,但是编程模型机会与编写成熟的AspectJ注解切⾯完全⼀致。
AspectJ切⾯,提供了超过简单的⽅法调⽤(⽐如构造器或者属性拦截),AspectJ切⾯可以拦截对象更多的调⽤。
2. Spring在运⾏时通知对象
通过在代理类中包裹切⾯,Spring在运⾏期把切⾯织⼊到Spring管理的bean中。如下图所⽰,代理类封装了⽬标类,并拦截被通知⽅法的调⽤,再把调⽤转发给真正的⽬标bean。当代理拦截到⽅法调⽤时,在调⽤⽬标bean⽅法之前,会执⾏切⾯逻辑。
直到应⽤需要被代理的bean时,Spring才创建代理对象。如果使⽤的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运⾏时才创建代理对象,所以我们不需要特殊的编译器来织⼊Spring AOP的切⾯。正如前⾯所探讨过的,通过使⽤各种AOP⽅案可以⽀持多种连接点模型。因为Spring基于动态代理,所以Spring只⽀持⽅法连接点。这与⼀些其他的AOP框架是不同的,例如AspectJ和JBoss,除了⽅法切点,它们还提供了字
段和构造器接⼊点。Spring缺少对字段连接点的⽀持,⽆法让我们创建细粒度的通知,例如拦截对象字段的修改。⽽且它不⽀持构造器连接点,我们就⽆法在bean创建时应⽤通知。但是⽅法拦截可以满⾜绝⼤部分的需求。如果需要⽅法拦截之外的连接点拦截功能,那么我们可以利⽤Aspect来补充Spring AOP的功能。
3. 借助Spring AOP 命名空间,纯POJO切⾯
每个通知都需要实现接⼝或者类
前置通知
public class AfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1)throws Throwable {
System.out.println("切点⽅法返回对象: "+o);
System.out.println("切点⽅法对象:"+method+", ⽅法名:"+Name());
if(method!=null && objects.length>0)
System.out.println("切点⽅法参数:"+objects);
else
System.out.println("切点所在⽅法没有参数.");
System.out.println("切点⽅法对象:"+o1);
System.out.println("---------后置通知--------");
System.out.println();
}
}
环绕通知
public class AroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation)throws Throwable {
System.out.println("---------执⾏前置--------");
Object proceed = methodInvocation.proceed();
System.out.println("---------后置通知--------");
return proceed;
}
}
后置通知
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o)throws Throwable {
System.out.println("切点⽅法对象:"+method+", ⽅法名:"+Name());
if(method!=null && objects.length>0)
System.out.println("切点⽅法参数:"+objects);
else
System.out.println("切点所在⽅法没有参数.");
System.out.println("切点⽅法对象:"+o.getClass());
System.out.println("---------执⾏前置--------");
}
}
异常通知只有当切点报异常后,才能触发异常通知
public class ThrowAdvice implements ThrowsAdvice{
public void afterThrowing(Method method, Object[] objects, Object target, Exception ex)throws Throwable{ //Do something with Exception
System.out.println("执⾏异常通过Schema-Based的⽅法执⾏异常: "+ex.getMessage());
}
public void afterThrowing(Exception ex)throws Throwable{
//Do something with Exception
System.out.println("执⾏异常通过Schema-Based的⽅法执⾏异常2: "+ex.getMessage());
}
}
定义切点(需要被切⼊的类)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论