【原】通过Spring结合Cglib处理非接口代理

前言:

  之前做的一个项目,虽然是查询ES,但内部有大量的逻辑计算,非常耗时,而且经常收到JVM峰值告警邮件。分析了一下基础数据每天凌晨更新一次,但查询和计算其实在第一次之后就可以写入缓存,这样后面直接从缓存拿数据,避免了大对象创建和网络开销,最后采用了Spring+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; } }

 

代理工具类:

 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.方法级别注解

@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 注解,结合缓存处理类就能完美的实现数据从缓存拉取。

 

 

你可能感兴趣的:(【原】通过Spring结合Cglib处理非接口代理)