通过Spring结合Cglib处理非接口代理
背景
之前做的一个关于报表的项目,大部分场景是查询下游ES订单中心然后进行分析和计算,这块涉及到的业务比较复杂非常耗时;复杂体现在需要对数据进行各种逻辑运算,耗时体现在ES查询有限制并发。
经常收到JVM峰值告警邮件,于是对其进行分析得到基础数据每天凌晨更新一次,但查询和计算其实在第一次之后就可以写入缓存,这样后面直接从缓存拿数据,避免了大对象创建和网络开销, 经过梳理+调研,最后采用Cglib动态代理进行优化。
遇到的问题
这个项目基于spring boot搭建,里面方法调用都是通过类与类,没有做接口层,所以无法使用Jdk的动态代理。我们都知道Jdk动态代理是基于接口层的代理,但基于的类的代理只能通过字节码层面代理,在这个项目中,很多方法调用是基于类方法的调用,如果要加入代理,可以采用字节码代理框架,最简单的实现方式无非如下:
CglibCacheProxy cacheMethodInterceptor = new CglibCacheProxy(); AgreementHotelPercentService proxyAgreementHotelPercentService = (AgreementHotelPercentService)cacheMethodInterceptor.createProxyObject(agreementHotelPercentService); AgreementAndMemberHotelPercent agreementAndMemberHotelPercent = proxyAgreementHotelPercentService.getHotelPercent(filterList);
上面的代码通过new一个Cglib工具类,然后需要代理的类丢进去,这么看起来是没什么问题,如果一个项目里有上百个这样的代码需要改造,效率、风险点、。于是想到采用AOP,把公共的代理模块抽取出来。问题是如何才能知道哪个类哪个方法要代理?如何代理?
基于Spring实现后置处理
大致思路就是在Spring加载完后, 再通过srping的后置处理器(BeanPostProcessor)拿出需要代理的Bean,然后通过注解方式给这个bean创建代理。
1. BeanPostProcessor简介
该接口我们也叫后置处理器,作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。接口的源码如下
public interface BeanPostProcessor { //bean初始化方法调用前被调用 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; //bean初始化方法调用后被调用 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
方法 | 说明 |
---|---|
postProcessBeforeInitialization | 实例化、依赖注入完毕, 在调用显示的初始化之前完成一些定制的初始化任务 |
postProcessAfterInitialization | 实例化、依赖注入、初始化完毕时执行 |
2.自定义 CglibCachePostBeanProcessor
public class CglibCachePostBeanProcessor implements BeanPostProcessor{ @Override public Object postProcessAfterInitialization(Object bean,String beanName) throws BeansException{ if(bean.getClass().isAnnotationPresent(CglibCache.class)){ //判断是代理的类 CglibCache cglibCache = bean.getClass().getAnnotation(CglibCache.class); if(cglibCache.isScan()){ //创建代理 return CglibCacheProxy.createProxy(bean); } }else{ return bean; } return bean; } @Override public Object postProcessBeforeInitialization(Object bean, String beanNames) throws BeansException{ return bean; } }
代理工具类(主要是用于给bean创建代理)
public static Object createCacheProxy(Object bean){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(bean.getClass());//被代理的类 enhancer.setCallback(new CacheMethodInterceptor(bean)); return enhancer.create(); }
类级别注解,用于修饰需要代理的类
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface CglibCache { //是否启用扫描 boolean isScan() default true; }
3.Cglib代理类
public class CacheMethodInterceptor implements MethodInterceptor{ CacheStorageService cacheStorageService; //代理对象 private Object target; public CacheMethodInterceptor (Object target){ this.target = target; } @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable{ Object result = null; //方法上打了Cache的注解则说明需要执行缓存切入 Cache cacheable = method.getAnnotation(Cache.class); if(cacheable!=null){ //具体的缓存业务逻辑.... } return method.invoke(target,args); } }
4.Cache缓存注解
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Cache { /** * 缓存key的名称 * * @return */ String key(); /** * key是否转换成md5值,有的key是整个参数对象,有的大内容的,比如一个大文本,导致redis的key很长 * 需要转换成md5值作为redis的key * * @return */ boolean keyTransformMd5() default true; /** * key 过期日期 秒 * * @return */ int expireTime() default 60; /** * 时间单位,默认为秒 * * @return */ TimeUnit dateUnit() default TimeUnit.SECONDS; }
最后
以上就是本篇博客核心代码,只需要在需要代理的类加上@CglibCache注解,并且在对应的方法加上@Cache 注解,结合缓存处理类就能实现数据从缓存拉取。