Spring 切面編程讓你的代碼策馬奔騰!

昨天介紹了自定義校驗(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á)式,效果都是一樣的;

馬上用起來吧!

end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容