Hello Spring Framework——面向切面编程(AOP)

本文主要参考了Spring官方文档第10章以及第11章和第40章的部分内容。如果要我总结Spring AOP的作用,不妨借鉴文档里的一段话:One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP, meaning you do not need to use AOP if you don’t want to, AOP complements Spring IoC to provide a very capable middleware solution.(译:如果你不想使用AOP完全可以忽略,只单独使用IoC。但是作为重点,Spring AOP框架是对IoC的有力补充)。

***************************以下是正文部分***************************

官方文档对AOP的讲述有点复杂,我稍微修改了一下组织结构,试图从实用性的角度介绍Spring AOP。有些词汇的译法并没有采用公认的翻译。

一、几个重要的概念

切面(Aspect):能够嵌入多个类的模块。Spring AOP框架使用普通类实现切面。

连接点(Join point):正常业务流上的作用点,例如一个方法或一段异常处理。

建言(Advice):在切点上执行的方法。不同类型的建言包括:"around", "before" 和 "after"。

切点(Pointcut):代表了嵌入模块上的作用点。只有切点上才能运行建言。

AOP代理(AOP proxy):SpringAOP通常采用JDK Dynamic Proxy或CGLIB两种方式实现AOP代理。

织入(Weaving):织入的概念是所有AOP框架所共有的。它指将切面对象与建言对象连接的行为。织入行为能够始于编译期(compile time)、加载期(load time)或运行期(runtime)。Spring AOP的织入是在运行期。

二、对于@AspectJ的支持

Spring AOP框架支持@AspectJ风格的注解声明。

AspectJ也是一种AOP框架,它是对JVM的一种嵌入式开发。AspectJ的AOP织入始于编译期。Spring AOP框架仅借鉴了AspectJ提供的注解方案,因此织入依然是在运行期完成的。

(1)通过Maven添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.2.2.RELEASE</version>
</dependency>

maven依赖

注意:使用IDE查询相关依赖关系可以看到spring-aspects仅仅依赖了aspectjweaver包,在有些教材中建议大家引入整个AspectJ包其实是不准确的。对于依赖关系的管理,我的建议是尽量做到精确引入,以防未知异常。

(2)声明对@AspectJ注解的支持

方法一:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

AppConfig

方法二:

<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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 声明基于注解的支持 -->
    <aop:aspectj-autoproxy />
    <!-- 扫描包文件 -->
    <context:component-scan base-package="..." />

</beans>

XML文档声明

方法二是常用的方式,第一种了解即可。

(3)声明切面

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AnyClazz {
    //...
}

增加@Aspect

在XML文档中配置Bean

<bean id="myAspect" class="org.xyz.AnyClazz">
    <!-- configure properties of aspect here as normal -->
</bean>

配置bean

@Aspect仅代表你希望将类声明为一个切面,如果希望使用Spring提供的包扫描自动初始化还需要增加@Component注解

package org.xyz;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AnyClazz {

}

支持包扫描的切面注解

(4)声明切点

切点包含三个方面的内容:切点的注解、连接点表达式、切点方法签名

/*
 * 最常用的声明方式
 * @Pointcut代表此方法是一个切点
 * 切点的名字是anyMethod
 * 代表连接点对象的表达式execution(public packagename.*.*(..))
 */
@Pointcut("execution(public packagename.*.*(..))")
public void anyMethod() {
    //...
}

最常用的声明方式

连接点表达式的前缀有三种,第一种最常见,后面两种知道含义足够

//execution意味着连接点的指定精确到类中的方法
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

//within代表了连接点的指定只精确到包,为包中的所有类和方法都添加代理
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}

//只为符合方法名的连接点添加代理
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

连接点表达式前缀

常用的连接点表达式语句

// 所有public方法
execution(public * *(..))

//所有以set开头的方法
execution(* set*(..))

//AccountService接口上的所有方法
execution(* com.xyz.service.AccountService.*(..))

//service包中的所有类上的所有方法(最常用)
execution(* com.xyz.service.*.*(..))

//service包及其子包里的所有类上的所有方法
within(com.xyz.service..*)

连接点表达式语句

(5)声明建言

初学者往往会在学习了建言之后混淆建言、切点和连接点三者的关系。这主要是因为Spring AOP建议将三者放在一条语句中声明。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    // 请注意()中的字符串并不是连接点表达语句而是指声明了@Aspect的方法,dataAccessOperation是切点的签名
    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

BeforeExample

以上提供的是单独的建言声明,对建言正确的理解应该是:建言对应切点,切点对应连接点。如果将三者合成一条注解声明,可以让代码显得更加紧凑也更加常用。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    // 三者的声明结合成一条注解
    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }

}

BeforeExample

另外几种常用建言举例:

i.正常返回的建言

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

AfterReturning

ii.处理异常的建言

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

