前言
之前看過SSM(十四) 基於annotation的http防重外掛的朋友應該記得我後文說過之後要用SpringBoot
來進行重構。
這次採用自定義的
starter
的方式來進行重構。
關於starter(起步依賴)
其實在第一次使用SpringBoot
的時候就已經用到了,比如其中的:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>複製程式碼
我們只需要引入這一個依賴SpringBoot
就會把相關的依賴都加入進來,自己也不需要再去擔心各個版本之間的相容問題(具體使用哪個版本由使用的spring-boot-starter-parent
版本決定),這些SpringBoot
都已經幫我們做好了。
Spring自動化配置
先加入需要的一些依賴:
<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.factories
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
\com.crossoverJie.request.check.conf.CheckReqConf複製程式碼
使用條件化配置
試著考慮下如下情況:
因為該外掛是使用
redis
來儲存請求資訊的,外部就依賴了redis
。如果使用了該外掛的應用沒有配置或者忘了配置redis
的一些相關連線,那麼在應用使用過程中肯定會出現寫入redis
異常。如果異常沒有控制好的話還有可能影響專案的正常執行。
那麼怎麼解決這個情況呢,可以使用Spring4.0
新增的條件化配置來解決。
解決思路是:可以簡單的通過判斷應用中是否配置有spring.redis.host
redis連線,如果沒有我們的這個配置就會被忽略掉。
實現程式碼:
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;
}
}複製程式碼
這裡我們的寫入redis
key的字首和過期時間改為從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 ;
}
}複製程式碼
使用效果如下:
總結
注意一點是spring.factories
的路徑不要搞錯了,之前就是因為路徑寫錯了,導致自動配置沒有載入,AOP也就沒有生效,排查了好久。。
部落格:crossoverjie.top。