1.傳統(tǒng)切面開(kāi)發(fā)
通過(guò)Spring AOP我們可以很便捷的進(jìn)行面向切面編程蚪缀,比如統(tǒng)一日志處理唱歧、權(quán)限處理等等亏栈,常見(jiàn)開(kāi)發(fā)范式如下:
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.動(dòng)態(tài)切面的AOP
傳統(tǒng)的AOP開(kāi)發(fā)溉浙,切點(diǎn)表達(dá)式是直接硬編碼的芋忿,也可以應(yīng)對(duì)大多數(shù)的業(yè)務(wù)場(chǎng)景炸客,但是很明顯,是缺少靈活性的戈钢。如果切點(diǎn)要想做到可擴(kuò)展痹仙,那么就需要借助所謂的動(dòng)態(tài)AOP,即支持切點(diǎn)的可配置殉了。下面的code通過(guò)SpringBoot的自動(dòng)配置機(jī)制开仰,實(shí)現(xiàn)切點(diǎn)的動(dòng)態(tài)可配置
配置類(lèi)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;
/**
* 通過(guò)Java config,借助ImportAware, BeanFactoryAware在容器啟動(dòng)過(guò)程中獲取項(xiàng)目中
* 通過(guò)注解EnableAlarmNotify(可以用在主類(lèi)或Java Config的類(lèi)上)指定的需要告警的包,
* 構(gòu)造出最終的告警切面的切點(diǎn)表達(dá)式,
* 達(dá)到靈活擴(kuò)展的目的
*/
@Configuration
@EnableAlarmNotify
@Slf4j
public class DynamicAspectAutoConfiguration implements ImportAware, BeanFactoryAware {
private BeanFactory beanFactory;
/**
* 切點(diǎn)表達(dá)式
*/
private String expressionPointCut = "(@within(org.springframework.web.bind.annotation.RestController) " +
"|| @within(org.springframework.stereotype.Controller))";
/**
* 通過(guò)Import 獲取注解元數(shù)據(jù)
*/
@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(" || ", "(", ")"));
//設(shè)置最終的切點(diǎn)表達(dá)式
this.expressionPointCut = this.expressionPointCut + " && " + givenPackage;
log.info("告警通知的切點(diǎn)表達(dá)式為: {}", this.expressionPointCut);
}
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
//通過(guò)接口注入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) {
//指定不告警 直接執(zhí)行目標(biāo)方法
return invocation.proceed();
}
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
Object[] args = invocation.getArguments();
if (args != null && args.length > 0) {
// 保留原始請(qǐng)求參數(shù)
args = Arrays.copyOf(args, args.length);
}
try {
Object result = invocation.proceed();
//可以自定義相關(guān)處理邏輯
return result;
} catch (Throwable throwable) {
//發(fā)送告警通知
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 {
}
說(shuō)明:動(dòng)態(tài)AOP(支持切點(diǎn)可配置)最好抽到公共服務(wù)上众弓,這樣公司內(nèi)部的其他服務(wù)就可以直接通過(guò)二方包引用恩溅,使用非常方便