zoukankan      html  css  js  c++  java
  • 【原】通过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 注解,结合缓存处理类就能完美的实现数据从缓存拉取。

    
    
  • 相关阅读:
    3D打印技术大潮
    有用网址
    linux下scp命令详解
    使用 GDB 调试多进程程序
    linux下top命令参数解释
    Sql动态查询拼接字符串的优化
    vmstat参数详解
    freebsd破解密码
    freebsd防火墙
    freebsd无法输入汉字
  • 原文地址:https://www.cnblogs.com/zdd-java/p/11988174.html
Copyright © 2011-2022 走看看