AOP提供另一种思想程序结构的方式对OOP进行补充;AOP框架是spring中的一个关键组件,但spring不依赖AOP,可以使用,AOP补充了IOC提供非常强大的中间件解决方案
关键词定义:
Spring AOP包含advice的类型:
在Spring 2.0中,所有通知参数都是静态类型的,因此您可以使用相应类型的Advice参数(例如,方法执行的返回值的类型)而不是Object数组。
功能及目标
Spring AOP目前仅支持方法执行连接点(建议在Spring bean上执行方法)。虽然可以在不破坏核心Spring AOP API的情况下添加对字段拦截的支持,但未实现字段拦截。如果您需要建议字段访问和更新连接点,请考虑使用AspectJ等语言。
Spring AOP的AOP方法与大多数其他AOP框架的方法不同。目的不是提供最完整的AOP实现(尽管Spring AOP非常强大)。相反,目标是在AOP实现和Spring IoC之间提供紧密集成,以帮助解决企业应用程序中的常见问题。Spring Framework的AOP功能通常与Spring IoC容器一起使用,通过使用普通bean定义语法来配置方面(尽管这允许强大的“自动代理”功能)。
Spring将Spring AOP和IoC与AspectJ无缝集成,以在一致的基于Spring的应用程序架构中实现AOP的所有使用
Spring AOP默认使用AOP代理的标准JDK动态代理。这使得任何接口(或接口集)都可以被代理。
Spring AOP也可以使用CGLIB代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。也可以强制使用这种
Spring使用AspectJ提供的库来解释与AspectJ 5相同的注释,用于切入点分析和匹配。但是,AOP运行时仍然是纯Spring AOP,并且不依赖于AspectJ编译器或weaver。
可以使用XML或Java样式配置启用@AspectJ支持。在任何一种情况下,您还需要确保AspectJ的aspectjweaver.jar库位于应用程序的类路径中(版本1.8或更高版本)
两种方式开启@AspectJ支持
//注解配置
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
//xml配置
声明切面
如果bean中存在@Aspect注解,Spring会自动检测并用于配置Spring AOP
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
切面(就是带有注释的类)与正常的类一样,还包含切点,advice,Introduction(inter-type)声明。
声明切点
//@Pointcut注释值的切入点表达式是常规的AspectJ 5切入点表达式。
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
Spring AOP仅限制与方法执行连接点的匹配
Spring AOP支持以下AspectJ切入点指示符(PCD)用于切入点表达式:
完整的AspectJ切入点语言支持未在Spring支持额外的切入点指示符:call,get,set,preinitialization, staticinitialization,initialization,handler,adviceexecution,withincode,cflow, cflowbelow,if,@this,和@withincode。在Spring AOP解释的切入点表达式中使用这些切入点指示符会导致IllegalArgumentException被抛出。
Spring AOP还支持另一个名为的PCD bean。此PCD允许您将连接点的匹配限制为特定的命名Spring bean或一组命名的Spring bean(使用通配符时)。该beanPCD具有下列形式:
bean(idOrNameOfBean)
idOrNameOfBean令牌可以是任何Spring bean的名字。提供了使用该*字符的有限通配符支持,因此,如果为Spring bean建立了一些命名约定,则可以编写beanPCD表达式来选择它们。与其他切入点指示符一样,beanPCD也可以与&&(和),||(或)和!(否定)运算符一起使用。
切点表达式
//public 方法
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
//指定包下的方法
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}
//同时符合这两个方法规则
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
示例:
//service包中定义的所有方法
execution(* com.xyz.service.*.*(..))
//service包及其子包中定义的所有方法
execution(* com.xyz.service..*.*(..))
//servie包中的切点(仅在spring Aop中)
within(com.xyz.service.*)
//servie包及其子包(仅在spring Aop中)
within(com.xyz.service..*)
//实现这个接口的代理
this(com.xyz.service.AccountService)
//实现这个接口的目标对象的切点
target(com.xyz.service.AccountService)
//单个参数,并且参数类型是Serializable
args(java.io.Serializable)
//目标对象带这个注解,应该是指类上
@target(org.springframework.transaction.annotation.Transactional)
//目标对象的声明类型据用注解
@within(org.springframework.transaction.annotation.Transactional)
//方法上有这个注解
@annotation(org.springframework.transaction.annotation.Transactional)
//有一个参数,并且参数类有这个注解
@args(com.xyz.security.Classified)
//按bean名称
bean(tradeService)
bean(*Service)
切入点编写建议:
动态匹配切入点是一个代价高昂的过程,所以在写切入点是需要尽可能缩小匹配的范围,故使用切点匹配时,至少应该包含kinded和scoping的过滤条件
匹配的范围:
kinded:execution,get,set,call,和handler。
scoping:within和withincode
contextual:this,target和@annotation
Before Advice
//在advice之前声明
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() {
// ...
}
}
//返回advice后,匹配的方法执行正常返回
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() {
// ...
}
}
//-------------------------------------------------
//After Returning Advice
//绑定返回值类型;returning属性中使用的名称必须与advice方法中的参数名称相对应;当方法执行返回时,返回值作为相应的参数值传递给advice方法
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
//-------------------------------------------------
//After Throwing Advice
//抛出异常时执行
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() {
// ...
}
}
//指定异常类型
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
//-------------------------------------------------
//After (Finally) Advice
//一定会执行的方法,类似于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() {
// ...
}
}
//-------------------------------------------------
//Around Advice
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
//方法参数的第一个参数必须是ProceedingJoinPoint
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
advice 参数
org.aspectj.lang.JoinPoint
//参数传递:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
//它将匹配仅限于那些方法至少采用一个参数的方法执行,而传递给该参数的参数是一个实例Account。其次,它Account通过account 参数使实际对象可用于advice
//第二种方法
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
代理对象(this),目标对象(target),和说明(@within, @target,@annotation,和@args)都可以以类似的方式结合
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
确定参数名:
//1.如果参数已知
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
//2.如果第一个参数是的JoinPoint,ProceedingJoinPoint或 JoinPoint.StaticPart类型;argNames可以不包含它
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
//3.如果只有JoinPoint,argNames可省
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
Advice Ordering
多个advice对应一个连接点时,默认是不存在顺序的,可使用org.springframework.core.Ordered指定优先级
给定一个名为interface的接口UsageTracked和该接口的实现DefaultUsageTracked,以下方面声明服务接口的所有实现者也实现了UsageTracked接口???
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
您可以通过perthis在@Aspect 注释中指定子句来声明方面
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {
private int someState;
@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}
}
//该'perthis'子句的作用是为执行业务服务的每个唯一服务对象创建一个方面实例
示例:对于高并发情况,服务调用可能会报错,如果重试可能会成功;针对这种需要重试的服务,可以视同around实现
@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.SystemArchitecture.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接口,所以可以设置优先级
//优化只有Idempotent 的才走
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
...
}
使用此功能需要spring-aop支持
基于schema的aop支持
声明一个aspect
...
...
声明切点
//第二种写法,引用了@Aspects定义的命名切点
//可以在aspect里面定义,同上;也可能定义切入方法类型
...
//before advice
...
...
//after returnning advice
...
...
//after throwing advice
...
...
//After (Finally) Advice
...
//Around Advice
...
参数:多个参数可以使用逗号分隔
实例化模型目前只支持单例
Spring AOP比使用完整的AspectJ更简单,因为不需要在开发和构建过程中引入AspectJ编译器/ weaver。如果您只需要建议在Spring bean上执行操作,那么Spring AOP是正确的选择。如果需要建议不受Spring容器管理的对象(例如域对象),则需要使用AspectJ。如果您希望建议除简单方法执行之外的连接点(例如,字段获取或设置连接点等),则还需要使用AspectJ。
XML风格有两个缺点:1、它不能在单个地方完全封装它所处理的需求的实现。DRY原则认为系统内任何知识片段都应该有一个单一的、明确的、权威性的表示。当使用XML样式时,关于如何实现需求的知识在配置文件中的支持bean类和XML的声明之间被分割。当您使用@AspectJ样式时,这个信息被封装在一个单独的模块中:.。2、XML样式在表达内容上比AspectJ样式稍微受限制:只支持“singleton”方面实例化模型,并且不能组合在XML中声明的命名切入点。
@Pointcut("execution(* get*())")
public void propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}
//aspectJ可以提供组合模式,xml不行
@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
只要您的方面不仅仅是简单的企业服务配置,Spring团队更喜欢@AspectJ风格。
通过使用自动代理支持,模式定义aop:aspect方面,aop:advisor声明的顾问程序,甚至在相同配置中使用Spring 1.2样式定义的代理和拦截器,完全可以混合@AspectJ样式方面
如果要代理的目标对象实现至少一个接口,则使用JDK动态代理。目标类型实现的所有接口都是代理的。如果目标对象未实现任何接口,则会创建CGLIB代理。
如果要强制使用CGLIB代理(例如,代理为目标对象定义的每个方法,而不仅仅是那些由其接口实现的方法),您可以这样做。但是,您应该考虑以下问题:
//强制使用cglib
或
spring aop是基于代理实现的,自我方法调用时,比如用this调用另外一个方法,这个时候并不是使用代理取调的;AspectJ不是基于代理实现的aop框架
这种做法的意义:
It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
// 使用this调用,是使用当前类的实例调用;使用这种方式是使用代理去调用的bar方法
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.adddInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
除了使用aop:config与aop:aspectj-autoproxy还可以以编程的方式创建通知目标对象的代理
org.springframework.aop.aspectj.annotation.AspectJProxyFactory接口可以为一个或多个@AspectJ创建代理
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
需要spring-aspects.jar包; 使用AspectJ编译器或weaver代替Spring AOP,如果您的需求超出了Spring AOP提供的功能
spring-aspects.jar包含一个注释驱动的方面,利用此功能允许依赖注入任何对象。该支持旨在用于在任何容器控制之外创建的对象。域对象通常属于此类别,因为它们通常由new运算符以编程方式创建,或者由于数据库查询而由ORM工具创建 。
后续章节为介绍如何使用aspects