sbc(三)自定義Starter-SpringBoot重構去重插件

前言

以前看過SSM(十四) 基於annotation的http防重插件的朋友應該記得我後文說過以後要用SpringBoot來進行重構。java

此次採用自定義的starter的方式來進行重構。 git

關於starter(起步依賴)其實在第一次使用SpringBoot的時候就已經用到了,好比其中的:github

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>複製代碼

咱們只須要引入這一個依賴SpringBoot就會把相關的依賴都加入進來,本身也不須要再去擔憂各個版本之間的兼容問題(具體使用哪一個版本由使用的spring-boot-starter-parent版本決定),這些SpringBoot都已經幫咱們作好了。web

01.jpg
01.jpg


Spring自動化配置

先加入須要的一些依賴:redis

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!--aop相關-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <!--redis相關-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-redis</artifactId>
    </dependency>

    <!--配置相關-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>

    <!--通用依賴-->
    <dependency>
        <groupId>com.crossoverJie</groupId>
        <artifactId>sbc-common</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </dependency>複製代碼

建立了CheckReqConf配置類用於在應用啓動的時候自動配置。
固然前提還得在resources目錄下建立META-INF/spring.factories配置文件用於指向當前類,才能在應用啓動時進行自動配置。spring

spring.factories:springboot

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
\com.crossoverJie.request.check.conf.CheckReqConf複製代碼

使用條件化配置

試着考慮下以下狀況:bash

由於該插件是使用redis來存儲請求信息的,外部就依賴了redis。若是使用了該插件的應用沒有配置或者忘了配置redis的一些相關鏈接,那麼在應用使用過程當中確定會出現寫入redis異常。app

若是異常沒有控制好的話還有可能影響項目的正常運行。ide

那麼怎麼解決這個狀況呢,可使用Spring4.0新增的條件化配置來解決。

解決思路是:能夠簡單的經過判斷應用中是否配置有spring.redis.hostredis鏈接,若是沒有咱們的這個配置就會被忽略掉。

實現代碼:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.crossoverJie.request.check.interceptor,com.crossoverJie.request.check.properties")

//是否有redis配置的校驗,若是沒有配置則不會加載改配置,也就是當前插件並不會生效
@Conditional(CheckReqCondition.class)
public class CheckReqConf {
}複製代碼

具體校驗的代碼CheckReqCondition:

public class CheckReqCondition implements Condition {

    private static Logger logger = LoggerFactory.getLogger(CheckReqCondition.class);


    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {

        //若是沒有加入redis配置的就返回false
        String property = context.getEnvironment().getProperty("spring.redis.host");
        if (StringUtils.isEmpty(property)){
            logger.warn("Need to configure redis!");
            return false ;
        }else {
            return true;
        }

    }
}複製代碼

只須要實現org.springframework.context.annotation.Condition並重寫matches()方法,便可實現我的邏輯。

能夠在使用了該依賴的配置文件中配置或者是不配置spring.redis.host這個配置,來看咱們的切面類(ReqNoDrcAspect)中53行的日誌是否有打印來判斷是否生效。

這樣只有在存在該key的狀況下才會應用這個配置。

固然最好的作法是直接嘗試讀、寫redis,看是否鏈接暢通來進行判斷。

AOP切面

最核心的其實就是這個切面類,裏邊主要邏輯和以前是如出一轍的就不在多說,只是這裏應用到了自定義配置。

切面類ReqNoDrcAspect:

