现在的位置: 首页 > 综合 > 正文

备忘-Spring AOP的使用方式

2013年10月23日 ⁄ 综合 ⁄ 共 10653字 ⁄ 字号 评论关闭
本测试基于SpringFramework 2.5.6, Java6

Spring AOP主要有3中使用方式,直接使用api,使用xml配置文件,和使用annotation简化xml配置文件

这里通过一个例子说明了这几种不同的用法,这个例子的需求是:方便的监控某个方法的运行时间。

如果用纯OO的方法,可能你不得不把代码加到每一个需要监控的方法中,或使用某种设计模式解决这个问题,无论如何,都改动了原有的代码。

对此,AOP显然是一个好的解决方案。

被检测的对象

package aoptest;

public class WorkerBean {
    public void doSomeWork1(int noOfTimes) {
        for (int x = 0; x < noOfTimes; x++) {
            work();
        }
    }

    public void doSomeWork2(int noOfTimes) {
        for (int x = 0; x < noOfTimes; x++) {
            work();
        }
    }
    
    private void work() {
        System.out.print("");
    }
}

首先我们定义了一个被检测类,它有两个方法doSomeWork1, doSomeWork2, 我们要监控这两个方法需要运行多长时间。无论你采用哪种AOP的使用方式,都不会改变这个类的代码。

1 使用API
1.1 定义一个advice类

package aoptest.code;

import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.util.StopWatch;

public class ProfilingAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // start the stop watch
        StopWatch sw = new StopWatch();
        sw.start(invocation.getMethod().getName());
        Object returnValue = invocation.proceed();
        sw.stop();
        
        dumpInfo(invocation, sw.getTotalTimeMillis());
        return returnValue;
    }

    private void dumpInfo(MethodInvocation invocation, long ms) {
        Method m = invocation.getMethod();
        Object target = invocation.getThis();
        Object[] args = invocation.getArguments();
        System.out.println("Executed method: " + m.getName());
        System.out.println("On object of type: " + target.getClass().getName());
        System.out.println("With arguments:");
        for (int x = 0; x < args.length; x++) {
            System.out.print(" > " + args[x]);
        }
        System.out.print("/n");
        System.out.println("Took: " + ms + " ms");
    }

}

这是一个为Around(环绕)类型的advice,它使用StopWatch记录被AOP的方法的执行时间,然后在console中打印出相关信息。

1.2 测试

package aoptest.code;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;

import aoptest.WorkerBean;

public class ProfilingExample {
    public static void main(String[] args) {
        WorkerBean bean = getWorkerBean();
        bean.doSomeWork1(10000000);
        bean.doSomeWork2(10000000);
    }

    private static WorkerBean getWorkerBean() {
        
        // 1. create a Pointcut
        JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();
        pc.setPattern(".*doSomeWork.*");
        
        //AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
        //pc.setExpression("execution(* aoptest.WorkerBean.doSomeWork*(..))");
        
        // 2. create a Advice
        ProfilingAdvice advice = new ProfilingAdvice();
        
        // 3. create a Advisor(Aspect)
        Advisor advisor = new DefaultPointcutAdvisor(pc, advice);
        
        // 4. create a Target
        WorkerBean target = new WorkerBean();
        
        // 5. create a ProxyFactory to Weaving
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisor(advisor);
        
        return (WorkerBean) factory.getProxy();
    }
}

注释1 使用正则表达式定义了一个pointcut,所有包含doSomeWork的method
注释2 生成了一个advice的实例
注释3 生成了一个advisor, 其实就是aspect, 它就是一个pointcut和advice的组合
注释4 生成了一个target对象,就是要被aop的对象
注释5 通过proxyFactory把aspect编织到target中

可以感觉到Spring是使用动态代理的方式实现AOP的。

以上示例说明了Spring AOP Api的一般使用过程,当然Spring提供了丰富的api进行不同操作,但概念是一致的。例子中注掉的两行代码就是使用AspectJ表达式定义一个pointcut,AspectJ表达式的表达能力更强。

Spring提倡的是无侵入式的编程方式,所以在实际使用时,我们一般不会直接使用proxyFactory来生成被AOP的对象,我们会使用xml来定义它们。

2 使用xml schema
说白了,就是通过xml配置文件告诉spring如何进行这个weaving过程。
2.1 编写配置文件apotest/schema2/beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 1. define the Target bean -->
    <bean id="workerBean" class="aoptest.WorkerBean" />

    <!-- 2. define the advice bean -->
    <bean id="profilingAdvice" class="aoptest.code.ProfilingAdvice" />

    <aop:config>

        <!-- 3. define the pointcut -->
        <aop:pointcut id="logBeanExecution"
            expression="execution(* aoptest.WorkerBean.doSomeWork*(..))" />

        <!-- 4. define the advisor(aspect) -->
        <aop:advisor pointcut-ref="logBeanExecution" advice-ref="profilingAdvice" />
        
    </aop:config>

</beans>

这里还是使用上个例子中实现的advice(aoptest.code.ProfilingAdvice)。
注释1 定义一个Target Bean, 就是我们要监控的类
注释2 定义一个advice Bean
注释3 定义一个pointcut, 这里使用aspectj表达式
注释4 定义一个advisor, 就是aspect啦,它组合了上面定义的pointcut和advice

2.2 测试

package aoptest.schema2;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import aoptest.WorkerBean;

public class ProfilingExample {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("aoptest/schema2/beans.xml");
        WorkerBean bean = (WorkerBean) ac.getBean("workerBean");
        bean.doSomeWork1(10000000);
        bean.doSomeWork2(10000000);
    }
    
}

