以前看过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
先加入须要的一些依赖: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.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。