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的自动配置机制,实现切点的动态可配置
配置类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(支持切点可配置)最好抽到公共服务上,这样公司内部的其他服务就可以直接通过二方包引用,使用非常方便