Spring核心技术--AOP

来源:互联网 发布:java 字节码 汇编 编辑:程序博客网 时间:2024/05/16 15:45

Spring核心技术–AOP

在OOP中, 模块化的单位是class, 在AOP中, 模块化的单位是aspect.

Spring IoC容器并不直接与AOP模块耦合, AOP模块是作为一个中间件方案提供给IoC容器使用的.

声明式事务管理就是AOP在Spring框架中的一个典型实现, 另外, 池化也是一个典型的应用.

对于Spring AOP编程来说, 推荐使用能够完成需求功能的最小Advice种类进行实现. 例如, 如果你想简单实现用返回值更新缓存, 那么应该使用@After Returning Advice而不是@Around Advice. 虽然Around advice更加强大, 也能完成相同的功能, 但是也更容易出错.

在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 匹配标有指定注解的方法

注意: 在Spring AOP中, this指的是代理类的instance, target才是被代理类的instance.

组合切点

使用’&&’, ‘||’和’!’进行切点定义的组合, 实现复杂的切点定义.

下面是一个例子

// 匹配任意公有方法的切点@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;@Aspectpublic 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     * 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切点匹配的时间和空间消耗.

前面介绍过的切点基本上可以分为三类:

  1. 按类型定义的切点: 如execution类型.
  2. 按范围定义的切点: 如within类型.
  3. 上下文相关的切点: 如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: @Around Advice使用

上述类型有很多实用的方法, 如:

  • 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对象)扩展某接口的功能.

下面是一个用例:

@Aspectpublic 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;    }}@Configuratinpublic class AspectConfig {    // 手动进行Aspect的初始化    @Bean    public ConcurrentOperationExecutor config() {        return new ConcurrentOperationExecutor(3, 100);    }}

小结

以上.

参考链接:

  • Spring官方文档–AOP编程

0 0