Spring AOP 的底层实现(spring官网描述)

1.AOP全称(Aspect Oriented Programming)

面向切面编程的简称,而Spring AOP 只是aop其中一种实现的方式,这里我们着重看一下spring aop.

1.1 AOP 使用场景

1、AOP框架种类
AspectJ
JBoss AOP
Spring AOP
2、使用 AOP 场景
性能监控:在方法调用前后记录调用事件,方法执行太长或超时报警。
缓存代理:缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
软件破解:使用AOP修改软件的验证类的判断逻辑。
记录日志:在方法执行前后记录系统日志。
工作流系统:工作流系统需要将业务代码和流畅引擎代码混合在一起执行,那么可以使用 AOP 将其分离,并动态挂载业务。
权限验证:方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕获。

2.官网介绍: 

在spring官网-Spring Framework-Core中, Aspect Oriented Programming with Spring这一节对spring aop做了详细描述如下: 

2.1 spring中的面向切面(aspect)编程

面向切面编程(AOP)通过提供另一种思考程序结构的方法,补充了面向对象编程(OOP)。面向对象的模块化的关键单位是类,而面向方面的模块化单位是方面。方面支持跨多个类型和对象的关注点(如事务管理)的模块化。(这种关注点在AOP文献中通常称为“横切”关注点。)

Spring的关键组件之一是AOP框架。虽然Spring IoC容器不依赖于AOP(这意味着如果您不想使用AOP,则不需要使用),但AOP对Spring IoC进行了补充,从而提供了一个功能非常强大的中间件解决方案。

AOP在Spring框架中被用于:

1.提供声明式企业服务。最重要的服务是声明式事务管理。

2.让用户实现自定义方面,用AOP来补充他们对OOP的使用。

2.2 AOP概念

切面(Aspect): 跨越多个类的关注点的模块化。事务管理是企业Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,方面是通过使用常规类(基于模式的方法)或使用@Aspect注释(@AspectJ风格)注释的常规类实现的。

连接点(Join point): 程序执行期间的一个点,如方法的执行或异常的处理。在Spring AOP中,连接点总是表示一个方法执行。

通知(Advice):方面在特定连接点上采取的动作。不同类型的建议包括“大约”、“之前”和“之后”的建议。(通知类型将在后面讨论。)许多AOP框架(包括Spring)将通知建模为拦截器,并维护连接点周围的拦截器链

切点(Pointcut):匹配连接点的谓词。通知与切入点表达式相关联,并在与切入点匹配的任何连接点上运行(例如,具有特定名称的方法的执行)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言。

引入(Introduction): 代表类型声明额外的方法或字段。Spring AOP允许您向任何被建议的对象引入新的接口(和相应的实现)。例如,您可以使用介绍使bean实现IsModified接口,以简化缓存。(在AspectJ社区中,引入称为类型间声明。)

目标对象(Target object):由一个或多个方面通知的对象。也称为“建议对象”。因为Spring AOP是通过使用运行时代理实现的,所以这个对象总是一个代理对象。

AOP代理(AOP proxy):AOP框架为实现方面契约(通知方法执行等)而创建的对象。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理

织入(Weaving) :将方面与其他应用程序类型或对象链接,以创建被建议的对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。与其他纯Java AOP框架一样,Spring AOP在运行时执行编织。

2.2.1 Spring AOP包括以下类型的通知:

前置通知(Before advice) :在连接点之前运行的通知,但它不能够阻止执行流进行到连接点(除非它引发异常)。

后置通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。

抛出异常通知(After throwing advice): 如果方法通过抛出异常退出,则运行的建议

最终通知(After (finally) advice): 不管连接点退出的方式是什么(正常或异常返回),都要运行的通知

环绕通知(Around advice): 围绕连接点(如方法调用)的通知。这是最有力的建议。Around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到连接点,还是通过返回自己的返回值或抛出异常来缩短建议的方法执行。

2.3 Spring AOP 的功能和目标

Spring AOP是用纯Java实现的。不需要特殊的编译过程。Spring AOP不需要控制类装入器层次结构,因此适合在servlet容器或应用程序服务器中使用。

Spring AOP目前只支持方法执行连接点(建议在Spring bean上执行方法)。没有实现字段拦截,尽管可以在不破坏核心Spring AOP api的情况下添加对字段拦截的支持。如果您需要通知字段访问和更新连接点,可以考虑使用AspectJ这样的语言。

Spring AOP的AOP方法不同于大多数其他AOP框架。其目的不是提供最完整的AOP实现(尽管Spring AOP相当有能力)。相反,其目的是提供AOP实现和Spring IoC之间的紧密集成,以帮助解决企业应用程序中的常见问题。