//切面註解
@Aspect
//掃描
@Component
//開啓cglib代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ReqNoDrcAspect {
    private static Logger logger = LoggerFactory.getLogger(ReqNoDrcAspect.class);

    @Autowired
    private CheckReqProperties properties ;

    private String prefixReq ;

    private long day ;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @PostConstruct
    public void init() throws Exception {
        prefixReq = properties.getRedisKey() == null ? "reqNo" : properties.getRedisKey() ;
        day = properties.getRedisTimeout() == null ? 1L : properties.getRedisTimeout() ;
        logger.info("sbc-request-check init......");
        logger.info(String.format("redis prefix is [%s],timeout is [%s]", prefixReq, day));
    }

    /** * 切面該註解 */
    @Pointcut("@annotation(com.crossoverJie.request.check.anotation.CheckReqNo)")
    public void checkRepeat(){
    }

    @Before("checkRepeat()")
    public void before(JoinPoint joinPoint) throws Exception {
        BaseRequest request = getBaseRequest(joinPoint);
        if(request != null){
            final String reqNo = request.getReqNo();
            if(StringUtil.isEmpty(reqNo)){
                throw new SBCException(StatusEnum.REPEAT_REQUEST);
            }else{
                try {
                    String tempReqNo = redisTemplate.opsForValue().get(prefixReq +reqNo);
                    logger.debug("tempReqNo=" + tempReqNo);

                    if((StringUtil.isEmpty(tempReqNo))){
                        redisTemplate.opsForValue().set(prefixReq + reqNo, reqNo, day, TimeUnit.DAYS);
                    }else{
                        throw new SBCException("請求號重複,"+ prefixReq +"=" + reqNo);
                    }

                } catch (RedisConnectionFailureException e){
                    logger.error("redis操做異常",e);
                    throw new SBCException("need redisService") ;
                }
            }
        }

    }



    public static BaseRequest getBaseRequest(JoinPoint joinPoint) throws Exception {
        BaseRequest returnRequest = null;
        Object[] arguments = joinPoint.getArgs();
        if(arguments != null && arguments.length > 0){
            returnRequest = (BaseRequest) arguments[0];
        }
        return returnRequest;
    }
}複製代碼

這裏咱們的寫入rediskey的前綴和過時時間改成從CheckReqProperties類中讀取:

@Component
//定義配置前綴
@ConfigurationProperties(prefix = "sbc.request.check")
public class CheckReqProperties {
    private String redisKey ;//寫入redis中的前綴
    private Long redisTimeout ;//redis的過時時間 默認是天

    public String getRedisKey() {
        return redisKey;
    }

    public void setRedisKey(String redisKey) {
        this.redisKey = redisKey;
    }

    public Long getRedisTimeout() {
        return redisTimeout;
    }

    public void setRedisTimeout(Long redisTimeout) {
        this.redisTimeout = redisTimeout;
    }

    @Override
    public String toString() {
        return "CheckReqProperties{" +
                "redisKey='" + redisKey + '\'' +
                ", redisTimeout=" + redisTimeout +
                '}';
    }
}複製代碼

這樣若是是須要不少配置的狀況下就能夠將內容封裝到該對象中,方便維護和讀取。

使用的時候只須要在本身應用的application.properties中加入

# 去重配置
sbc.request.check.redis-key = req
sbc.request.check.redis-timeout= 2複製代碼

應用插件

使用方法也和以前差很少(在sbc-order應用):

  • 加入依賴:
<!--防重插件-->
<dependency>
    <groupId>com.crossoverJie.request.check</groupId>
    <artifactId>request-check</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>複製代碼
  • 在接口上加上註解:
@RestController
@Api(value = "orderApi", description = "訂單API", tags = {"訂單服務"})
public class OrderController implements OrderService{
    private final static Logger logger = LoggerFactory.getLogger(OrderController.class);


    @Override
    @CheckReqNo
    public BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) {
        BaseResponse<OrderNoResVO> res = new BaseResponse();
        res.setReqNo(orderNoReq.getReqNo());
        if (null == orderNoReq.getAppId()){
            throw new SBCException(StatusEnum.FAIL);
        }
        OrderNoResVO orderNoRes = new OrderNoResVO() ;
        orderNoRes.setOrderId(DateUtil.getLongTime());
        res.setCode(StatusEnum.SUCCESS.getCode());
        res.setMessage(StatusEnum.SUCCESS.getMessage());
        res.setDataBody(orderNoRes);
        return res ;
    }
}複製代碼

使用效果以下:

02.jpg
02.jpg

03.jpg
03.jpg

總結

注意一點是spring.factories的路徑不要搞錯了,以前就是由於路徑寫錯了,致使自動配置沒有加載,AOP也就沒有生效,排查了很久。。

項目:github.com/crossoverJi…

博客:crossoverjie.top

weixinchat.jpg
weixinchat.jpg
相關文章
相關標籤/搜索