快捷搜索:  汽车  科技

什么是aop切面编程(一文学会AOP动态切面开发)

什么是aop切面编程(一文学会AOP动态切面开发)package com.xxx.service; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(DynamicAspectAutoConfiguration.class) public @interface EnableAlarmNotify { String[] basePackages() default {"com.xxx"}; } 注解NotSendAlarmNotifypackage com.xxx.service; import lombok.extern.sl

什么是aop切面编程(一文学会AOP动态切面开发)(1)

面向切面编程

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 @Slf4j public class DemoAspectConfig { @Pointcut(value = "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 @Slf4j public class DynamicAspectAutoConfiguration implements ImportAware BeanFactoryAware { private BeanFactory beanFactory; /** * 切点表达式 */ private String expressionPointCut = "(@within(org.springframework.web.bind.annotation.RestController) " "|| @within(org.springframework.stereotype.Controller))"; /** * 通过Import 获取注解元数据 */ @Override public 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); } } } @Override public 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; } } }

注解EnableAlarmNotify:

package com.xxx.service; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(DynamicAspectAutoConfiguration.class) public @interface EnableAlarmNotify { String[] basePackages() default {"com.xxx"}; }

注解NotSendAlarmNotify

package com.xxx.service; import java.lang.annotation.*; @Target({ElementType.TYPE ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NotSendAlarmNotify { }

说明:动态AOP(支持切点可配置)最好抽取到公共服务上,这样公司内部的其他服务就可以直接通过二方包引用,使用非常方便。以上内容希望对你有所帮助~~

猜您喜欢: