昨天介绍了自定义校验器注解的使用,后续自己可以再添加一个统一异常处理类,一套参数校验框架就出炉了,说道异常处理类,就要使用到spirng的aop
给介绍一下自定义注解 + spring boot切面的使用
1. 声明一个自定义注解(一个比较简单的demo)
这注解是我用来标注哪些方法需要参与spring mybatis 的数据库分表,这个会在下一篇介绍
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SplitParam {
/**
*
* 使用参数列表的索引
* @return
*/
int index() default 0;
}
同样关于java的自带的注解
@Retention
、@Target
就不过多介绍了,具体的枚举类型使用可以参见javadoc
2. 定义一个切面处理自定义注解SplitParamAspect
我这里使用的是
spring boot
配置注解,让spring
扫描到就ok了
3. 切面的定义
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
@Component
@Aspect
@Slf4j
public class SplitParamAspect
@Order: 这个注解定义了你的自定义注解被处理的顺序,value指定了优先级(可选的),value的值越小被处理的优先级越高,相反则越低,这个值因人而异,如果项目中自定义注解比较多的化建议加上
/**
* 最高优先级
* @see java.lang.Integer#MIN_VALUE
*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
/**
* 最低优先级
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
@Component 这个在 spring boot中经常被用到,看注释就能看懂意思了:表示标有此注释的类是一个
组件
,并且呢,如果使用在spring 的配置上(也就是注解@Configuration),后者这个注解类在@ComponentScan
的扫描路径下的化,此类会被自动扫描auto-detection
@Aspect 这个是切面的核心注解,声明一个切面
4. 切面的使用
上面介绍了切面编程的首要工作,可能你现在对整个逻辑还云里雾里,别慌,下面马上介绍:
举个例子:一个比较简单的场景
一个类中有一个的方法,在执行此方法前我想获取到方法的参数值并放到ThreadLocal中,执行完毕在后从ThreaLocal中删除。
为了高内聚低耦合,业务的方法应该单独封装在一个类里面,参数放入和参数删除的功能应该另外封装,但是在调用时必须按照顺序来调用执行,这时候就需要用到切面编程了
。
或者统计一个项目中这个方法被调用的次数;
或者在方法的执行前后打印时间统计方法的执行时间等等;
- [x] 切入点(Pointcut):在你方法上使用切点,表明这是一个需要处理的点。这里我们没有直接在方法使用
@PointCut(value=切点表达式)
来直接表明这是一个切点,而是在方法上使用了自定义注解@SplitParam
,然后使用@PointCut(@SplitParam路径)
标明含有此注解的方法全部作为切点
- [x] 通知、增强处理(Advice):就是上述你想要把参数放入到ThreadLoal、执行完毕后从ThreadLocl中移除的功能;
- [x] 连接点(JoinPoint): Spring允许你是
Advice
的地方,基本方法前、方法后、或者是(Around方法)、抛出异常都可以是连接点
- [x] 切面(Aspect):切面是你处理功能点,全部实现的总称
5. 具体实现之一码便知
import com.google.common.collect.Maps;
import com.huatu.common.exception.BizException;
import com.huatu.tiku.push.annotation.SplitParam;
import com.huatu.tiku.push.constant.NoticePushErrors;
import com.huatu.tiku.push.dao.strategy.Strategy;
import com.huatu.tiku.push.util.ConsoleContext;
import com.huatu.tiku.push.util.ThreadLocalManager;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.NumberUtils;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 描述:参数处理切面
*
* @author biguodong
* Create time 2018-12-04 下午8:37
**/
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
@Component
@Aspect
@Slf4j
public class SplitParamAspect {
@Pointcut(value = "@annotation(com.yourpath.SplitParam)")
public void pointCut(){
}
/**
* joinPoint 一种定义方式-直接使用注解简单粗暴
* 代码逻辑因人而异
* 放入本地线程中
* @param joinPoint
* @param splitParam
*/
@Before("@annotation(splitParam)")
public void before(JoinPoint joinPoint, SplitParam splitParam){
Object[] argsObject = joinPoint.getArgs();
if(argsObject.length == 0){
throw new BizException(NoticePushErrors.TABLE_SPLIT_PARAMS_EMPTY);
}
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
try{
Object value = argsObject[splitParam.index()];
if(!(value instanceof Number)){
throw new BizException(NoticePushErrors.TABLE_SPLIT_PARAMS_TYPE_ERROR);
}
Long valueL = NumberUtils.convertNumberToTargetClass((Number) value, Long.class);
ConsoleContext consoleContext = ConsoleContext.getInstance();
Map<String, Object> params = Maps.newHashMap();
params.put(Strategy.USER_ID, valueL);
consoleContext.setRequestHeader(params);
ThreadLocalManager.setConsoleContext(consoleContext);
}catch (Exception e){
log.error("pars split params value error, method:{}", method.getName());
}
}
/**
* joinPoint的另外一中实现方式
* 切面执行完后移除
*/
@AfterReturning("pointCut()")
public void after(){
ThreadLocalManager.clear();
}
@AfterThrowing(value = "pointCut()", throwing = "throwable")
public void afterException(Throwable throwable){
if(null != throwable && null != throwable.getCause()){
log.error(" run aspect caught an error:{}", throwable.getCause().getMessage());
}
throw new BizException(NoticePushErrors.TABLE_SPLIT_PARAMS_AOP_ERROR);
}
}
上述可以看到我有两种方式实现了连接点
@JointPoint
;
在Before
上直接用的注解形式;
在AfterReturning
上则是用的自定义的PointCut()
方法,这个方上使用了@PointCut()
来声明切点表达式,效果都是一样的;
马上用起来吧!