Spring基础系列——AOP

基础知识

AOP是Aspect Oriented Programing 的简称,译为“面向切面编程”。涉及到的概念有连接点、切点、增强、目标对象、引介、织入、代理、切面。

织入:

  1)编译期织入,需要特殊的java编译器;

  2)类装载期织入,需要特殊的类装载器;

  3)动态代理织入,在运行期为目标类添加增强生成子类的方法;

Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

View Code
public interface ForumService {
    public void removeTopic(int topicId);
}

public class ForumServiceImpl implements ForumService{

    public void removeTopic(int topicId) {
        System.out.println("delete topic record:" + topicId);
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 

时间统计类代码

View Code
public class PerformanceMonitor {
    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();

    public static void begin(String method) {
        System.out.println("begin monitor...");
        MethodPerformance mp = new MethodPerformance(method);
        performanceRecord.set(mp);
    }

    public static void end() {
        System.out.println("end monitor...");
        MethodPerformance mp = performanceRecord.get();
        mp.printPerformance();
    }
}


public class MethodPerformance {
    private long begin;
    private long end;
    private String serviceMethod;

    public MethodPerformance(String serviceMethod){
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();
    }

    public void printPerformance(){
        end = System.currentTimeMillis();
        long elapse = end - begin;
        System.out.println(serviceMethod+" cost " + elapse + " ms.");
    }
}

 

如果想对此service方法进行性能统计,传统方法只能在removeTopic方法中添加统计代码,记录开始,结束的时间然后计算差值。但是这样代码工作量巨大而且不易维护。

采用动态代理之后

View Code
/**
 * Author: Leo Sun
 * Blog: http://fuxinci.com/
 * Date: 3/25/13
 */
public class PerformanceHandler implements InvocationHandler {
    private Object target;

    public PerformanceHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
        Object obj = method.invoke(target, args);
        PerformanceMonitor.end();

        return obj;
    }
}

 

测试方法:

View Code
    public static void main(String[] args) {
        // test dynamic proxy
        ForumService target = new ForumServiceImpl();
        PerformanceHandler handler = new PerformanceHandler(target);
        ForumService proxy = (ForumService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler);

        proxy.removeTopic(1012);

    }

 

采用动态代理有一个限制,那就是只能为借口创建代理,对于没有使用借口定义的业务类如何织入呢?可以采用CGLib实现。

View Code
/**
 * Author: Leo Sun
 * Blog: http://fuxinci.com/
 * Date: 3/25/13
 */
public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName());
        Object result = methodProxy.invokeSuper(obj, args);
        PerformanceMonitor.end();

        return null;
    }
}

 

测试方法:

View Code
    public static void main(String[] args) {
        // test cglib
        CglibProxy cglibProxy = new CglibProxy();
        ForumServiceImpl forumService = (ForumServiceImpl)cglibProxy.getProxy(ForumServiceImpl.class);
        forumService.removeTopic(1012);

    }

  运行上面的代码输出以下信息:

begin monitor...
delete topic record:1012
end monitor...
org.sun.aop.ForumServiceImpl$$EnhancerByCGLIB$$a5b47e2a.removeTopic cost 38 ms.

输出的代理名字是ForumServiceImpl$$EnhancerByCGLIB$$a5b47e2a,这个特殊的类就是CGLib为ForumServiceImpl动态创建的子类。所以CGLib不能对目标类中的final方法进行代理。

关于性能:

  有研究表明CGLib所创建的动态代理对象性能比jdk所创建的代理对象的性能高不少(大概10倍),但是CGLib在创建代理的时候所花的时间却比jdk动态代理多(大概8倍)。

增强类

按照增强在目标类的方法的连接点的位置,可以分为以下5种:前置增强、后置增强、环绕增强、异常抛出增强、引介增强。

1.前置增强:

目标类

View Code
/**
 * Author: Leo Sun
 * Blog: http://fuxinci.com/
 * Date: 3/26/13
 */
public interface Waiter {
    void greetTo(String name);

    void serveTo(String name);
}

public class NaiveWaiter implements Waiter {
    public void greetTo(String name) {
        System.out.println("Greet to " + name);
    }

    public void serveTo(String name) {
        System.out.println("Serve to " + name);
    }
}

增强内容

View Code
/**
 * Author: Leo Sun
 * Blog: http://fuxinci.com/
 * Date: 3/26/13
 */
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        String clientName = (String) objects[0];
        System.out.println("How are you! Mr." + clientName + ".");
    }
}

测试类

View Code
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;

/**
 * Author: Leo Sun
 * Blog: http://fuxinci.com/
 * Date: 3/26/13
 */
public class TestBeforeAdvice {
    public static void main(String[] args){
        Waiter target = new NaiveWaiter();
        BeforeAdvice advice = new GreetingBeforeAdvice();

        // spring 提供的代理工厂
        ProxyFactory pf = new ProxyFactory();
        // 设置代理目标
        pf.setTarget(target);
        // 为代理目标添加增强
        pf.addAdvice(advice);

        Waiter proxy = (Waiter)pf.getProxy();
        proxy.greetTo("John");
        proxy.serveTo("Leo");

    }
}

2.后置增强:

实现AfterReturningAdvice

3.环绕增强:

实现MethodInterceptor

4.异常抛出增强

实现ThrowsAdvice

5.引介增强

它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强是类级别的,而非方法级别。

切面

在介绍增强时,增强织入到目标类的所有方法中,如果我们想织入到目标类的特定方法中,那么就要使用切点进行目标连接点的定位了。

静态普通方法名切面,静态正则表达式方法匹配切面,动态切面,流程切面,符合切点切面,引介切面。

 

 

 

 

 

 

 

 

 

 

 

posted @ 2013-03-26 18:00  傅心词  阅读(1124)  评论(0编辑  收藏  举报