切面编程

 
    切面编程
       每个模块的核心功能都是为特定业务领域提供服务, 但是这些模块都需要类似的辅助功能, 例如安全和事务管理。如果要重用通用功能的话, 最常见的面向对象技术是继承(inheritance) 或委托(delegation) 。 但是, 如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系; 而使用委托可能需要对委托对象进行复杂的调用。
切面提供了取代继承和委托的另一种可选方案, 而且在很多场景下更清晰简洁。 在使用面向切面编程时, 我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用, 而无需修改受影响的类。 横切关注点可以被模块化为特殊的类, 这些类被称为切面(aspect) 。 这样做有两个好处: 首先, 现在每个关注点都集中于一个地方, 而不是分散到多处代码中; 其次, 服务模块更简洁, 因为它们只包含主要关注点(或核心功能) 的代码, 而次要关注点的代码被转移到切面中了。 
       AOP术语 
    与大多数技术一样, AOP已经形成了自己的术语。 描述切面的常用术语有通知(advice) 、 切点(pointcut) 和连接点(join point) 
    切面编程
通知( Advice) ,在AOP术语中, 切面的工作被称为通知。 
     Spring切面可以应用5种类型的通知:
        前置通知(Before) : 在目标方法被调用之前调用通知功能;
        后置通知(After) : 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
        返回通知(After-returning) : 在目标方法成功执行之后调用通知;
        异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
        环绕通知(Around) : 通知包裹了被通知的方法, 在被通知的方法调用之前和调用之后执行自定义的行为。 
连接点( Join point) 
应用可能也有数以千计的时机应用通知。 这些时机被称为连接点。 连接点是在应用执行过程中能够插入切面的一个点。 这个点可以是调用方法时、 抛出异常时、 甚至修改一个字段时。 切面代码可以利用这些点插入到应用的正常流程之中, 并添加新的行为。 
切点( Poincut) 
      通知定义了切面的“什么”和“何时”的话, 那么切点就定义了“何处”。 切点的定义会匹配通知所要织入的一个或多个连接点。 我们通常使用明确的类和方法名称, 或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。 切点有助于缩小切面所通知的连接点的范围。 

切面( Aspect) 
     切面是通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能。 
引入( Introduction) 
       引入允许我们向现有的类添加新方法或属性。 
织入( Weaving) 
       织入是把切面应用到目标对象并创建新的代理对象的过程。 切面在指定的连接点被织入到目标对象中。 在目标对象的生命周期里有多个点可以进行织入:
    编译期: 切面在目标类编译时被织入。 这种方式需要特殊的编译器。 
    类加载期: 切面在目标类加载到JVM时被织入。 这种方式需要特殊的类加载器(ClassLoader) , 它可以在目标类被引入应用之前增
    强该目标类的字节码。
    运行期: 切面在应用运行的某个时刻被织入。 一般情况下, 在织入切面时, AOP容器会为目标对象动态地创建一个代理对象。 Spring    AOP就是以这种方式织入切面的。 

基于代理的经典Spring AOP;
    纯POJO切面;
    @AspectJ注解驱动的切面;
    注入式AspectJ切面(适用于Spring各版本) 。  
引入了简单的声明式AOP和基于注解的AOP之后,直接使用 ProxyFactory Bean的 Spring典AOP看起来就显得非常笨重和过于复杂。
  借助Spring的aop命名空间, 我们可以将纯POJO转换为切面。 实际上, 这些POJO只是提供了满足切点条件时所要调用的方法。 遗憾的是,这种技术需要XML配置, 但这的确是声明式地将对象转换为切面的简便方式。 
Spring借鉴了AspectJ的切面, 以提供注解驱动的AOP。 本质上, 它依然是Spring基于代理的AOP, 但是编程模型几乎与编写成熟的    AspectJ注解切面完全一致。 这种AOP风格的好处在于能够不使用XML来完成功能。 
如果你的AOP需求超过了简单的方法调用(如构造器或属性拦截) , 那么你需要考虑使用AspectJ来实现切面。 在这种情况下, 上文所示的第四种类型能够帮助你将值注入到AspectJ驱动的切面中。 
Spring只支持方法级别的连接点 

通过切点来选择连接点 :
切点用于准确定位应该在什么地方应用切面的通知。 通知和切点是切面的最基本元素。 
    切面编程
在Spring中尝试使用AspectJ其他指示器时, 将会抛出IllegalArgument-Exception异常。 只有execution指示器是实际执行匹配的, 而其他的指示器都是用来限制匹配的。 这说明execution指示器是我们在编写切点定义时最主要使用的指示器。 在此基础上, 我们使用其他指示器来限制所匹配的切点。 
编写切点 
    切面编程
现在假设我们需要配置的切点仅匹配concert包。 在此场景下, 可以使用within()指示器来限制匹配 
    切面编程
我们使用了“&&”操作符把execution()和within()指示器连接在一起形成与(and) 关系(切点必须匹配所有的指示器) 。 类似地, 我们可以使用“||”操作符来标识或(or) 关系, 而使用“!”操作符来标识非(not) 操作。因为“&”在XML中有特殊含义, 所以在Spring的XML配置里面描述切点时, 我们可以使用and来代替“&&”。 同样, or和not可以分别用来代替“||”和“!”。 
在切点中选择bean 
    切面编程
AspectJ面向注解的模型可以非常简便地通过少量注解把任意类转变为切面 
    切面编程
Audience类使用@AspectJ注解进行了标注。 该注解表明Audience不仅仅是一个POJO, 还是一个切面。 Audience类中的方法都使用注解来定义切面的具体行为。 
    切面编程
通过@Pointcut注解声明频繁使用的切点表达式 
    切面编程
像其他的Java类一样, 它可以装配为Spring中的bean: 
    切面编程
如果你就此止步的话, Audience只会是Spring容器中的一个bean。 即便使用了AspectJ注解, 但它并不会被视为切面, 这些注解不会解析,也不会创建将其转换为切面的代理。
如果你使用JavaConfig的话, 可以在配置类的类级别上通过使用EnableAspectJ-AutoProxy注解启用自动代理功能。 程序清单4.3展现了如何在JavaConfig中启用自动代理。 
在JavaConfig中启用AspectJ注解的自动代理 
    切面编程
假如你在Spring中要使用XML来装配bean的话, 那么需要使用Spring aop命名空间中的<aop:aspectj-autoproxy>元素。 下面的XML配置展现了如何完成该功能。 
在XML中, 通过Spring的aop命名空间启用AspectJ自动代理 
    切面编程
不管你是使用JavaConfig还是XML, AspectJ自动代理都会为使用@Aspect注解的bean创建一个代理, 这个代理会围绕着所有该切面的切点所
匹配的bean。 在这种情况下, 将会为Concertbean创建一个代理, Audience类中的通知方法将会在perform()调用前后执行。 

使用环绕通知重新实现Audience切面 
    切面编程
需要注意的是, 别忘记调用proceed()方法。 如果不调这个方法的话, 那么你的通知实际上会阻塞对被通知方法的调用。 有可能这就是你想要的效果, 但更多的情况是你希望在某个点上执行被通知的方法。 

处理通知中的参数:
如果切面所通知的方法确实有参数该怎么办呢? 切面能访问和使用传递给被通知方法的参数吗? 
    切面编程
这里的不同点在于切点还声明了要提供给通知方法的参数。 图4.6将切点表达式进行了分解, 以展现参数是在什么地方指定的。 
    切面编程