Spring框架的AOP功能通常与Spring IoC容器一起使用。方面是通过使用普通的bean定义语法来配置的(尽管这允许强大的“自动代理”功能)。这是与其他AOP实现的一个关键区别。使用Spring AOP,您无法轻松或有效地做一些事情,例如通知非常细粒度的对象(通常是域对象)。在这种情况下,AspectJ是最佳选择。然而,我们的经验是Spring AOP为企业Java应用中的大多数问题提供了一个优秀的解决方案

2.4  AOP代理(AOP Proxies)

Spring AOP默认为AOP代理使用标准JDK动态代理。这允许代理任何接口(或接口集)

Spring AOP也可以使用CGLIB代理。这对于代理类而不是代理接口是必要的。默认情况下,如果业务对象没有实现接口,则使用CGLIB。由于针对接口而不是类编程是一种好的实践,业务类通常实现一个或多个业务接口。强制使用CGLIB是可能的,在这些情况下(希望很少发生),您需要通知一个没有在接口上声明的方法,或者需要将代理对象作为具体类型传递给方法

2.5 @AspectJ支持(@AspectJ support)

@AspectJ是一种将方面声明为使用注释注释的常规Java类的风格。@AspectJ样式是由AspectJ项目作为AspectJ 5版本的一部分引入的。Spring解释与AspectJ 5相同的注释,使用AspectJ提供的用于切入点解析和匹配的库。AOP运行时仍然是纯Spring AOP,并且不依赖于AspectJ编译器或编织器。

2.5.1 使用Java配置启用@AspectJ支持

使用Java @Configuration启用@AspectJ支持:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

2.5.2 声明一个切面(Declaring an Aspect)

启用了@AspectJ支持后,在您的应用程序上下文中定义的任何带有@AspectJ方面(具有@Aspect注释)类的bean都将被Spring自动检测并用于配置Spring AOP

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

@Aspect
public class NotVeryUsefulAspect {

}

2.5.3 声明一个切入点(Declaring a Pointcut)

下面的示例定义了一个名为anyOldTransfer的切入点,它与任何名为transfer的方法的执行相匹配:

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

2.5.4 声明通知(Declaring Advice)

 前置通知(Before Advice): 你可以使用@Before注释在方面的advice之前声明:

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

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}

后置通知(After Returning Advice): 

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

@Aspect
public class AfterReturningExample {

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

抛出异常后通知(After Throwing Advice):

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

@Aspect
public class AfterThrowingExample {

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

最终通知(After (Finally) Advice )

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

@Aspect
public class AfterFinallyExample {

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

2.6 通知参数(Advice Parameters)

访问当前连接点(Access to the Current JoinPoint)

JoinPoint接口提供了许多有用的方法:

  • getArgs(): Returns the method arguments.返回方法参数

  • getThis(): Returns the proxy object.返回代理对象

  • getTarget(): Returns the target object.返回目标对象

  • getSignature(): Returns a description of the method that is being advised.返回被建议的方法的描述

  • toString(): Prints a useful description of the method being advised. 打印被建议的方法的有用描述

2.7 多个切点的执行顺序

@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}

上面的例子中,定义了order=3,重新创建一个切面,定义order=6,执行的结果是:

@Around:进入方法---环绕通知--order=3
@Before:开始添加--order=3
@Around:进入方法---环绕通知--order=6
@Before:开始添加--order=6
============执行业务方法findUser,查找的用户是:张三=============
@Around:退出方法---环绕通知--order=6
@After:最终通知--order=6
@AfterReturning:后置通知--order=6
---张三---
@Around:退出方法---环绕通知--order=3
@After:最终通知--order=3
@AfterReturning:后置通知--order=3
---张三---
 
 
@Around:进入方法---环绕通知--order=3
@Before:开始添加--order=3
@Around:进入方法---环绕通知--order=6
@Before:开始添加--order=6
============执行业务方法addUser=============
@After:最终通知--order=6
@AfterThrowing:例外通知--order=6
null
@After:最终通知--order=3
@AfterThrowing:例外通知--order=3
null

 

3.Spring AOP 代理机制( Proxying Mechanisms)

Spring AOP使用JDK动态代理或CGLIB为给定的目标对象创建代理。JDK动态代理内置在JDK中,而CGLIB是一个通用的开源类定义库(重新打包到spring-core中)。

如果要代理的目标对象实现了至少一个接口,则使用JDK动态代理。目标类型实现的所有接口都是代理的。如果目标对象没有实现任何接口,则创建CGLIB代理

1、JDK 动态代理
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
2、CGLib动态代理
CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值