前言
我们通常可以用aop做一些公共的问题,比如:权限管理,事务委托,验证,日志管理。
通知
环绕通知和前置通知,后置通知
前置和后置通知是在方法的调用前后执行通知,目标方法一定会执行的,然而环绕通知是可以决定方法是否可以执行的。
环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用
参数验证aop Demo
@Aspect
@Component
public class HibernateValidatorAspect implements Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(HibernateValidatorAspect.class);
private final int order;
private final RestStatus throwIfInvalidModel;
public HibernateValidatorAspect() {
this(Byte.MAX_VALUE);
}
public HibernateValidatorAspect(int order) {
this(order, DefaultInvalidModelStatus.INVALID_MODEL_STATUS);
}
public HibernateValidatorAspect(int order, RestStatus throwIfInvalidModel) {
this.order = order;
this.throwIfInvalidModel = throwIfInvalidModel;
}
@Around(value = "within(com.spring..*)&&(@annotation(org.springframework.web.bind.annotation.PostMapping)|| @annotation(org.springframework.web.bind.annotation.RequestMapping))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof BindingResult) {
throwIfInvalidModel((BindingResult) arg, throwIfInvalidModel);
}
}
return joinPoint.proceed();
}
/**
* 校验实体合法性, 自动向Map封装错误信息.
*
* @param result Spring MVC中与@Valid成对出现的BindingResult, 用于绑定错误信息
* @throws IllegalValidateException 实体校验失败异常
* @see org.springframework.web.bind.annotation.ControllerAdvice
*/
public static void throwIfInvalidModel(BindingResult result, RestStatus errorStatus) {
Preconditions.checkNotNull(result);
// 默认为true, 检测到错误时赋值为false
boolean isValid = true;
final HashMap<Object, Object> errorMap = Maps.newHashMap();
if (result.getErrorCount() > 0) {
isValid = false;
String errorFieldName;
for (FieldError fieldError : result.getFieldErrors()) {
errorFieldName = acquireFieldName(result, fieldError);
final String errorMessage = fieldError.getDefaultMessage();
LOGGER.debug("request id: {}, error field: {}, error msg: {}",
ServletContextHolder.fetchRequestId(), errorFieldName, errorMessage);
errorMap.put(errorFieldName, errorMessage);
}
}
if (!isValid) {
final ErrorEntity entity = new ErrorEntity(errorStatus, errorMap);
// 以entity中的code为key存入Request中
final String errorCode = String.valueOf(errorStatus.code());
bindStatusCodesInRequestScope(errorCode, entity);
throw new IllegalValidateException(errorCode);
}
}
/**
* 获取错误的字段名, 如果被{@link JsonProperty}修饰则优先选择
*
* @see JsonProperty
*/
private static String acquireFieldName(BindingResult result, FieldError fieldError) {
Preconditions.checkNotNull(result);
Preconditions.checkNotNull(fieldError);
// 获取错误字段名
String errorFieldName = fieldError.getField();
// 获取校验非法的类
Class<?> clazz = result.getTarget().getClass();
final Field field;
try {
// 获取其字段名
field = clazz.getDeclaredField(fieldError.getField());
final JsonProperty annotation = field.getAnnotation(JsonProperty.class);
// 若JsonProperty里value()不为null则覆盖该值
if (annotation != null) {
errorFieldName = annotation.value();
}
} catch (NoSuchFieldException e) {
LOGGER.error("request id: {}, 反射字段名时抛出异常: {}", ServletContextHolder.fetchRequestId(), e.getMessage());
}
return errorFieldName;
}
private static void bindStatusCodesInRequestScope(String key, ErrorEntity entity) {
Preconditions.checkNotNull(entity);
Preconditions.checkNotNull(key);
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
((ServletRequestAttributes) requestAttributes).getRequest().setAttribute(key, entity);
}
}
@Override
public int getOrder() {
return order;
}
private enum DefaultInvalidModelStatus implements RestStatus {
INVALID_MODEL_STATUS(40001, "invalid request model");
DefaultInvalidModelStatus(int code, String messge) {
this.code = code;
this.message = messge;
}
private final int code;
private final String message;
@Override
public int code() {
return code;
}
@Override
public String message() {
return message;
}
}
}
请注意上面的切面类实现了 Ordered 接口,这样我们就可以把切面的优先级设定为高于事务通知(我们每次重试的时候都想要在一个新的事务中进行)。 maxRetries 和 order 属性都在Spring中配置。
如何配置 @around @before @after 里面的切面点可以参考
spring aop
within 和 annotation
表示切入点在 包com.spring 下的所有方法
within(com.spring..*)
表示切入点在 有注解service 所有的类
within(@org.springframework.stereotype.Service *)
等于
@within(org.springframework.stereotype.Service)
表示有注解PostMapping的所有方法
@annotation(org.springframework.web.bind.annotation.PostMapping)
表示切入点在包com.spring下所有的方法,并且有PostMapping或RequestMapping注解的
@Around(value = "within(com.spring..*)&&(@annotation(org.springframework.web.bind.annotation.PostMapping)|| @annotation(org.springframework.web.bind.annotation.RequestMapping))")