编程式动态AOP实践

编程式动态AOP实践

技术杂谈小彩虹2021-08-01 9:39:27200A+A-

背景

在实际开发中我们经常需要打印日志或者监控方法执行,使用spring ao能够很好的实现这个功能,但是切面中的Pointcut是固定值,不可被更改,就导致多个应用程序使用功能相同的aop需要有大量重复代码,我们希望将这些功能相同的部分封装并抽离为一个公共组件包,能够动态配置Pointcut

实现思路

从Spring的AOP机制已知,要对一个方法或类切入需要实现以下步骤

  • Pointcut,实现spring 中的Pointcut接口自定已切入点,根据具体的业务实现

    本文章示例使用spring中的 AspectJExpressionPointcut实现

  • Advisor,它可以扩展SpringAbstractBeanFactoryPointcutAdvisor

  • 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默认有一些切入点的实现,我们只需要实例化就可以了

8X2inA.png

但是对于不满足我们需求的可以通过实现Pointcut接口实现。

package org.springframework.aop;

public interface Pointcut {
    Pointcut TRUE = TruePointcut.INSTANCE;
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

其他的请自行探索。

参考文章

[Spring AOP - Dynamic Pointcut](

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们