文章插图
面向切面编程
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;}}}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 夏季如何预防湿疹,学会四个好方法
- 创业|创业者和自由职业者,为什么要学会干苦力,而不是高大上?
- 工控人要学会的专业软件都有这些 常用上位机软件有哪些
- 3分钟教你学会看手相 手相的看法
- |这4个诀窍建议收藏,学会了职场永远快人一步!
- 怎么样才能学会做生意 如何学会做生意
- 小红书|减肥除了“管住嘴、迈开腿”,还有第三种选择?一文给你科普分析
- 西装|“职场穿搭教科书”来了!秋冬学会这样穿西装,时髦又不失气场
- 学会洗脸祛斑了吗?
- 我学会了骑自行车200字?我学会了骑自行车四年级400字