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创建动态代理。