AOP是Spring体系中非常重要的两个概念之今天这篇文章就来带大家通过实战的方式,在编程猫SpringBoot项目中使用AOP技术为controller层添加一个切面来实现接口访问的统一日志记录。
关于AOP
AOP,也就是Aspect-orientedProgramming,译为面向切面编程,是计算机科学中的一个设计思想,旨在通过切面技术为业务主体增加额外的通知,从而对声明为“切点”的代码块进行统一管理和装饰。
这种思想非常适用于,将那些与核心业务不那么密切关联的功能添加到程序中,就好比我们今天的主题——日志功能,就是一个典型的案例。
AOP是对面向对象编程的一种补充,OOP的核心单元是类,而AOP的核心单元是切面。利用AOP可以对业务逻辑的各个部分进行隔离,从而降低耦合度,提高程序的可重用性,同时也提高了开发效率。
我们可以简单的把AOP理解为贯穿于方法之中,在方法执行前、执行时、执行后、返回值后、异常后要执行的操作。
AOP的相关术语
来看下面这幅,这是一个AOP的模型,就是在某些方法执行前后执行一些通用的操作,并且这些操作不会影响程序本身的运行。
我们了解下AOP涉及到的5个关键术语:
横切关注点,从每个方法中抽取出来的同一类非核心业务
切面,对横切关注点进行封装的类,每个关注点体现为一个通知方法;通常使用@Aspect注解来定义切面。
通知,切面必须要完成的各个具体工作,比如我们的日志切面需要记录接口调用前后的时长,就需要在调用接口前后记录时间,再取差值。通知的方式有五种:
@Before:通知方法会在目标方法调用之前执行@After:通知方法会在目标方法调用后执行@AfterReturning:通知方法会在目标方法返回后执行@AfterThrowing:通知方法会在目标方法抛出异常后执行@Around:把整个目标方法包裹起来,在被调用前和调用之后分别执行通知方法
连接点,通知应用的时机,比如接口方法被调用时就是日志切面的连接点。
切点,通知功能被应用的范围,比如本篇日志切面的应用范围是所有controller的接口。通常使用@Pointcut注解来定义切点表达式。
切入点表达式的语法格式规范如下所示:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern)
throws-pattern?)
modifiers-pattern?为访问权限修饰符ret-type-pattern为返回类型,通常用*来表示任意返回类型declaring-type-pattern?为包名name-pattern为方法名,可以使用*来表示所有,或者set*来表示所有以set开头的类名param-patter为参数类型,多个参数可以用,隔开,各个参与也可以使用*来表示所有类型的参数,还可以使用(..)表示零个或者任意参数throws-pattern?为异常类型?表示前面的为可选项
举个例子:
表示cocodingmorcontroller包下的所有public方法都要应用切面的通知。
实操AOP记录接口访问日志
第一步,在SpringBoot项目的poxml文件中添加spring-boot-starter-aop依赖。
org.springframework.boot
spring-boot-starter-aop
第二步,添加日志信息封装类WebLog,用于记录什么样的操作、操作的人是谁、开始时间、花费的时间、操作的路径、操作的方法名、操作主机的IP、请求参数、返回结果等。
/**
* Controller层的日志封装类
* Created by macro on 2018/4/26.
*/
public class WebLog {
private String description;
private String username;
private Long startTime;
private Integer spendTime;
private String basePath;
private String uri;
private String url;
private String method;
private String ip;
private Object parameter;
private Object result;
//省略了getter,setter方法
}
第三步,添加统一日志处理切面WebLogAspect。
/**
* 统一日志处理切面
* Created by 石磊
*/
@Aspect
@Component
@Order(1)
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut('execution(public * com.codingmore.controller.*.*(..))')
public void webLog() {
}
@Before('webLog()')
public void doBefore(JoinPoint joinPoint) throws Throwable {
}
@AfterReturning(value = 'webLog()', returning = 'ret')
public void doAfterReturning(Object ret) throws Throwable {
}
@Around('webLog()')
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录请求信息(通过Logstash传入Elasticsearch)
WebLog webLog = new WebLog();
Object result = joinPoint.proceed();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation log = method.getAnnotation(ApiOperation.class);
webLog.setDescription(log.value());
}
long endTime = System.currentTimeMillis();
String urlStr = request.getRequestURL().toString();
webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
webLog.setIp(request.getRemoteUser());
Map logMap = new HashMap<>();
logMap.put('spendTime',webLog.getSpendTime());
logMap.put('description',webLog.getDescription());
LOGGER.info('{}', JSONUtil.parse(webLog));
return result;
}
}
第四步,运行项目,并对controller下的某个控制器进行测试。
执行登录用户查询操作:可以在控制台可以看到以下日志信息:
源码地址:
作者cxuan:https://www.cnblogs.com/cxuanBlog/p/13060510.html灰小猿:https://bbs.huaweicloucom/blogs/289045山高我为峰:https://www.cnblogs.com/liaojie970/p/788368htmlmacrozheng:https://githucom/macrozheng/mall
本篇已收录至GitHub上星标6k+star的开源专栏《Java程序员进阶之路》,据说每一个优秀的Java程序员都喜欢她,风趣幽默、通俗易懂。内容包括Java基础、Java并发编程、Java虚拟机、Java企业级开发、Java面试等核心知识点。学Java,就认准Java程序员进阶之路????。
star了这个仓库就等于你拥有了成为了一名优秀Java工程师的潜力。也可以戳下面的链接跳转到《Java程序员进阶之路》的官网网址,开始愉快的学习之旅吧。
没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。
文章为作者独立观点,不代表股票交易接口观点