代码干净多了,Spring容器在生成workerBean时,会根据aspect定义来判断是否对这个bean进行AOP, 显然它的两个方法符合条件。

注意,这里使用的advice是上个例子中实现的advice(aoptest.code.ProfilingAdvice),它实现了MethodInterceptor这个接口,Spring AOP提供了另外一种定义advice的方式,它不用实现任何接口。

3 使用xml schema,无接口的advice
3.1 定义一个无接口的advice

package aoptest.schema1;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class ProfilingAdvice {
    
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        // start the stop watch
        StopWatch sw = new StopWatch();
        sw.start(pjp.getSignature().getName());
        Object returnValue = pjp.proceed();
        sw.stop();
        
        dumpInfo(pjp, sw.getTotalTimeMillis());
        return returnValue;
    }

    private void dumpInfo(ProceedingJoinPoint pjp, long ms) {
        Object[] args = pjp.getArgs();
        System.out.println("Executed method: " + pjp.getSignature().getName());
        System.out.println("On object of type: " + pjp.getTarget().getClass().getName());
        System.out.println("With arguments:");
        for (int x = 0; x < args.length; x++) {
            System.out.print(" > " + args[x]);
        }
        System.out.print("/n");
        System.out.println("Took: " + ms + " ms");
    }

}

其中方法log有个参数,类型为ProceedingJoinPoint,我们可以通过这个参数得到target的信息。如果你不需要target的任何信息,可以不需要这个参数。由于这是一个环绕类型的advice,没有这个参数是不行的。

3.2 编写配置文件apotest/schema1/beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 1. define the Target bean -->
    <bean id="workerBean" class="aoptest.WorkerBean" />

    <!-- 2. define the advice bean -->
    <bean id="profilingAdvice" class="aoptest.schema1.ProfilingAdvice" />

    <aop:config>
    
        <!-- 3. define the aspect -->
        <aop:aspect id="myAspect" ref="profilingAdvice">
        
            <!-- 4. define the pointcut -->
            <aop:pointcut id="logBeanExecution"
                expression="execution(* aoptest.WorkerBean.doSomeWork*(..))" />
                
            <!--  5. define the advice type -->
            <aop:around pointcut-ref="logBeanExecution" method="log" />

        </aop:aspect>
    </aop:config>

</beans>

注释1 定义Target
注释2 定义advice
注释3 定义aspect
注释4 定义这个aspect包含的pointcut
注释5 定义这个aspect包含的advice

3.3 测试

package aoptest.schema1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import aoptest.WorkerBean;

public class ProfilingExample {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("aoptest/schema1/beans.xml");
        WorkerBean bean = (WorkerBean) ac.getBean("workerBean");
        bean.doSomeWork1(10000000);
        bean.doSomeWork2(10000000);
    }
    
}


4 使用annotation简化xml配置文件
4.1 定义一个无接口的advice

package aoptest.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;

// 1. @Aspect annotation
@Aspect
public class ProfilingAdvice {
    
    // 2. @Pointcut annotation
    @Pointcut("execution(* aoptest.WorkerBean.doSomeWork*(..))")
    private void logBeanExecution() { }
    
    // 3. @Around annotation
    @Around("logBeanExecution()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        // start the stop watch
        StopWatch sw = new StopWatch();
        sw.start(pjp.getSignature().getName());
        Object returnValue = pjp.proceed();
        sw.stop();
        
        dumpInfo(pjp, sw.getTotalTimeMillis());
        return returnValue;
    }

    private void dumpInfo(ProceedingJoinPoint pjp, long ms) {
        Object[] args = pjp.getArgs();
        System.out.println("Executed method: " + pjp.getSignature().getName());
        System.out.println("On object of type: " + pjp.getTarget().getClass().getName());
        System.out.println("With arguments:");
        for (int x = 0; x < args.length; x++) {
            System.out.print(" > " + args[x]);
        }
        System.out.print("/n");
        System.out.println("Took: " + ms + " ms");
    }

}

这个advice的代码中只是比上一个例子中的advice多了一些annotation。
注释1 标注这是一个aspect
注释2 把logBeanExecution标注为一个pointcut的名字
注释3 把方法log标注为一个类型为Around的advice, 并使用名为logBeanExecution的pointcut
所以,严格的说这个advice加上这些标注后就不能称之为advice了,它就是一个aspect,这里仍然用这个类名是为了和上个例子做比较。

4.2 编写配置文件apotest/schema1/beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="workerBean" class="aoptest.WorkerBean" />
    
    <bean class="aoptest.annotation.ProfilingAdvice"/>
    
    <aop:aspectj-autoproxy />

</beans>

配置文件简单多了,你只需要定义这些bean即可,spring容器会自动发现哪些bean是aspect,并把它们编织到合适的其他bean中。

4.3 测试

package aoptest.annotation;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import aoptest.WorkerBean;

public class ProfilingExample {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("aoptest/annotation/beans.xml");
        WorkerBean bean = (WorkerBean) ac.getBean("workerBean");
        bean.doSomeWork1(10000000);
        bean.doSomeWork2(10000000);
    }
    
}

5. 使用哪一种方式
如果你使用jdk1.4,那么就不能使用annotation了。
API的方式一般我们不会使用。但是如果我们需要动态性,就是说target是在程序运行时动态生成的,原因可能是状态是动态的,不能通过配置文件确定,这时我们可能只能使用API的方式了。
如果你使用的是java5以上版本,是否应该使用annotation呢?这是个使用spring的普遍存在的问题,值得讨论...

抱歉!评论已关闭.