如何不用修改原来的代码呢?
一.需求描述
最近遇到这样一个需求,有个数据计算的方法,我们需要优化一下,因为有些数据在一段时间内计算出的结果总是一样的,为了提高性能,我们把这些数据的计算结果缓存起来,这样下次计算的时候就先去缓存中查一下,要是缓存中存在,就直接把缓存中的数据返回,要是缓存中不存在就走原来的计算逻辑去计算,面对这个需求,我们需要做两件事情,在调用计算方法之前读取一下缓存,在调用方法之后把值写入缓存。这样的计算方法有10多个,相应的类也有10多个。
二.思路分析
面对这样的需求,绝对不能去修改10多个类,首先这样做有很多工作量,其次你所做的事情都是Repeat Yourself,没意义的。其实这种需求,我们最容易想到的就是AOP理念了,定义两个切面,方法执行前执行一下,方法返回前执行一下就好,为了好描述,我们暂且认为读缓存的切面是A,写缓存的切面是B,仔细分析一下,我们执行完切面A后,可能不需要再继续执行原来的计算方法了,直接返回就完事了,要是我们把读缓存的单独抽象出一个切面来,我们在这个切面中根本没法控制不去执行被拦截的计算方法,因此我们不能把读取缓存这个逻辑单独抽象成一个Spring AOP的切面,所以上面抽象出A,B切面的思路不可行,这时候我们需要正对这些方法定义一个统一的MethodInterceptor去拦截一下这个方法,在执行方法之前去读一下缓存,执行方法之后写一下缓存,这样写出来的伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class XXXMethodInterceptor implements MethodInterceptor { private static final String METHOD_NAME = "xxxx"; @Override public Object invoke(MethodInvocation arg) throws Throwable { // 对xxxx方法做拦截 if (METHOD_NAME.equals(arg.getMethod().getName())) { // 读取缓存,要是缓存中有值,直接返回 Object cacheResult = readFromCache(); if (cacheResult != null) { return cacheResult; } // 调用原来的方法 Object result = arg.proceed(); // 写缓存,当然有些计算结果是不用写缓存的 writeCache(result); // 返回结果 return resultFromDataSource; } } return arg.proceed(); } } |
这样我们的方法拦截器就定义好了,如何把它织入到原来的代码逻辑中,我们使用Spring的ProxyFactoryBean,这是一个代理的工厂bean,它可以返回被代理的bean,同时把我们的拦截逻辑织入到代理bean中,生成代理对象的时候如果我们的类实现了接口,就会使用jdk的动态代理,重新生成一个代理类,要是我们的类没有实现接口,就会使用cglib动态去修改字节码。下面我们来看看如何织入我们的拦截逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <!-- 定义抽象的代理bean --> <bean id="abstractProxy" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true"> <property name="interceptorNames"> <list> <value>xXXXMethodInterceptor</value> </list> </property> </bean> <!-- 拦截器的定义 --> <bean id="xXXXMethodInterceptor" class="xxxx.xxx.XXXMethodInterceptor" /> <!-- 新定义一个bean 织入我们的拦截器 --> <bean id="xxxx" parent="abstractProxy"> <property name="target"> <bean class="xxxxxxx.xxxxx" /> </property> </bean> |
注意:
- 1.我们可以在ProxyFactoryBean中定义多个拦截器,目前只是定义了一个方法的拦截器
- 2.在写Spring配置文件的时候注意抽象bean的灵活使用