Home Spring AOP
Post
Cancel

Spring AOP

AOP(Aspect Oriented Programming),面向切面编程(也叫面向方面),是Spring框架中的内容,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要的功能有:日志记录,性能统计,安全控制,事务处理,异常处理等等。

什么是AOP

AOP可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来

,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。

业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

OOP和AOP

OOP专注于对象,我们利用对象的属性,行为来解决现实中的问题,而AOP则用来在使用OOP解决问题的过程中增强解决问题的能力,实现更好的模块化。

AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。 而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。

AOP能做什么

AOP可以从增强模块化以及降低切面与业务逻辑的耦合度来增强OOP程序。使用AOP可以为需要某些特定功能的对象集合增加功能而不必修改这些对象的类代码。也就是我们将与实际业务逻辑无关但是需要关注的部分提取出来通过AOP来动态的添加到业务逻辑代码中。即使这些Aspect的实现机制以及代码进行了修改,只需改动一处而不会影响原有业务逻辑代码。

Spring中的AOP

与同样实现AOP的AspectJ不同,AspectJ将附加功能加入到目标对象中进行编译,Spring中附加功能与目标对象是分开的,在runtime时动态增加到目标对象中。这通常是通过Proxy实现的(可以看看设计模式中的代理模式,Spring的AOP就是基于Proxy实现的)。也因为Spring没有将附加功能与目标对象混合后编译所以Spring的在为对象增加附加功能只能实现在method(方法)的级别。相当于只能在执行某个method前或之后增加特定功能,而无法在方法体内部截断并增加功能。Spring通过配置文件来随时对一系列特定的对象增加、修改或取消特定功能而使程序不受其影响,也就是说这一系列对象即使没有通过Spring的AOP增加应用的功能时也是可以正常运行的。可以理解为这一系列对象根本不知道自己是否通过AOP被增加了某些功能。

AOP相关概念

切面(Aspect)

切面可以理解为我们程序中许多并无关联的对象、子程序所共同拥有的需要关注的方面,通常遍布于整个程序中

,比如我们需要在许多代码逻辑中增加安全性保障的代码(大部分这些代码都是相同的),比如在执行一个方法前后要进行日志记录,比如在执行方法时要实现事务,保证操作的原子性等等。这些一个共同点就是我们程序中需要关注的但是与实际业务逻辑无关。在利用AOP以前,它分散于整个程序中并混杂在业务逻辑代码中,假如对于安全性考虑需要变更安全性相关代码,则需要改动大量原先混杂在业务逻辑中的安全性处理代码。类似这种需要考虑的方面我们叫他切面,也就是Aspect。

切入点(PointCut)

指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解,MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上。

连接点(JointPoint)

程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

通知(Advice)

在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice:BeforeAdvice,AfterAdvice,ThrowAdvice和DynamicIntroductionAdvice。

目标对象(Target)

要使用方面功能的组件对象或被切入点表达式指定的对象,包含连接点的对象,也被称作被通知或被代理对象,POJO。

动态代理对象(AutoProxy)

Spring使用了AOP机制后,采用的是动态代理技术实现的,当采用了AOP之后,Spring通过getBean返回的对象是一个动态代理类型对象,当使用该对象的业务方法时,该对象会负责调用方面组件和目标组件的功能,如果未采用AOP,Spring通过getBean返回的是原始类型对象,因此执行的是原有目标对象的处理Spring动态代理技术采用的是以下两种:

  • 采用JDK Proxy API实现(目标对象有接口定义)
  • 采用Cglib.jar工具包API实现(目标对象没有接口定义)

引入(Introduction)

添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。Spring中要使用Introduction,可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。

AOP代理(AOP Proxy)

AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving)

组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯JavaAOP框架一样,在运行时完成织入。

AOP通知类型

通知主要负责指定方面功能和目标方法功能的作用关系。Spring框架提供了以下5种类型通知:

  • 前置通知<aop:before>:方面功能在目标方法之前调用
  • 后置通知<aop:after-returning>:方面功能在目标方法之后调用,目标方法无异常执行
  • 最终通知<aop:after>:方面功能在目标方法之后调用,目标方法有无异常都执行
  • 异常通知<aop:after-throwing>:方面功能在目标方法抛出异常之后执行
  • 环绕通知<aop:around>:方面功能在目标方法执行前和后调用

AOP切入点表达式的指定

切入点表达式用于指定哪些对象和方法调用方面功能,方法限定表达式:execution(修饰符? 返回类型 方法名(参数) throws 异常类型?)

  • 示例1–匹配所有Bean对象中以add开头的方法execution(* add*(..))
  • 示例2–匹配UserService类中所有的方法execution(* tarena.service.UserService.*(..))
  • 示例3–匹配UserService类中有返回值的所有方法execution(!void tarena.service.UserService.*(..))
  • 示例4–匹配所有Bean对象中修饰符为public,方法名为add的方法execution(public * add(..))
  • 示例5–匹配tarena.service包下所有类的所有方法execution(* tarena.service.*.*(..))
  • 示例6–匹配tarena.service包及其子包中所有类所有方法execution(* tarena.service..*.*(..))

如何使用AOP

可以通过配置文件或者编程的方式来使用Spring AOP。配置可以通过xml文件来进行,大概有四种方式:

  • 配置ProxyFactoryBean,显式地设置advisors,advice,target等。
  • 配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象。
  • 通过<aop:config>来配置。
  • 通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点。

也可以直接使用ProxyFactory来以编程的方式使用Spring AOP,通过ProxyFactory提供的方法可以设置target对象,advisor等相关配置,最终通过getProxy()方法来获取代理对象。

AOP使用示例

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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:context="http://www.springframework.org/schema/context" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
            http://www.springframework.org/schema/context  
            http://www.springframework.org/schema/context/spring-context-3.0.xsd 
            http://www.springframework.org/schema/aop  
            http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 
            http://www.springframework.org/schema/tx  
            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 

    <!-- AspectJ框架 方面编程 -->
    <bean id="join" class="com.book.aspect.BuyJoin"></bean>
    <bean id="appleBuy" class="com.book.AppleBuy"></bean>

    <aop:config>
        <!-- 执行:正则表达式,匹配切入点 
            1、execution(* com.book.AppleBuy.*(..))  AppleBuy类内所有的方法
            2、execution(* com.book.*.*(..))  com.book包下所有的类的方法内所有的方法
            3、execution(* com..*.*(..))  book包下及子包下的所有方法
        -->
        <!-- 定义切入点(什么位置执行通知) -->
        <aop:pointcut id="testpoint" 
                expression="execution(* com.book.AppleBuy.*buy*(..))"/>
        <!-- 定义切面(对切入点统一操作) -->
        <aop:aspect ref="join">
            <aop:around pointcut-ref="testpoint" method="buy"/>
        </aop:aspect>
    </aop:config>
</beans> 

BuyJoin.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.book.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

public class BuyJoin {
    public void buy(ProceedingJoinPoint join) throws Throwable {
        System.out.println("支付");
        // 买苹果的业务方法,需要用到连接点
        join.proceed();
        System.out.println("完成");
    }
}  

Test.java

1
2
3
4
5
6
7
8
9
public class Test {
    public static void main(String[] args) {
        BeanFactory bf = new ClassPathXmlApplicationContext("applicationContext1.xml");
        IBuy o = (IBuy) bf.getBean("appleBuy");
        o.buy();
        System.out.println("=========================");
        o.steal();
    }
} 
This post is licensed under CC BY 4.0 by the author.