xiaobaoqiu Blog

Think More, Code Less

Spring AOP无效

最近在重构项目,有一个场景:根据方法的返回值判断是否成功,成功则从入参里获取需要的参数,构造消息(Msg)并发送通知其他模块.

因为是比较通用的逻辑(判断返回值,获取参数,发送消息),因此做成Spring注解的形式,注解中会用AOP拦截方法获取方法的返回值和参数.

其中获取参数使用的是Spring SpEL表达式.

1.使用背景

碰到的一个问题是AOP拦截无法获取private,protected和inner方法的参数.使用场景如下:

1.1 注解定义

首先定义注解用于获取

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Param {
    /**
     * 参数定义,标识了参数的获取方式,或常量值
     */
    String[] params();

    /**
     * 操作类型,标识了当前这注解该被哪个处理类来处理
     */
    String[] operate();
}

1.2 定义切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
public class testAspect {
    /**
     * 根据注解获取切面
     */
    @Pointcut("@annotation(com.aaa.bbb.ccc.ParamGrabber)")
    public void aspectPointCut() {
    }

    /**
    * AOP, 定义方法返回之后的操作
    */
    @AfterReturning(value = "aspectPointCut()", returning = "returnValue")
    public void afterReturning(JoinPoint point, Object returnValue){
        //1.获取拦截的类和方法

        //2.获取方法上我们定义的特定注解

        //3.根据注解的参数中的spel表达式,从方法入参中获取我们需要的参数

        //4.一些列的Processor处理逻辑
    }
}

1.3 处理类

定义获取的参数之后的处理类的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Processor {
    /**
     * 本processor可以处理的操作类型
     */
    List<String> supportOperates();

    /**
     *
     * 此processor的处理逻辑
     */
    void process(XxxContext context);

    /**
     * 返回值验证,是否需要执行process操作
     */
    boolean isReturnValid(XxxContext context, Object returnValue);
}

之后利用Spring就可以获取上下问中所有的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ProcessorSupport implements ApplicationContextAware, InitializingBean {
    // Spring上下文
    private ApplicationContext applicationContext;

    private final Multimap<String, Processor> processorMap = HashMultimap.create();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // 从Spring上下文获取Processor的所有具体实现类
        Map<String, Processor> beansOfType = applicationContext.getBeansOfType(Processor.class);
        for(Processor processor : beansOfType.values()) {
            for(String op : processor.supportOperates()) {
                processorMap.put(op, processor);
            }
        }
    }

    //根据注解中定义的operate来获取具体的处理类Processor
    @Override
    public Collection<Processor> getProcessor(String operate) {
        return processorMap.get(operate);
    }
}

1.4 使用

使用的时候,首先得实现一个具体的Processor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MyProcessor implements Processor {

    public static final String SPECIFIC_PROCESSOR_KEY = "SPECIFIC_PROCESSOR_KEY";

    @Override
    public List<String> supportOperates() {
        return ImmutableList.of(
                SPECIFIC_PROCESSOR_KEY
        );
    }

    @Override
    public void process(XxxContext context) {
        //获取注解解析得到的参数
        Map<String, Object> parsedParam = context.getParsedParam();

        //构建发送的消息内容
        Msg msg = buildNoticeMsg(parsedParam);

        //发送消息
        noticeService.doNotice(msg);
    }

    @Override
    public boolean isReturnValid(XxxContext context, Object returnValue) {
        //判断返回值是否有效...
    }
}

具体使用的地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据SPECIFIC_PROCESSOR_KEY找到MyProcessor
* params中使用SpEL获取参数中的某个属性
*/
@Param(operate = MyProcessor.SPECIFIC_PROCESSOR_KEY, params = {
        CrateConstants.KEYS_KEY + "={#p0.![propertyName]}"})
public int batchInsert(List<Tag> tagList) {
    if (CollectionUtils.isEmpty(tagList)) {
        return 0;
    }

    return tagDao.insertBatchly(tagList);
}

2.问题

2.1 private和protected方法无效

private和protected方法(通常为类的inner方法)无法使用我们定义的注解来达到目的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//供外部调用的方法
public class XxxService {
    public int outerMethod(List<Tag> tagList) {
        return innerMethod(tagList);
    }
}

