一文学会AOP动态切面开发


一文学会AOP动态切面开发

文章插图
面向切面编程
1.传统切面开发
通过Spring AOP我们可以很便捷的进行面向切面编程 , 比如统一日志处理、权限处理等等 , 常见开发范式如下:
package com.xxx.service;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration@EnableAspectJAutoProxy@Aspect@Slf4jpublic class DemoAspectConfig {@Pointcut(value = https://www.isolves.com/it/cxkf/bk/2022-09-30/"execution(* com.xxx.service.controller..*(..))")public void pointcut() {}@Around("pointcut()")public Object advice(ProceedingJoinPoint pjp) {try {// do something...Object result = pjp.proceed();return result;} catch (Throwable throwable) {// do something...return null;} finally {// do something}}}2.动态切面的AOP
传统的AOP开发 , 切点表达式是直接硬编码的 , 也可以应对大多数的业务场景 , 但是很明显 , 是缺少灵活性的 。如果切点要想做到可扩展 , 那么就需要借助所谓的动态AOP , 即支持切点的可配置 。下面的code通过SpringBoot的自动配置机制 , 实现切点的动态可配置 。原理如下:
通过JAVA config , 借助ImportAware, BeanFactoryAware在容器启动过程中获取项目中通过注解EnableAlarmNotify(可以用在主类或Java Config的类上, 详见下文)指定的需要告警的包 , 构造出最终的告警切面的切点表达式, 达到灵活扩展的目的
动态切面配置类
DynamicAspectAutoConfiguration:
package com.xxx.service;import lombok.extern.slf4j.Slf4j;import org.aopalliance.aop.Advice;import org.aopalliance.intercept.MethodInterceptor;import org.Apache.commons.lang.StringUtils;import org.springframework.aop.Advisor;import org.springframework.aop.Pointcut;import org.springframework.aop.aspectj.AspectJExpressionPointcut;import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.BeanFactoryAware;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.ImportAware;import org.springframework.core.annotation.AnnotationAttributes;import org.springframework.core.annotation.AnnotationUtils;import org.springframework.core.type.AnnotationMetadata;import java.lang.reflect.Method;import java.util.Arrays;import java.util.stream.Collectors;/** * 通过Java config , 借助ImportAware, BeanFactoryAware在容器启动过程中获取项目中 * 通过注解EnableAlarmNotify(可以用在主类或Java Config的类上)指定的需要告警的包 ,  * 构造出最终的告警切面的切点表达式, * 达到灵活扩展的目的 */@Configuration@EnableAlarmNotify@Slf4jpublic class DynamicAspectAutoConfiguration implements ImportAware, BeanFactoryAware {private BeanFactory beanFactory;/*** 切点表达式*/private String expressionPointCut = "(@within(org.springframework.web.bind.annotation.RestController) " +"|| @within(org.springframework.stereotype.Controller))";/*** 通过Import 获取注解元数据*/@Overridepublic void setImportMetadata(AnnotationMetadata importMetadata) {AnnotationAttributes attributes = AnnotationAttributes.fromMap(importMetadata.getAnnotationAttributes(EnableAlarmNotify.class.getName()));if (attributes != null) {String[] basePackages = attributes.getStringArray("basePackages");if (basePackages != null && basePackages.length > 0) {String givenPackage = Arrays.stream(basePackages).filter(StringUtils::isNotBlank).map(basePackage -> "within(" + getPackageName(basePackage) + "..*)").collect(Collectors.joining(" || ", "(", ")"));//设置最终的切点表达式this.expressionPointCut = this.expressionPointCut + " && " + givenPackage;log.info("告警通知的切点表达式为: {}", this.expressionPointCut);}}}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {//通过接口注入beanFactory容器this.beanFactory = beanFactory;}private String getPackageName(String basePackage) {if (StringUtils.isEmpty(basePackage)) {return basePackage;}while (basePackage.endsWith(".")) {basePackage = basePackage.substring(0, basePackage.length() - 1);}return basePackage;}@Bean("alarmNotifyPointcut")@ConditionalOnClass(Pointcut.class)public Pointcut alarmNotifyPointcut() {AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expressionPointCut);pointcut.setBeanFactory(beanFactory);return pointcut;}@Bean("alarmNotifyAdvice")@ConditionalOnClass(Advice.class)public MethodInterceptor alarmNotifyAdvice() {return invocation -> {final Method method = invocation.getMethod();NotSendAlarmNotify notSendAlarmNotify = Util.getDefaultIfNull(AnnotationUtils.findAnnotation(method, NotSendAlarmNotify.class),AnnotationUtils.findAnnotation(method.getDeclaringClass(), NotSendAlarmNotify.class));if (notSendAlarmNotify != null) {//指定不告警 直接执行目标方法return invocation.proceed();}String className = method.getDeclaringClass().getName();String methodName = method.getName();Object[] args = invocation.getArguments();if (args != null && args.length > 0) {// 保留原始请求参数args = Arrays.copyOf(args, args.length);}try {Object result = invocation.proceed();//可以自定义相关处理逻辑return result;} catch (Throwable throwable) {//发送告警通知SendAlarmNotifyUtil.sendAlarm(className, methodName, args);throw throwable;}};}@Bean("alarmNotifyAdvisor")@ConditionalOnClass(Advisor.class)public DefaultBeanFactoryPointcutAdvisor alarmNotifyAdvisor() {DefaultBeanFactoryPointcutAdvisor advisor = new DefaultBeanFactoryPointcutAdvisor();advisor.setPointcut(alarmNotifyPointcut());advisor.setAdvice(alarmNotifyAdvice());advisor.setBeanFactory(beanFactory);return advisor;}static final class Util {public static <T> T getDefaultIfNull(T object, T defaultValue) {return object != null ? object : defaultValue;}}}


推荐阅读