当前位置:网站首页>AOP implementation enterprise API access interface monitoring (via Google Guava cache data)
AOP implementation enterprise API access interface monitoring (via Google Guava cache data)
2022-07-29 15:03:00 【A programmer who loves sports】
Developed enterprise function modules,分享给大家参考,If you see the shortcomings of my implementation or have your own opinions, please share them in the comment area٩꒰▽ ꒱۶⁼³₌₃ go learn
文章目录
前言
需求:
1、Records the number of times the interface access succeeds and fails
2、Record the time spent on the success and failure of interface access
3、Access timestamp records,以5分钟为一个节点,例如:13:14The points are recorded as13:10分
4、公开一个接口,返回Google Guava缓存的数据,以JSON的格式
5、It is still available under high concurrency,保证线程安全
The above are the specific needs,At present, this requirement only stores specific interface information,In the future, monitoring and early warning may be implemented:The number of interface failures in the current node exceeds the threshold、If the time taken to call the interface exceeds the threshold, an alert needs to be sent(邮箱通知等等)
一、AOP的基本知识
1、什么是AOP?
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性.
2、有哪些AOP的概念?
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理.
3、AOPcontains several concepts
1)、Jointpoint(连接点):Concrete facets of abstract concepts,Can be in the field、方法上,SpringThe specific form of expression isPointCut(切入点),Only works on methods.
2)、Advice(通知): The specific operation performed at the connection point,How to enhance processing,分为前置、后置、异常、最终、Surrounding five situations.
3)、目标对象:被AOP框架进行增强处理的对象,Also known as an enhanced object.
4)、AOP代理:AOP框架创建的对象,简单的说,代理就是对目标对象的加强.Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理.
5)、Weaving(织入):将增强处理添加到目标对象中,The process of creating an enhanced object
总结为一句话就是:在目标对象(target object)的某些方法(jointpoint)Add different kinds of actions(通知、Enhanced handling),Finally by some means(weaving、织入操作)Implements a new proxy target object.
4、AOP 有哪些应用场景?
举几个例子:
- 记录日志(Log after the method is called)
- 监控性能(统计方法运行时间)
- 权限控制(Check for permission before calling the method)
- 事务管理(调用方法前开启事务,调用方法后提交关闭事务 )
- 缓存优化(第一次调用查询数据库,将查询结果放入内存对象, 第二次调用,直接从内存对象返回,不需要查询数据库 )
5、有哪些AOP Advice通知的类型?
特定 JoinPoint 处的 Aspect 所采取的动作称为 Advice.Spring AOP 使用一个 Advice 作为拦截器,在 JoinPoint “周围”维护一系列的拦截器.
- 前置通知(Before advice) : 这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置.
- 后置通知(After advice) :这些类型的 Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置.
- 返回后通知(After return advice) :这些类型的 Advice 在连接点方法正常执行后执行,并使用@AfterReturning 注解标记进行配置.
- 环绕通知(Around advice) :这些类型的 Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置.
- 抛出异常后通知(After throwing advice) :仅在 joinpoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行.
二、Google Guava是什么?
This is a tool class,很好用,Boss let me study hard,Because he also used this to cache when he was working in Ali before.
Google Guava 官网: https://guava.dev/
Google Guava 中文教程: https://wizardforcel.gitbooks.io/guava-tutorial/content/
Just look at the documentation,in conjunction with the Internetdemo,Basic use is fine.
三、功能实现
1.引入依赖
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.12</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2、Write a return content entity
MonitoringVO.java:
/**
* @author 一个爱运动的程序员
*/
@Data
public class MonitoringVO {
/**
* 请求时间
*/
private String time;
/**
* 接口请求的URL
*/
private String apiURL;
/**
* The number of times the interface request was successful
*/
private Long successfulNum;
/**
* The number of times the interface request failed
*/
private Long failuresNum;
/**
* Cumulative time spent on successful interface requests
*/
private Long successfulTime;
/**
* Cumulative time spent for interface request failures
*/
private Long failuresTime;
}
3、编写AOP
ApiVisitHistory.java:
/**
* APIAccess historical statistics
*
* @author 一个爱运动的程序员
*/
@Component
@Aspect
public class ApiVisitHistory {
private Logger log = LoggerFactory.getLogger(ApiVisitHistory.class);
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 定义切面
* - 此处代表com.example.demo.monitoring.controllerAll interfaces under the package will be counted
*/
@Pointcut("execution(* com.example.demo.monitoring.controller..*.*(..))")
public void pointCut() {
}
/**
* 在接口原有的方法执行前,将会首先执行此处的代码
*/
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
//获取传入目标方法的参数
log.info("类名:{}", joinPoint.getSignature().getDeclaringType().getSimpleName());
log.info("方法名:{}", joinPoint.getSignature().getName());
}
/**
* 只有正常返回才会执行此方法
* 如果程序执行失败,则不执行此方法
*/
@Async
@AfterReturning(returning = "returnVal", pointcut = "pointCut()")
public void doAfterReturning(JoinPoint joinPoint, Object returnVal) throws ExecutionException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String key = AtomicCounter.getTimeStamp() + "--" + AtomicCounter.getApiURL(request);
// 成功计数+1
AtomicCounter.increaseSucceed(AtomicCounter.getCountObject(key));
// 耗时计算
Long succeedTime = System.currentTimeMillis() - startTime.get();
log.info("接口访问成功,URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), succeedTime);
// 存储APIInterface access information
AtomicCounter.setSuccessfulTime(key, succeedTime);
}
/**
* 当接口报错时执行此方法
*/
@AfterThrowing(pointcut = "pointCut()")
public void doAfterThrowing(JoinPoint joinPoint) throws ExecutionException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String key = AtomicCounter.getTimeStamp() + "--" + AtomicCounter.getApiURL(request);
// 失败计数+1
AtomicCounter.increaseFail(AtomicCounter.getCountObject(key));
// 耗时计算
Long failTime = System.currentTimeMillis() - startTime.get();
log.info("接口访问失败,URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), failTime);
AtomicCounter.setMultiMap(key, failTime);
}
}
4、Write storage utility classes
AtomicCounter java:
/**
* 记录API接口访问成功/Failed count and time
*
* @author 一个爱运动的程序员
*/
public class AtomicCounter {
/**
* Guava Cache 缓存API访问记录
*/
private static Cache<String, ConcurrentHashMap<AtomicCounter, MonitoringVO>> cache = CacheBuilder.newBuilder().expireAfterWrite(60 * 10, TimeUnit.SECONDS).build();
/**
* Called count object
*
* @param key
* @return
*/
public static ConcurrentHashMap<AtomicCounter, MonitoringVO> getCountObject(String key) {
ConcurrentHashMap<AtomicCounter, MonitoringVO> ifPresent = cache.getIfPresent(key);
if (ifPresent == null || ifPresent.isEmpty()) {
ifPresent = new ConcurrentHashMap<>();
AtomicCounter atomicCounter = new AtomicCounter();
MonitoringVO monitoringVO = new MonitoringVO();
String[] split = key.split("--");
monitoringVO.setTime(split[0]);
monitoringVO.setApiURL(split[1]);
monitoringVO.setSuccessfulNum(0L);
monitoringVO.setFailuresNum(0L);
monitoringVO.setSuccessfulTime(0L);
monitoringVO.setFailuresTime(0L);
ifPresent.put(atomicCounter, monitoringVO);
cache.put(key, ifPresent);
}
return ifPresent;
}
/**
* Increase the number of times the access interface is called successfully
* @param concurrentHashMap
* @return
*/
public static void increaseSucceed(ConcurrentHashMap<AtomicCounter, MonitoringVO> concurrentHashMap) {
MonitoringVO monitoringVO = null;
for (MonitoringVO m : concurrentHashMap.values()) monitoringVO = m;
Lock lock = new ReentrantLock();
try {
lock.lock();
monitoringVO.setSuccessfulNum(monitoringVO.getSuccessfulNum() + 1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* The elapsed time for a successful update call
* @param key
* @param successTime
* @throws ExecutionException
*/
public static void setSuccessfulTime(String key, Long successTime) {
ConcurrentHashMap<AtomicCounter, MonitoringVO> map = getCountObject(key);
MonitoringVO monitoringVO = null;
for (MonitoringVO m : map.values()) monitoringVO = m;
Lock lock = new ReentrantLock();
try {
lock.lock();
monitoringVO.setSuccessfulTime(monitoringVO.getSuccessfulTime() + successTime);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* Increase the number of times the access interface invocation fails
* @param concurrentHashMap
* @return
*/
public static void increaseFail(ConcurrentHashMap<AtomicCounter, MonitoringVO> concurrentHashMap) {
MonitoringVO monitoringVO = null;
for (MonitoringVO m : concurrentHashMap.values()) monitoringVO = m;
Lock lock = new ReentrantLock();
try {
lock.lock();
monitoringVO.setFailuresNum(monitoringVO.getFailuresNum() + 1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
*The elapsed time for the update call to fail
* @param key
* @param failTime
*/
public static void setMultiMap(String key, Long failTime) {
ConcurrentHashMap<AtomicCounter, MonitoringVO> map = getCountObject(key);
MonitoringVO monitoringVO = null;
for (MonitoringVO m : map.values()) monitoringVO = m;
Lock lock = new ReentrantLock();
try {
lock.lock();
monitoringVO.setFailuresTime(monitoringVO.getFailuresTime() + failTime);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 获取时间戳,并以5Minutes are nodes
* @return
*/
public static String getTimeStamp() {
// 获取时间戳
Long timeStamp = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");
SimpleDateFormat sdfMinute = new SimpleDateFormat("mm");
// 时间戳转换成时间
String sd = sdf.format(new Date(Long.parseLong(String.valueOf(timeStamp))));
// 时间戳转换成时间
String sdMinute = sdfMinute.format(new Date(Long.parseLong(String.valueOf(timeStamp))));
sd += ":" + (Integer.valueOf(sdMinute) - Integer.valueOf(sdMinute) % 5);
return sd;
}
/**
* 获取API的接口URL
* @param request
* @return
*/
public static String getApiURL(HttpServletRequest request) {
// 请求路径
String requestURI = request.getRequestURI();
return requestURI;
}
/**
* 获取API监控信息
*
* @return
*/
public static CopyOnWriteArrayList<MonitoringVO> getCacheMap() {
ConcurrentMap<String, ConcurrentHashMap<AtomicCounter, MonitoringVO>> stringConcurrentHashMapConcurrentMap = cache.asMap();
CopyOnWriteArrayList<MonitoringVO> list = new CopyOnWriteArrayList<>();
for (ConcurrentHashMap<AtomicCounter, MonitoringVO> c : stringConcurrentHashMapConcurrentMap.values())
for (MonitoringVO m : c.values()) list.add(m);
return list;
}
}
5、编写测试类
HelloController.java:
@RestController
@RequestMapping("/aop")
public class HelloController {
static int r = 0;
@GetMapping("success")
public String success() {
return "调用成功";
}
@GetMapping("fail")
public String fail() {
if (r == 3) {
r++;
return "调用错误接口,成功一次";
} else {
r++;
int i = 1 / 0;
return "The test is wrongAOP方法";
}
}
@GetMapping("cache")
public String get() {
CopyOnWriteArrayList<MonitoringVO> list = AtomicCounter.getCacheMap();
return JSONObject.valueToString(list);
}
}
项目目录:
运行效果:
总结
以上就是今天要分享的内容,在线程安全方面,Google Guava CacheCaches are inherently thread-safe,Therefore, it is necessary to pay attention to the thread safety of the operation process,本可以使用AtomicIntegerA thread-safe integer data type,But because the storage is too cumbersome and difficult to handle,Finally used in processing operationslock来保证线程安全.今天的分享就到这里,给个三连呗,创作不易(づ。◕ᴗᴗ◕。)づ
附加功能
需求:Can be customized to enhance the interface,例如:Provide monitoring functions to the interface in the form of annotations
代码如下:
MonitoringAnnotation .java:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MonitoringAnnotation {
}
ApiVisitHistory.java:
@Pointcut("@annotation(com.example.demo.monitoring.annotation.MonitoringAnnotation)")
修改路径即可:
Just add this annotation to the interface method or class we need to monitor:
@MonitoringAnnotation
源码地址
https://github.com/XIN007-C/api-monitoring/tree/main/aopdemo
如果对大家有帮助,给个start吧 ψ(*`ー´)ψ
边栏推荐
- 测试日报怎么写 ?
- 【Postman】Download and installation (novice graphic tutorial)
- 疫情之下的裁员浪潮,7点建议帮你斩获心仪offer
- 共享内存 - shmget填坑记
- EA&UML日拱一卒-活动图::StartClassifierBehavior和StartObjectBehavior
- 请写出用Graphics2D类的新方法画一个图形的步骤(表格如何画斜线)
- 威纶通触摸屏制作自定义欢迎界面的几种方法介绍
- 打卡广汽本田喜悦安全驾驶中心,体验最刁钻的场地训练
- 升级openssl1.1.1(mix2s哪个版本不断流)
- 电视处理器a53和a55哪个厉害(cortexa55处理器好吗)
猜你喜欢
随机推荐
MySQL 是如何实现 ACID 的?
【LeetCode】350. 两个数组的交集 II
Pinia状态持久化
面对互联网的裁员潮,我们该何去何从?
微服务实战|集中配置中心Config非对称加密与安全管理
Introduction to several methods of making custom welcome interface on Weiluntong touch screen
文本处理之xml
图斑自上而下,自左而右顺序编码,按照权属单位代码分组,每组从1开始编码
【Postman】下载与安装(新手图文教程)
力扣之顺序表
Principles Of Mathematical Analysis, Third Edition免费下载地址
函数柯里化
求教一下 现在最新版的flinkcdc能获取到oracle的ddl变更信息吗?
Map遍历 key-value 的4种方法
【表达式计算】表达式计算问题的通用解法(练习加强版,含总结)
WOLFLAB一方老师为什么要写网络虚拟化《VMware NSX-T卷2》路由架构-2
基于SSM实现在线聊天系统
uni 的下拉式筛选菜单的功能/图片懒加载
全球级的分布式数据库 Google Spanner原理 热:报错
2022开放原子全球开源峰会数据库分论坛圆满召开