suninf 's blog

Enjoy From Programming And Technique

Spring 之方法拦截

Catalog

本文介绍一种基于Spring的通用的方法拦截方案,配合注解来轻量级的使用,方法拦截技术经常用于性能监控,统一日志等。

ProxyFactory

代理工厂提供了对象行为的扩展机制(addAdvice)可以在方法调用前后做一些事情。

// 接口和对象
interface UserReadService {
    String getUserId(String id);
}

class UserReadServiceImpl implements UserReadService {
    @Override
    public String getUserId(String id) {
        System.out.println(id);
        return id;
    }
}

// 用于拦截扩展的代理对象
class UserInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("start");
        Object obj = invocation.proceed();
        System.out.println("end");
        return obj;
    }
}
@Test
// 用代理对象factory.getProxy()代替原始对象UserReadServiceImpl
public void test() {
    ProxyFactory factory = new ProxyFactory(new UserReadServiceImpl());
    factory.addAdvice(new UserInterceptor());
    UserReadService userReadService = (UserReadService)factory.getProxy();
    userReadService.getUserId("suninf");
}

/*
输出:
start
suninf
end
*/

BeanPostProcessor

BeanPostProcessor是Spring在Bean对象创建好后,初始化前后提供一种改变对象用代理包装对象的机会。

BeanPostProcessor接口的定义:

public interface BeanPostProcessor {
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

性能监控的例子

  • 基于注解识别需要拦截的对象和方法
  • 基于BeanPostProcessor针对需要拦截的bean设置代理对象
  • 使用ProxyFactory设置拦截器作为代理
// MonitorPerf.java
// 定义用于监控方法的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MonitorPerf {
}
// MonitorAdvisor.java
// 针对使用了注解MonitorPerf来修饰方法的类,做代理
@Component
public class MonitorAdvisor implements BeanPostProcessor {
    @Resource
    private MethodCostAdvise methodCostAdvise;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (hasMethodPerf(bean.getClass())) {
            ProxyFactory proxyFactory = new ProxyFactory();
            proxyFactory.addAdvice(methodCostAdvise);
            proxyFactory.setOptimize(true);
            proxyFactory.setFrozen(true);
            proxyFactory.setTarget(bean);
            System.out.println("[monitor proxy] object:" + bean.getClass().getName() + " bean name : " + beanName + " init proxy");
            return proxyFactory.getProxy();
        }
        return bean;
    }

    private boolean hasMethodPerf(Class<? extends Object> aClass) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(aClass);
        for (int i=0; i<methods.length; ++i) {
            if ( methods[i].getAnnotation(MonitorPerf.class) != null ) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
// MethodCostAdvise.java
// 重载invoke,针对MonitorPerf修饰的方法做时间打点
@Component("methodCostAdvise")
public class MethodCostAdvise implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if ( invocation.getMethod().getAnnotation(MonitorPerf.class) != null ) {
            return logCost(invocation);
        } else {
            return invocation.proceed();
        }
    }

    public Object logCost(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();

        System.out.println("[method cost]"+invocation.getMethod().getDeclaringClass() + "::" +
                invocation.getMethod().getName() + " costs:" + (end - start) + "ms");
        return result;
    }
}

参考

Comments