昨天介紹了自定義校驗(yàn)器注解的使用,后續(xù)自己可以再添加一個(gè)統(tǒng)一異常處理類,一套參數(shù)校驗(yàn)框架就出爐了,說道異常處理類,就要使用到spirng的aop
給介紹一下自定義注解 + spring boot切面的使用
1. 聲明一個(gè)自定義注解(一個(gè)比較簡單的demo)
這注解是我用來標(biāo)注哪些方法需要參與spring mybatis 的數(shù)據(jù)庫分表,這個(gè)會(huì)在下一篇介紹
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SplitParam {
/**
*
* 使用參數(shù)列表的索引
* @return
*/
int index() default 0;
}
同樣關(guān)于java的自帶的注解
@Retention、@Target就不過多介紹了,具體的枚舉類型使用可以參見javadoc
2. 定義一個(gè)切面處理自定義注解SplitParamAspect
我這里使用的是
spring boot配置注解,讓spring掃描到就ok了
3. 切面的定義
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
@Component
@Aspect
@Slf4j
public class SplitParamAspect
@Order: 這個(gè)注解定義了你的自定義注解被處理的順序,value指定了優(yōu)先級(jí)(可選的),value的值越小被處理的優(yōu)先級(jí)越高,相反則越低,這個(gè)值因人而異,如果項(xiàng)目中自定義注解比較多的化建議加上
/**
* 最高優(yōu)先級(jí)
* @see java.lang.Integer#MIN_VALUE
*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
/**
* 最低優(yōu)先級(jí)
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
@Component 這個(gè)在 spring boot中經(jīng)常被用到,看注釋就能看懂意思了:表示標(biāo)有此注釋的類是一個(gè)
組件,并且呢,如果使用在spring 的配置上(也就是注解@Configuration),后者這個(gè)注解類在@ComponentScan的掃描路徑下的化,此類會(huì)被自動(dòng)掃描auto-detection
@Aspect 這個(gè)是切面的核心注解,聲明一個(gè)切面
4. 切面的使用
上面介紹了切面編程的首要工作,可能你現(xiàn)在對整個(gè)邏輯還云里霧里,別慌,下面馬上介紹:
舉個(gè)例子:一個(gè)比較簡單的場景
一個(gè)類中有一個(gè)的方法,在執(zhí)行此方法前我想獲取到方法的參數(shù)值并放到ThreadLocal中,執(zhí)行完畢在后從ThreaLocal中刪除。
為了高內(nèi)聚低耦合,業(yè)務(wù)的方法應(yīng)該單獨(dú)封裝在一個(gè)類里面,參數(shù)放入和參數(shù)刪除的功能應(yīng)該另外封裝,但是在調(diào)用時(shí)必須按照順序來調(diào)用執(zhí)行,這時(shí)候就需要用到切面編程了
。
或者統(tǒng)計(jì)一個(gè)項(xiàng)目中這個(gè)方法被調(diào)用的次數(shù);
或者在方法的執(zhí)行前后打印時(shí)間統(tǒng)計(jì)方法的執(zhí)行時(shí)間等等;
- [x] 切入點(diǎn)(Pointcut):在你方法上使用切點(diǎn),表明這是一個(gè)需要處理的點(diǎn)。這里我們沒有直接在方法使用
@PointCut(value=切點(diǎn)表達(dá)式)來直接表明這是一個(gè)切點(diǎn),而是在方法上使用了自定義注解@SplitParam,然后使用@PointCut(@SplitParam路徑)標(biāo)明含有此注解的方法全部作為切點(diǎn)
- [x] 通知、增強(qiáng)處理(Advice):就是上述你想要把參數(shù)放入到ThreadLoal、執(zhí)行完畢后從ThreadLocl中移除的功能;
- [x] 連接點(diǎn)(JoinPoint): Spring允許你是
Advice的地方,基本方法前、方法后、或者是(Around方法)、拋出異常都可以是連接點(diǎn)
- [x] 切面(Aspect):切面是你處理功能點(diǎn),全部實(shí)現(xiàn)的總稱
5. 具體實(shí)現(xiàn)之一碼便知
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;
/**
* 描述:參數(shù)處理切面
*
* @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的另外一中實(shí)現(xiàn)方式
* 切面執(zhí)行完后移除
*/
@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);
}
}
上述可以看到我有兩種方式實(shí)現(xiàn)了連接點(diǎn)
@JointPoint;
在Before上直接用的注解形式;
在AfterReturning上則是用的自定義的PointCut()方法,這個(gè)方上使用了@PointCut()來聲明切點(diǎn)表達(dá)式,效果都是一樣的;
馬上用起來吧!