前言:

  之前做的一个项目,虽然是查询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处理非接口代理的更多相关文章

  1. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  2. 死磕Spring之AOP篇 - 初识JDK、CGLIB两种动态代理

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...

  3. Spring AOP 介绍与基于接口的实现

    热烈推荐:超多IT资源,尽在798资源网 声明:转载文章,为防止丢失所以做此备份. 本文来自公众号:程序之心 原文地址:https://mp.weixin.qq.com/s/vo94gVyTss0LY ...

  4. Spring Boot整合MyBatis(非注解版)

    Spring Boot整合MyBatis(非注解版),开发时采用的时IDEA,JDK1.8 直接上图: 文件夹不存在,创建一个新的路径文件夹 创建完成目录结构如下: 本人第一步习惯先把需要的包结构创建 ...

  5. spring ----> 事务:传播机制和接口TransactionDefinition

    spring事务: 编程式事务(细粒度) 声明式事务(粗粒度,xml或者注解格式) spring接口TransactionDefinition: TransactionDefinition接口定义了事 ...

  6. Spring源码解析 - BeanFactory接口体系解读

    不知道为什么看着Spring的源码,感触最深的是Spring对概念的抽象,所以我就先学接口了. BeanFactory是Spring IOC实现的基础,这边定义了一系列的接口,我们通过这些接口的学习, ...

  7. Spring Boot实现通用的接口参数校验

    Spring Boot实现通用的接口参数校验 Harries Blog™ 2018-05-10 2418 阅读 http ACE Spring App API https AOP apache IDE ...

  8. JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解

    在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...

  9. 【Spring】Spring的事务管理 - 1、Spring事务管理概述(数据库事务、Spring事务管理的核心接口)

    Spring事务管理概述 文章目录 Spring事务管理概述 数据库事务 什么是Spring的事务管理? Spring对事务管理的支持 Spring事务管理的核心接口 Platform Transac ...

随机推荐

  1. React教程:4 个 useState Hook 示例

    摘要: React示例教程. 原文:快速了解 React Hooks 原理 译者:前端小智 到 React 16.8 目前为止,如果编写函数组件,然后遇到需要添加状态的情况,咱们就必须将组件转换为类组 ...

  2. Ubantu搭建虚拟环境

    配置虚拟环境 Ubantu16.0.4 1.安装python虚拟环境 sudo apt-get install virtualenv 2.vrtaulenvwrapper是virtualenv的扩展包 ...

  3. idea之前的版本

    https://www.jetbrains.com/idea/download/previous.html

  4. C++ class外的 << 重载,输出流,重载示例。不应该定义类内的<<重载

    #include <iostream> // overloading "operator << " outside class // << 应该 ...

  5. CF915E Physical Education Lessons 珂朵莉树

    问题描述 CF915E LG-CF915E 题解 \(n \le 10^9\) 看上去非常唬人. 但是这种区间操作的题,珂朵莉树随便跑啊. \(\mathrm{Code}\) #include< ...

  6. ESP8266 AT指令开发(基于STC89C52单片机): 测试下诱人的程序(51单片机,8266,MQTT远程通信控制)

    前言 实现的功能,APP通过SmartConfig给Wi-Fi模块配网并绑定设备,然后通过MQTT远程控制开发板的继电器, 简而言之: 51单片机+ESP8266用AT指令实现实现MQTT,(连接的本 ...

  7. 【BZOJ4518】[SDOI2016] 征途(重拾斜率优化DP)

    点此看题面 大致题意: 让你把一个长度为\(n\)的序列划分成\(m\)块,求每块数总和的最小方差乘\(m^2\)的值. 转化方差 首先方差显然是一个比较复杂的东西,需要进行一定转化. 设\(p_i\ ...

  8. Educational Codeforces Round 69 (Rated for Div. 2) C. Array Splitting 水题

    C. Array Splitting You are given a sorted array

  9. R语言两自定义矩阵的基本运算-实例

    #sink("matrix_history.txt") cat("请输入矩阵的行和列数,“,”号隔开,建议行 等于列 数:") number<-scan( ...

  10. tsar使用说明

    常用命令    tsar --nginx --live -i 1  查询1秒的状态每秒采样一次 系统模块 cpu 字段含义 user: 表示CPU执行用户进程的时间,通常期望用户空间CPU越高越好. ...