AfterThrowing

iii.针对finally的建言

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}

After

vi.Around建言(重点)

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

Around

Around建言比较特殊,需要用ProceedingJoinPoint对象作为参数。熟悉JDK DynamicProxy的人应该对类似方法不陌生。

(6)为多层建言指定顺序

对某个连接点指定多层建言时就有必要为此提供一个执行顺序,让切面类实现Ordered接口就能指定这个顺序。

package aop.order;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AdvicerFirst implements Ordered {
    // 设定的值越小执行顺序越靠前
    private final int order = 1;

    public int getOrder() {
        return order;
    }

    @Before("execution(* aop.order.*.*(..))")
    public void display() {
        System.out.println("advicer 1st");
    }
}

AdvicerFirst

package aop.order;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AdvicerThen implements Ordered {
    //设定的值越大执行顺序越靠后
    private final int order = 10;

    public int getOrder() {
        return order;
    }

    @Around("execution(* aop.order.*.*(..))")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("advicer then start");
        Object retVal = pjp.proceed();
        System.out.println("advicer then end");
        return retVal;
    }
}

AdvicerThen

上面只是对Ordered接口的简单演示,实际开发中一般会增加setOrder(int order)方法,再在xml文档中通过IoC容器注入order值。

<aop:aspectj-autoproxy/>

<bean id="beanId" class="className">
    <property name="order" value="10"/>
</bean>

注入order值

(7)使用CGLIB生成代理

有关JDK DynamicProxy和CGLIB生成代理的差异不在本文的讲解范围。配置CGLIB的支持标签

<aop:aspectj-autoproxy proxy-target-class="true"/>

CGLIB支持标签

由于在Spring 3.2版本之后官方已经不再要求显式配置上述标签,针对JDK DynamicProxy无法实现代理的情况下Spring AOP框架会自动调用CGLIB。因此结论就是,你可以更放心的让Spring去自动生成代理了(感觉文档描述Spring AOP框架对CGLIB的支持就是广告)。

三、基于xml文档的AOP配置

上面详细介绍了如何通过注解来完成AOP代理,但通常情况下我们无法修改源代码或者我们能够获得的是已经经过编译的二进制文件。这种情形下如何使用Spring AOP框架来实现代理呢。

(1)为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:tx="http://www.springframework.org/schema/tx"
    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.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 我将开发环境中常用的命名前缀配置在一起,方便查找 -->

    <!-- bean definitions here -->

</beans>

常用命名前缀

(2)声明切面

<aop:config>
    <!-- 将普通Bean对象声明为一个切面 -->
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

aop:aspect

(3)声明切点

<aop:config>
    <!-- aop:pointcut也可以放在aop:aspect标签内部 -->
    <!-- id值代表切点签名,expression值代表连接点表达式 -->
    <aop:pointcut id="pointcutId" expression="execution(* packagename.*.*(..))"/>

</aop:config>

aop:pointcut

(4)声明建言

<aop:aspect id="beforeExample" ref="aBean">
    <!-- aop:before标签同样可以声明在aop:aspect内部 -->
    <!-- pointcut-ref属性指代切点ID,method属性指代切面类中的方法 -->
    <aop:before pointcut-ref="pointcutId" method="aspectInMethodName"/>

    ...
</aop:aspect>

aop:before

除了上面的常规配置手段以外,Spring AOP框架还提供了一种高度集成化的配置方案,但是只适合于方便修改源码的情况下使用。

(5)实现切面接口

i.Around接口

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

Interception around advice

ii.Before接口

public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {
        System.out.println("Before...");
    }

    public int getCount() {
        return count;
    }
}

Before advice

iii.AfterReturn接口

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        System.out.println("After...");
    }

    public int getCount() {
        return count;
    }
}

After Returning advice

(6)声明顾问(advisor)

对于实现了切面接口的类,只需要在xml文档中使用aop:advisor标签就可以了。

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

aop:advisor

注意:顾问标签在实际的开发中并不常用,在Spring集成Hibernate的时候会用来声明事务管理。原因正如前文所述,使用切面接口仅限于方便修改源码的情况,而在类似的情况下采用注解方式配置AOP框架才是更合理的选择。所有接口在org.springframework.aop包中,需要了解的可以自己查阅API文档。

四、总结

根据Spring官方文档最后对AOP框架的总结,说明了以下两个问题:

(1)选择Spring AOP还是完整的AspectJ?

基本上如果你打算使用Spring IoC框架那么你就应该采用Spring AOP框架。除非你仅仅是希望使用AOP,而切面对象又不是通过Spring容器产生,那么你只能使用AspectJ。

(2)使用注解还是xml文档配置?

通常情况下两者几乎等价,官方文档的建议是使用xml配置。大概是显得思路清晰,易于修改。采用xml配置的局限性在于生成的切面只能是单例对象,并且无法实现组合式切面配置。

