最近有一个新的功能要开发:记录http请求的耗时。
我心想:ok,这个需求很简单啊,不就是上下都记录下时间就完事了吗
伪代码
long startTimeMillis = System.currentTimeMillis();
httpService.request(); // 核心代码
long endTimeMillis = System.currentTimeMillis();
long spendMillis = endTimeMillis - startTimeMillis; // 耗时
提交代码,搞定一个任务
随着时间发展,有越来越多的系统要对接,每个接口的http请求都要写一撮low low的代码,自己看了怪恶心的
于是我另起炉灶,把埋点的方式抛弃掉,使用spring切面的方式去实现,切面
外行人听着可能觉得挺高级的,咱来弱化一下对他的理解。
切面,可以方法执行前or执行后做一些特殊处理。
这样就好办了,我只需要在切面-前置
记录开始时间,切面-后置
记录结束时间,然后两者相减就完成需求了。
举个例子,定义一下http请求的原方法
@Service
public class HttpService{
public void request(){
//核心代码...发起http请求
}
}
定义一个HttpServiceAspect切面类
@Aspect
@Component
public class HttpServiceAspect {
/**
* 声明AOP签名
* 这里我指定HttpService.request执行时,会被我捕获到
*/
@Pointcut("execution(* com.whale.data.service.impl.HttpService.request(..))")
public void pointcut() {
}
/**
* 环绕切入
*
* @param joinPoint 切面对象
* @return 底层方法执行后的返回值
* @throws Throwable 底层方法抛出的异常
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTimeMillis = System.currentTimeMillis();
proceed = joinPoint.proceed(); // 执行原方法
long endTimeMillis = System.currentTimeMillis();
long spendMillis = endTimeMillis - startTimeMillis; // 耗时
return proceed;
}
搞定!任务调用到com.whale.data.service.impl.HttpService.request()的方法都会被我的切面拦截,这样我就不需要在每个系统交互上下去埋点了。(可以开心一会)
快乐的日子总是短暂的,成年人的世界里总是需要经历一定的坎坷。这不,新的问题又来了。
假如开发人员新增了一个接口httpService.post(),那上述方式切面的方式就不够用了,我还得在Pointcut里指定新的切点
如:
@Pointcut("execution(* com.whale.data.service.impl.HttpService.request(..)) || execution(* com.whale.data.service.impl.HttpService.post(..)) ")
public void pointcut() {
}
切面也长大了,要学会自己去管理切点才行呀!
如果接口耗时功能的入口不在切面,而在方法上,那开发人员就可以根据自己的需要,自己选择是否启用了,这样的动态配置,谁不喜欢呢?
于是乎,我们又进行了稍许改造,引入了自定义注解
。
跟之前切面的案例一样,我们也进行一次降维打击哈
ok,看完上图大家对注解应该有个初步的认知,通过注解去实现切面的开关
同样我们上一段代码来看看程序是如何实现的。
定义自定义注解HttpLog
@Target(ElementType.METHOD) // 用于方法上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface HttpLog {
}
改造切面HttpServiceAspect的切点,使其切入注解
@Aspect
@Component
public class HttpServiceAspect {
/**
* 声明AOP签名
* 这里我指定HttpService.request执行时,会被我捕获到
*/
@Pointcut("@annotation(com.whale.data.annotate.HttpLog)")
public void pointcut() {
}
/**
* 环绕切入
*
* @param joinPoint 切面对象
* @return 底层方法执行后的返回值
* @throws Throwable 底层方法抛出的异常
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTimeMillis = System.currentTimeMillis();
proceed = joinPoint.proceed(); // 执行原方法
long endTimeMillis = System.currentTimeMillis();
long spendMillis = endTimeMillis - startTimeMillis; // 耗时
return proceed;
}
以上的满足就已经完成自定义注解了。 接下来看看注解怎么使用
只需要在方法上加入注解@HttpLog即可
@Service
public class HttpService{
@HttpLog
public void request(){
//核心代码...发起http请求
}
}
举一反三,如果开发新增了post方法也需要统计耗时,那么在post方法上也加上对应的注解即可
@Service
public class HttpService{
@HttpLog
public void request(){
//核心代码...发起http请求
}
@HttpLog
public void post(){
//核心代码...发起http请求
}
public void get(){
//核心代码...发起http请求。不使用注解,不会被切面拦截
}
}
通过上面描述的,大家应该可以知道切面的由来,以及切面和注解的关系了。
切面:无需侵入业务代码,在外层进行特殊的逻辑处理,常用于日志,监控记录。
注解:切面的高级用法