在实际开发中我们经常需要打印日志或者监控方法执行,使用 spring ao
能够很好的实现这个功能,但是切面中的 Pointcut
是固定值,不可被更改,就导致多个应用程序使用功能相同的 aop
需要有大量重复代码,我们希望将这些功能相同的部分封装并抽离为一个公共组件包,能够动态配置 Pointcut
。
实现思路
从Spring的AOP机制已知,要对一个方法或类切入需要实现以下步骤
-
Pointcut
,实现spring
中的Pointcut
接口自定已切入点,根据具体的业务实现本文章示例使用
spring
中的AspectJExpressionPointcut
实现 -
Advisor
,它可以扩展Spring
中AbstractBeanFactoryPointcutAdvisor
-
MethodInterceptor
, 在此接口中实现拦截逻辑 -
实例化注入到
spring
容器
具体实践
DynamicPointcutAdvisor 动态切入点
public class DynamicPointcutAdvisor extends AbstractBeanFactoryPointcutAdvisor {
/**
* 表达式
*/
private String expression;
public DynamicPointcutAdvisor(String expression) {
this.expression = expression;
}
public Pointcut aspectJExpressionPointcut() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(this.expression);
return pointcut;
}
@Override
public Pointcut getPointcut() {
return aspectJExpressionPointcut();
}
}
复制代码
DynamicMethodInterceptor 动态方法拦截
@Slf4j public class DynamicMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { long startTime = System.currentTimeMillis(); Object ret; JSONObject rest = new JSONObject(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); try { UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent")); rest.put("RequestUrl", request.getRequestURL().toString()); rest.put("RequestType", request.getMethod()); rest.put("RequestHeader", RequestUtils.getHeadersInfo(request)); rest.put("RequestMethod", invocation.getMethod().getName()); rest.put("RequestParams", invocation.getArguments()); rest.put("ServerAddr", StrFormatter.format("{}{}{}{}{}", request.getScheme(), "://", request.getServerName(), ":", request.getServerPort())); rest.put("RemoteAddr", RequestUtils.getRemoteAddr(request)); rest.put("Browser", userAgent.getBrowser().toString()); rest.put("BrowserVersion", userAgent.getBrowserVersion()); rest.put("operatingSystem", userAgent.getOperatingSystem().toString()); rest.put("requestTime", LocalDateTime.now()); ret = invocation.proceed(); rest.put("responseResult", ret); } catch (Throwable throwable) { rest.put("ExceptionName", throwable.getClass().getName()); rest.put("ExceptionMessage", throwable.getMessage()); JSONObject e = new JSONObject(); e.put("ExceptionName", throwable.getClass().getName()); e.put("ExceptionMessage", throwable.getMessage()); log.error(e.toString()); throw throwable; } finally { rest.put("Time", System.currentTimeMillis() - startTime); log.info(rest.toString()); } return ret; } } 复制代码
DynamicAdvisorConfiguration 实例化
@Configuration @ConditionalOnProperty(name = "log.dynamic.pointcut.expression") public class DynamicAdvisorConfiguration { @Value("${log.dynamic.pointcut.expression:}") private String expression; @Bean(name = "dynamicPointcutAdvisor") @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public DynamicPointcutAdvisor adapterServiceAdvisor() { DynamicPointcutAdvisor advisor = new DynamicPointcutAdvisor(expression); advisor.setAdviceBeanName("dynamicPointcutAdvisor"); advisor.setAdvice(new DynamicMethodInterceptor()); advisor.setOrder(Ordered.HIGHEST_PRECEDENCE); return advisor; } } 复制代码
配置文件 application.properties
log.dynamic.pointcut.expression=execution(public * com.devin.dynamic.controller..*.*(..)) log.dynamic.pointcut.expression=true 复制代码
结果
[nio-9000-exec-2] c.s.s.a.d.DynamicMethodInterceptor : {"responseResult":{"code":200,"message":"16003908714631539353"},"RequestMethod":"dynamicNumber","RequestUrl":"http://localhost:9000/dynamic/number","ServerAddr":"http://localhost:9000","Time":52,"RemoteAddr":"0:0:0:0:0:0:0:1","operatingSystem":"WINDOWS_10","RequestParams":[],"requestTime":"2020-03-25T11:26:10.344","RequestHeader":{"accept-language":"zh-CN,en-US;q=0.7,en;q=0.3","host":"localhost:9000","upgrade-insecure-requests":"1","connection":"keep-alive","accept-encoding":"gzip, deflate","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"},"BrowserVersion":{"majorVersion":"74","minorVersion":"0","version":"74.0"},"RequestType":"GET","Browser":"FIREFOX7"} 复制代码
拓展 Pointcut
spring
默认有一些切入点的实现,我们只需要实例化就可以了

但是对于不满足我们需求的可以通过实现 Pointcut
接口实现。
package org.springframework.aop; public interface Pointcut { Pointcut TRUE = TruePointcut.INSTANCE; ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); } 复制代码
其他的请自行探索。
原文
https://juejin.im/post/5e7ae01a5188255df86cce8e
本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 编程式动态AOP实践