本文介绍一种基于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;
}
}
参考
- http://liuwuhen.iteye.com/blog/1639050
- http://www.cnblogs.com/chenmo-xpw/p/5538663.html
- https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.htm
- http://winneryj.iteye.com/blog/307736
- http://wiki.jikexueyuan.com/project/spring/bean-post-processors.html
- http://www.jianshu.com/p/fb39f568cd5e
- http://www.shouce.ren/api/spring2.5/ch03s07.html
- http://www.cnblogs.com/jyyzzjl/p/5417418.html
- http://blog.csdn.net/xiao_jun_0820/article/details/7242379
- http://zhangw2004.iteye.com/blog/1965943