Spring核心技术--AOP
2017-04-11 14:38
429 查看
Spring核心技术–AOP
在OOP中, 模块化的单位是class, 在AOP中, 模块化的单位是aspect.Spring IoC容器并不直接与AOP模块耦合, AOP模块是作为一个中间件方案提供给IoC容器使用的.
声明式事务管理就是AOP在Spring框架中的一个典型实现, 另外, 池化也是一个典型的应用.
对于Spring AOP编程来说, 推荐使用能够完成需求功能的最小Advice种类进行实现. 例如, 如果你想简单实现用返回值更新缓存, 那么应该使用
@After ReturningAdvice而不是
@AroundAdvice. 虽然
Aroundadvice更加强大, 也能完成相同的功能, 但是也更容易出错.
在Spring中, Pointcut + Advice = Spring AOP基本组件
Spring AOP的能力和局限性
只能用于方法级别的连接点, 不支持属性级别的拦截.如果想实现属性级别的拦截, 考虑使用AspectJ.
Spring AOP与AspectJ并不是相互替代的关系, Spring AOP也不是用来取代AspectJ或者是其他AOP框架, Spring AOP只是为了更好的为Spring IoC容器提供一些通用问题的解决方案. 你可以同时使用AspectJ作为Spring AOP的补充.
注意: Spring AOP的使用借鉴了AspectJ的一些语法(例如使用了一些AspectJ同名的注解, 和切点解释, 匹配的方法, spring-aop模块默认已经添加了依赖, 无需自己手动整合), 实际实现与AspectJ本身大相径庭.
AOP Proxy类型
Spring AOP的实现方式是proxy-based AOP. 默认使用JDK动态代理作为AOP Proxy的实现方式. JDK动态代理能够代理被代理对象的所有接口.Spring AOP也能使用CGLIB代理, 当一个business object中想被代理的某非final方法不是接口中的方法的时候, Spring AOP就会使用GCLIB的方式进行代理.
注意: 基于proxy的aop都只能够拦截被代理对象的外部调用, 也就是下例的方式不会被advice拦截:
public class SimplePojo implements Pojo { public void foo() { // this next method invocation is a direct call on the 'this' reference // 当proxy对象调用实际的被代理对象的foo方法时, this指针已经指向被代理对象, 所以bar方法不会被advice拦截 this.bar(); } public void bar() { // some logic... } }
这种情况下只能使用AspectJ的方式通过weaving的方式实现AOP.
使用AspectJ风格进行Pointcut定义
在切点定义中, 可用的切点类型主要有以下几种.切点类型 | 简介 |
---|---|
execution | (最常用)匹配任意符合的方法 |
within | 匹配指定类型中的方法 |
this | 仅匹配动态代理类中的方法 |
target | 仅匹配被代理类中的方法 |
args | 仅匹配参数的运行时类型符合指定类型的方法 |
@target | 仅匹配含有指定注解的被代理类的方法 |
@args | 仅匹配参数运行时类型含有指定注解的方法 |
@within | 匹配含有指定注解的方法 |
@annotaion | 匹配标有指定注解的方法 |
组合切点
使用’&&’, ‘||’和’!’进行切点定义的组合, 实现复杂的切点定义.下面是一个例子
// 匹配任意公有方法的切点 @Pointcut("execution(public * *(..))") private void anyPublicOperation() {} // 匹配任意trading包中方法的切点 @Pointcut("within(com.xyz.someapp.trading..*)") private void inTrading() {} // 组合切点: 匹配trading包中任意共有方法的切点 @Pointcut("anyPublicOperation() && inTrading()") private void tradingOperation() {}
按层定义可复用的切点定义
下面是一个符合这个规范的例子:package com.xyz.someapp; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class SystemArchitecture { /** * A join point is in the web layer if the method is defined * in a type in the com.xyz.someapp.web package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() {} /** * A join point is in the service layer if the method is defined * in a type in the com.xyz.someapp.service package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.service..*)") public void inServiceLayer() {} /** * A join point is in the data access layer if the method is defined * in a type in the com.xyz.someapp.dao package or any sub-package * under that. */ @Pointcut("within(com.xyz.someapp.dao..*)") public void inDataAccessLayer() {} /** * A business service is the execution of any method defined on a service * interface. This definition assumes that interfaces are placed in the * "service" package, and that implementation types are in sub-packages. * * If you group service interfaces by functional area (for example, * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then 4000 * the pointcut expression "execution(* com.xyz.someapp.service.*.*(..))" * could be used instead. * * Alternatively, you can write the expression using the 'bean' * PCD, like so "bean(*Service)". (This assumes that you have * named your Spring service beans in a consistent fashion.) */ @Pointcut("execution(* com.xyz.someapp..service.*.*(..))") public void businessService() {} /** * A data access operation is the execution of any method defined on a * dao interface. This definition assumes that interfaces are placed in the * "dao" package, and that implementation types are in sub-packages. */ @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() {} }
一些切点定义的例子
这里我们以execution类型的切点定义为例, 通用的模式是:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
其中, 必选字段是
ret-type-pattern
name-pattern
param-pattern
表达式支持’*’, ‘..’等通配符, 下面是一些符合上述定义的例子:
// 所有public方法 execution(public * *(..)) // 所有以set开头的方法 execution(* set*(..)) // AccoutService接口中定义的所有方法 execution(* com.xyz.service.AccountService.*(..)) // service包中定义的所有方法 execution(* com.xyz.service.*.*(..)) // service及其子包中定义的所有方法 execution(* com.xyz.service..*.*(..)) // service包中定义的所有方法 within(com.xyz.service.*) // service及其子包中定义的所有方法 within(com.xyz.service..*) // 所有实现了AccountService接口的代理类instance的所有方法 this(com.xyz.service.AccountService) // 所有实现了AccountService接口的被代理类instance的所有方法 target(com.xyz.service.AccountService) // 所有接受单个java.io.Serializable类型参数的方法(这个接口只要是参数的接口之一即可) // 注意: 如果是execution(* *(java.io.Serializable)), 要求参数类型声明必须严格是java.io.Serializable. args只要是Serializable的就可以了, 匹配面更广 args(java.io.Serializable)
如何定义好的切点
我们定义的任何切点都会在运行时被AspectJ重写和优化, 但即使如此, 我们也应该通过更明确的显式定义, 加速AspectJ切点匹配的时间和空间消耗.前面介绍过的切点基本上可以分为三类:
按类型定义的切点: 如
execution类型.
按范围定义的切点: 如
within类型.
上下文相关的切点: 如
this,
target.
一个好的切点定义应该尽量包括1, 2两种类型的定义, 再退一步, 至少要包含第2种定义, 因为
within类型能够快速抛弃掉不符合的大量切点, 极大加速运行时解析速度.
使用AspectJ风格进行Advice定义
一个Advice与一个Pointcut绑定, 构成AOP的基本组件.以下是一些不同类型的Advice定义:
@AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") // returning能够对被代理类的对象的方法执行返回结果进行绑定 public void doAccessCheck(Object retVal) { // ... } @AfterThrowing( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex") // 可以根据方法参数指定拦截的异常类型 public void doRecoveryActions(DataAccessException ex) { // ... } // 使用@Around advice, 一般是有@Before和@After共享状态的需要时启用 // 下面是一个使用@Around实现的方法执行耗时计算. // Spring的缓存机制也是使用这种Advice实现的. @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch // 执行方法(甚至可以不执行, 决定权完全掌握在应用编写者手中!) Object retVal = pjp.proceed(); // stop stopwatch return retVal; }
在Advice中获取连接点(方法)状态
在Spring AOP中, 仅支持方法作为连接点, 所以Advice获取的总是方法的状态(包括参数, 方法签名, 类名等等).Spring中要想获取连接点的状态, 必须将连接点作为Advice方法的第一个参数传入, 主要用两个类型描述:
org.aspectj.lang.JoinPoint
ProceedingJoinPoint:
@AroundAdvice使用
上述类型有很多实用的方法, 如:
getArgs()
getThis()
getTarget()
getSignature()
toString
获取和使用连接点(方法)参数
下面是一个例子:@Around("execution(List<Account> find*(..)) && " + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)") public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) throws Throwable { String newPattern = preProcess(accountHolderNamePattern); return pjp.proceed(new Object[] {newPattern}); }
Advice执行顺序
当多个Advice作用于同一个连接点的时候, 我们可以通过Order来显式指定Advice优先级.
举个例子:
@Before: 优先级高的, 先运行;
@After: 优先级高的, 后运行;
使用AspectJ风格进行introductions定义
introductions允许一个aspect为连接点方法对应的this对象(proxy对象)引入一个新的接口, 并给出默认的实现, 作为proxy对象的增强实现.简单的说, 就是能够动态为this对象(proxy对象)扩展某接口的功能.
下面是一个用例:
@Aspect public class UsageTracking { // @DeclareParents为value对应的所有类型的代理对象引入了新的接口实现, 也就是说对于指定类型, 我们可以使用UsageTracked usageTracked = (UsageTracked) context.getBean("myService"); 去获取它们了. 注意: 它们的类型声明中本没有实现UsageTracked接口 @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(); } }
案例解析
案例1: 使用AOP实现失败自动重试
需求说明: 对于一些会由于并发问题(例如死锁)导致的服务执行失败, 如果操作是幂等性的, 我们希望程序能够透明的重试, 而不是抛给用户一个PessimisticLockingFailureException.
// 定义一个幂等操作标识, 用于辅助切面识别 @Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // marker annotation } @Aspect // 实现Ordered接口能够让我们的类中定义一个order属性, 执行的时候, 会按照order属性的顺序进行执行(值越大,优先级越低, 相同值的执行顺序随机. 与servlet执行顺序策略相似) 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() && " + "@annotation(com.xyz.myapp.service.Idempotent)") 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; } } @Configuratin public class AspectConfig { // 手动进行Aspect的初始化 @Bean public ConcurrentOperationExecutor config() { return new ConcurrentOperationExecutor(3, 100); } }
小结
以上.参考链接:
Spring官方文档–AOP编程
相关文章推荐
- AOP: Spring3核心技术之AOP配置
- Spring3核心技术之AOP动态代理
- Spring核心技术原理-(2)-通过Web开发演进过程了解一下为什么要有Spring AOP?
- Spring核心技术阐述(IOC、DI、AOP)
- Spring核心技术之Ioc和AOP
- Spring 框架的核心功能之AOP技术
- Spring 4 官方文档学习(七)核心技术之Spring AOP APIs
- Spring核心技术之Ioc和AOP
- Spring3核心技术之AOP配置
- Spring3核心技术之AOP配置
- Spring 技术核心 IOC AOP <二> AOP详解
- [置顶] Spring3核心技术之AOP配置
- Spring核心技术原理-(2)-通过Web开发演进过程了解一下为什么要有Spring AOP?
- Spring核心技术原理-(2)-通过Web开发演进过程了解一下为什么要有Spring AOP?
- Spring3核心技术之AOP配置
- Spring3核心技术之AOP配置
- Spring核心技术Ioc和AOP
- Spring核心技术阐述(IOC、DI、AOP)
- Spring核心技术原理-(2)-通过Web开发演进过程了解一下为什么要有Spring AOP?
- Spring.NET AOP技术学习