//被当前类的其他方法调用的内部方法
@Param(operate = MyProcessor.SPECIFIC_PROCESSOR_KEY, params = {
        CrateConstants.KEYS_KEY + "={#p0.![propertyName]}"})
private int innerMethod(List<Tag> tagList) {
    if (CollectionUtils.isEmpty(tagList)) {
        return 0;
    }

    return tagDao.insertBatchly(tagList);
}

2.2 inner public方法无效

定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//供外部调用的方法
public class XxxService {
    public int outerMethod(List<Tag> tagList) {
        return innerMethod(tagList);
    }
}

//被当前类的其他方法调用的内部方法
@Param(operate = MyProcessor.SPECIFIC_PROCESSOR_KEY, params = {
        CrateConstants.KEYS_KEY + "={#p0.![propertyName]}"})
public int innerMethod(List<Tag> tagList) {
    if (CollectionUtils.isEmpty(tagList)) {
        return 0;
    }

    return tagDao.insertBatchly(tagList);
}

调用的时候调用

1
xxxServiceIns.outerMethod(...);

3.原因

3.1 private和protected方法无效

Spring的AOP框架基于代理实现的(proxy-based).使用原生的Java的代理是无法代理protected和private类型的方法(详见后续结束).

而CGLIB的代理虽然在技术上可以代理protected和private类型的方法,但是用于AOP的时候不推荐代理protected和private类型的方法.

结论就是,任何定义的pointcut只在public方法上有效.

参考:

http://stackoverflow.com/questions/15093894/aspectj-pointcut-for-annotated-private-methods

spring官方文档关于AOP的解释中也有相关的说明:

http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/aop.html#aop-introduction-spring-defn

3.2 inner public方法无效

原因其实就是一句话:Spring AOP是基于代理机制的.

考虑如下场景,当你拿到一个无代理的、无任何特殊之处的对象引用时:

1
2
3
4
5
6
7
8
9
10
public class SimplePojo implements SimpleInterface {
    public void foo() {
       // this next method invocation is a direct call on the 'this' reference
       this.bar();
    }
                
    public void bar() {
        // some logic...
    }
}

当你调用一个对象引用的方法时,此对象引用上的方法直接被调用,如下所示:

1
2
3
4
5
public static void main(String[] args) {
   SimplePojo ins = new SimplePojo();
   // this is a direct method call on the 'ins' reference
   ins.foo();
}

当客户代码所持有的引用是一个代理的时候则略有不同了,当调用foo()方法时候,首先会调用原始foo()方法上的@Before的代码逻辑,然后调用原始的foo()方法,原始foo()方法内的bar()调用的是原始对象的this.bar(),即一旦调用最终抵达了目标对象 (此处为SimplePojo类的引用),任何对自身的调用例如this.bar()将对this引用进行调用而非代理。

这一点意义重大,它意味着自我调用将不会导致和方法调用关联的AOP通知得到执行的机会。

1
2
SimplePojo proxy = proxyFactory.getProxy(Pojo.class);
proxy.foo();

参考: http://m.oschina.net/blog/161387

4.Spring AOP代理

Spring AOP使用JDK动态代理或者CGLIB来为目标对象创建代理。

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

如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法) 那也可以。但是需要考虑以下问题:

1.无法通知(advise)final方法,因为他们不能被覆写。
2.代理对象的构造器会被调用两次。因为在CGLIB代理模式下每一个代理对象都会 产生一个子类。每一个代理实例会生成两个对象:实际代理对象和它的一个实现了通知的子类实例 而是用JDK代理时不会出现这样的行为。通常情况下,调用代理类型的构造器两次并不是问题, 因为除了会发生指派外没有任何真正的逻辑被实现。
3.CGLib的效率没有使用JDK代理机制高,速度平均要慢8倍左右。

强制使用CGLIB代理需要将<aop:config>的proxy-target-class属性设为true:

1
2
3
<aop:config proxy-target-class="true">
   ...
</aop:config>

当使用@AspectJ自动代理时要强制使用CGLIB,请将<aop:aspectj-autoproxy>的proxy-target-class属性设置为true:

1
<aop:aspectj-autoproxy proxy-target-class="true"/>