@Pointcut(propertyAccess() && operationReturningAnAccount())
public void accountPropertyAccess() {}

组合式切面配置

Spring IoC和AOP框架构成了它的底层实现,也是Spring学习的基础。了解这些知识并不能说明你已经精通了Spring,而是说明你可以继续更深入的学习了。

时间: 2024-08-28 17:08:44

Hello Spring Framework——面向切面编程(AOP)的相关文章

spring中面向切面编程(AOP)的个人理解

面向切面编程AOP,是spring的一大特点 Aspect切面:封装共性功能的(增强功能的)类 Advice通过:切面类中封装的增强功能的方法. PointCut:切入点,是一个集合的概念,该集合的表达使用一个正则表达式表达 所有核心业务对象的所有方法的前后(事务处理AOP典型的应用) JoinPoint:连接点,程序中需要加入advice的地方,而且正在执行的ponitCut 织入(Weaving):将aspect和核心业务对象,进行整合的过程. 通过特定接口实现AOp Aop通知的类型: B

Spring之面向切面编程AOP(二)

简介 当积累的知识点到一定量的时候,学新知识就变得容易多了.希望再接下来的学习顺利进行下去.今天知识也是挺简单的,主要就是AOP面向切面编程.其中牵涉到了JDKProxy和CGLIB两个代理类,如何使用好,加以深刻理解.学起Spring切面编程也就简单多了 代理模式 1. 代理模式介绍 代理模式的英文叫做Proxy或Surrogate,中文都可译为"代理",所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对

Spring IOP 面向切面编程

Spring IOP  面向切面编程 AOP操作术语 Joinpoint(连接点):所谓连接点是指那些被拦截到的点.在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.(类里面可以增强的方法.这些方法称为连接点) Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义. (在类里面可以有很多的方法被增强,比如实际操作中,只是增强了类里面add方法和update方法,实现增强的方法称为切入点) Advice(通知/增强):所谓通知是指拦截到J

Spring(四):面向切面编程AOP

横切关注点:分布于应用中多处的功能 面向切面编程AOP:将横切关注点与业务逻辑相分离 在使用面向切面编程时,仍在一个地方定义通用功能,但是可以通过声明的方式定义这个功能以何种方式在何处应用,而无需修改受影响的类. 横切关注点可以被模块化为特殊的类,这些类被称为切面. 好处: 每个关注点集中于一处,而不是分散到多处代码中 服务模块更加简洁,因为它们只包含主要关注点的代码,次要关注点被转移到切面中了 1.定义AOP术语 1.1.通知(Advice) 切面的工作被称为通知. 通知定义了切面是什么以及何

Web项目中静态代理和动态代理为基础的面向切面编程AOP

本来每天更新的,我一般喜欢夜里过了十二点的时候发文章,结果难道是愚人节吗?学校的网也很有意思,断了,把我给耍了...好吧-开始今天的话题AOP.AOP太重要了,所以放到第二篇文章来谈这个话题,AOP是Spring中的重要概念.如果这个不理解Web开发中的三大框架的原理,那就呵呵了.时常听到同学和网友议论Web程序员大部分时间都是在考皮XML配置,我当时听到也是醉了,所以我要用心学习Web,其实这里面蕴含的设计模式.算法.架构思想在源码中体现的淋漓尽致啊,一个大宝库竟然视而不见可惜了.下面就一起品

【串线篇】面向切面编程AOP

面向切面编程AOP 描述:将某段代码“动态”的切入到“指定方法”的“指定位置”进行运行的一种编程方式 (其底层就是Java的动态代理)spring对其做了简化书写 场景: 1).AOP加日志保存到数据库 2).AOP做权限验证,filter能做的它都能 3).AOP做安全检查 4).AOP做事务控制 AOP专业术语: 原文地址:https://www.cnblogs.com/yanl55555/p/11744089.html

面向切面编程——Aop

一.概念 AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.它是一种新的方法论,它是对传统OOP编程的一种补充. 二.Aop原理 1.面向对象编程模型 OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分.面向对象编程是关注将需求功能划分为不同的并且相对独立,封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等

面向切面编程aop

面向切面编程 (AOP) Aspect Oriented Programming 可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现. 主要功能 日志记录,性能统计,安全控制,事务处理,异常处理等等 主要意图 将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的

使用Spring进行面向切面编程(AOP)

转载于http://www.blogjava.net/supercrsky/articles/174368.html 文章太长,写的很好,没看完,转过来慢慢理解,品味 简介 面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足. 除了类(classes)以外,AOP提供了 切面.切面对关注点进行模块化,例如横切多个类型和对象的事务管理. (这些关注点术语通常称作 横切(crosscutting) 关注点.) Spring的一个关键的组件就是 AOP