PSJay Blog

#FIXME, seriously

Spring 3 AOP APIs

| Comments

上篇文章说到了Spring AOP的一些术语和三种实现方式。本篇文章就讲基于Spring AOP API的实现。

在Spring 1.2中,Spring AOP就是用的这种实现方式。Spring 3也完全对其向后兼容。

Pointcut API

org.springframework.aop.Pointcut是Spring的Pointcut接口。其定义如下:

1
2
3
4
5
6
7
public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

可以看到,Pointcut接口有两个方法。分别是取到类过滤器(ClassFilter)和方法匹配器(MethodMatcher)。ClassFilter和MethodMatcher也是两个接口。

ClassFilter接口

1
2
3
public interface ClassFilter {
    boolean matches(Class clazz);
}

ClassFilter主要用来过滤target object。

MethodMatcher接口

1
2
3
4
5
6
7
public interface MethodMatcher {
    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}

注意到MethodMatcher有两个matches方法。有两个参数的matches方法仅仅用来匹配target class的某个method,而三个参数的matches方法还会匹配method的参数。因为一个方法的参数每次都可能是不同的对象引用,所以三个参数的matches方法会在每次匹配的时候都执行。而带两个参数的metches方法仅仅只在第一次匹配时执行,因为一个方法本身和它所属的类,总是固定不变的。

所以Spring AOP将Pointcut分成两类:静态pointcut和动态pointcut。静态pointcut只检查class和method,动态pointcut在静态pointcut的基础上还需检查method的参数。如果你想实现自己的静态pointcut只需要继承StaticMethodMatcherPointcut并且重写matches方法即可。Spring文档中的例子如下:

1
2
3
4
5
class TestStaticPointcut extends StaticMethodMatcherPointcut {
    public boolean matches(Method m, Class targetClass) {
        // 条件符合就返回true
    }
}

Advice API

在Spring中,advice就是一个Spring bean,所以它可以是singleton或prototype的,在AOP中叫做per-class和per-instance。

通常,我们使用的是per-class advice,因为我们的advice通常和target object或其代理的状态没有关系,advice仅仅只作用域目标方法(point cut)和它的参数。当然,如果需要执行introductions操作,我们就要用到per-instance advice了。

Advice的几种类型

Advice API中,定义了一些不同类型的advice接口。如before advice, after returning advice等。另外,Spring为了符合AOP联盟的标准,还引入了inteceptor around advice。Spring的官方文档中一一列举了这些接口的用法

Advisor API

Advisor是一种特殊的Aspect,它只包含一个Advice及与其相关联的pointcut。

ProxyFactoryBean

Spring AOP是基于代理的,每个被代理的类的代理实例都由对应的ProxyFactory工厂bean来生产。不同类的ProxyFactoryBean一般是不同的。

所以,在创建ProxyFactoryBean对象时,必须指明被代理的类(target object)和代理的实现方式,即JDK Proxy或CGLIB Proxy。

示例

我们以一个简单的例子来演示怎样使用Spring AOP APIs来实现一个BeforeAdivce。

假设我们有一个UserManager类,代码如下:

1
2
3
4
5
public class UserManager {
  public void addUser(String user) {
      System.out.println("addUser(String str) method is executed!");
  }
}

我们需要在UserManager的addUser方法之前做一些操作。所以我们需要定义一个Advice类。这个Advice类需要实现org.springframework.aop.MethodBeforeAdvice接口。

1
2
3
4
5
6
7
8
public class MyBeforeAdvice implements MethodBeforeAdvice {

  @Override
  public void before(Method arg0, Object[] arg1, Object arg2)
          throws Throwable {
      System.out.println("Before advice is executed!");
  }
}

在这里,我们所做的操作只是简单的打印一句话。

然后,我们必须在Spring的配置文件中配置UserManager、MyBeforeAdvice和对应的ProxyFactoryBean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="userManager" class="com.psjay.example.spring.aop.UserManager"></bean>


<bean id="myBeforeAdvice" class="com.psjay.example.spring.aop.MyBeforeAdvice"></bean>


<bean id="userManagerProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property ref="userManager" name="target"></property>
  <property name="interceptorNames">
      <list>
          <value>myBeforeAdvice</value>
      </list>
  </property>
</bean>

UserManagerProxy的class属性必须是org.springframework.aop.framework.ProxyFactoryBean,因为我们需要靠这个类来创建UserManager的代理。target属性是被代理的bean,interceptorNames则是Advice。

最后,我们建立一个测试类Test。

1
2
3
4
5
6
7
public class Test {
  public static void main(String[] args) {
      ApplicationContext ctx  = new ClassPathXmlApplicationContext("applicationContext.xml");
      UserManager um =  (UserManager) ctx.getBean("userManagerProxy");
      um.addUser("hey");
  }
}

运行结果:

Before advice is executed! addUser(String str) method is executed!

可以看到,我们的advice已经生效了。

Spring AOP 的API的大致框架就是如此。关键类是ProxyFactoryBean,它负责创建对应类的代理实例。使用底层的API来实现AOP显然不是明智的选择,我们只不过是借此来看看Spring AOP的底层。

Comments