1.在某微服务项目中，测试提出一个缺陷：在列表输入框选择条件，不点击查询而是点击翻页，点击后页面刷新，按照条件筛选出数据数量但不显示数据，其实，本质上就是分页查询的页号溢出问题。由于该项目使用的是MybatisPlus，要解决这个问题其实只要写一个MybatisPlus配置类，中设置一个属性即可。 2.但是发现这个配置类设置的这个属性值未生效，在启动类中获取bean发现这个属性依然是false，注释掉这个Bean后发现依然存在这个Bean，这时候就找到在该项目的jar包中已经存在这个Bean导致自己写的Bean未生效。 3.于是思路就是要重写这个Bean，可以实现Spring中的接口：Bean定义注册后置处理器BeanDefinitionRegistryPostProcessor。代码如下：MybatisPlusMyConfigpackage jnpf.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * MyBatisPlus配置 */ @Configuration public class MybatisPlusMyConfig { /** * @Author LiXiangrong * @Description 复写MybatisPlusInterceptor * @Date 2024/01/11 18:04:33 * @Return com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor **/ @Bean("mybatisPlusInterceptor1") public MybatisPlusInterceptor mybatisPlusInterceptor() { // 核心插件 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加分页插件(如果配置多个插件,切记分页最后添加) PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); // dbType(数据库类型) innerInterceptor.setOverflow(true); // 默认值：false。溢出总页数后是否进行处理。 interceptor.addInnerInterceptor(innerInterceptor); return interceptor; } }MyBeanDefinitionRegistryPostProcessorpackage jnpf.config; import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.stereotype.Component; /** * @Author LiXiangrong * @Description 自定义的Bean定义注册后置处理器 * @Date 2024/01/11 18:06:08 **/ @Component public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { String bean1 = "mybatisPlusInterceptor"; String bean2 = "mybatisPlusInterceptor1"; // 复写MybatisPlusInterceptor // 若jar包和程序都注册了mybatisPlusInterceptor，就将程序里的bd覆盖掉jar包里的bd（继续使用jar包里的beanName，防止jar包中其它bean指定依赖了这个beanName） if (registry.containsBeanDefinition(bean1) && registry.containsBeanDefinition(bean2)) { // 移除bean1，用bean2注册一个新的bean1，然后把bean2移除（复写了bean1） registry.removeBeanDefinition(bean1); registry.registerBeanDefinition(bean1, registry.getBeanDefinition(bean2)); registry.removeBeanDefinition(bean2); } } @Override public void postProcessBeanFactory(@NotNull ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } }
1.防止重复提交注解package com.risesun.common.core.annotation; import java.lang.annotation.*; /** * @Author LiXiangrong * @Description 防止重复提交注解 * @Date 2023/04/03 9:04:15 **/ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatSubmit { /** * 防重复操作限时标记数值(存储redis限时标记数值) */ String value() default "value" ; /** * 防重复操作过期时间(借助redis实现限时控制) */ long expireSeconds() default 10; }2.AOP实现防止请求重复提交package com.risesun.business.aspect; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.json.JSONUtil; import com.risesun.common.core.annotation.RepeatSubmit; import com.risesun.common.core.constant.TokenConstants; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * @Author LiXiangrong * @Description AOP实现防止请求重复提交 * @Date 2023/04/03 8:41:04 **/ @Slf4j @Component @Aspect public class NoRepeatSubmitAspect { @Autowired private RedisTemplate redisTemplate; private final String SIGN = "submit duplication"; private final String PREFIX = "PREVENT_DUPLICATION_PREFIX:"; /** * @Author LiXiangrong * @Description 定义切点 * @Date 2023/04/03 8:53:11 * @Return void **/ @Pointcut("@annotation(com.risesun.common.core.annotation.RepeatSubmit)") public void preventDuplication() {} /** * @Author LiXiangrong * @Description 环绕方法 * @Date 2023/04/03 8:52:50 * @Param pjp * @Return java.lang.Object **/ @Around("NoRepeatSubmitAspect.preventDuplication()") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 获取请求信息 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取执行方法 Method method = ((MethodSignature) pjp.getSignature()).getMethod(); //获取防重复提交注解 RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); // 获取authorization以及方法标记，生成redisKey和redisValue String authorization = request.getHeader(TokenConstants.AUTHENTICATION); String url = request.getRequestURI(); // 通过前缀 + url + token + 函数参数签名 来生成redis上的 key String redisKey = PREFIX.concat(url).concat(authorization).concat(getMethodSign(method, pjp.getArgs())); // 判断redisTemplate是否存在标记，如果没有，则是首次提交该请求，需要把设置标记并且正常返回 if (!redisTemplate.hasKey(redisKey) && Objects.nonNull(annotation)) { // 这个值只是为了标记，不重要 String redisValue = redisKey.concat(annotation.value()).concat(SIGN); // 设置防重复操作的限时标记(前置通知) redisTemplate.opsForValue().set(redisKey, redisValue, annotation.expireSeconds(), TimeUnit.SECONDS); try { // 正常执行方法并返回 ProceedingJoinPoint类型参数可以决定是否执行目标方法， // 环绕通知必须要有返回值，返回值即为目标方法的返回值 return pjp.proceed(); } catch (Throwable throwable) { //确保方法执行异常实时释放限时标记(异常后置通知) redisTemplate.delete(redisKey); throw throwable; } } else { throw new RuntimeException("数据正在处理，请勿重复提交！"); } } /** * @Author LiXiangrong * @Description 获取方法签名生成方法标记，采用数字签名算法SHA1对方法签名字符串加签 * @Date 2023/04/03 8:49:45 * @Param method * @Param args * @Return java.lang.String **/ private String getMethodSign(Method method, Object... args) { StringBuilder sb = new StringBuilder(method.toString()); for (Object arg : args) { sb.append(toString(arg)); } return DigestUtil.sha1Hex(sb.toString()); } /** * @Author LiXiangrong * @Description 对参数重写toString方法，避免空参造成的影响 * @Date 2023/04/03 8:50:58 * @Param arg * @Return java.lang.String **/ private String toString(Object arg) { if (Objects.isNull(arg)) { return "null"; } if (arg instanceof Number) { return arg.toString(); } return JSONUtil.toJsonStr(arg); } }
自定义Spring转换器解决参数格式转换异常
自定义Spring转换器解决参数格式转换异常1.出现的场景当某个实体查询参数类型是Long，Integer，BigDecimal等的时候，前端按照规范如果没有参数应该不传递参数，但是却传递的是字符串“null”的情况下，SpringMVC出现数据格式转换异常的错误。2.利用Spring的转换器拦截请求参数判断null映射成想要的结果import org.apache.commons.lang3.StringUtils; import org.springframework.core.convert.converter.Converter; public class LongConverter implements Converter<String, Long> { @Override public Long convert(String from) { if (StringUtils.isEmpty(from) || "null".equals(from)) { return null; } return Long.valueOf(from); } }3.实现WebMvcConfigurer配置自定义的转换器import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @Author LiXiangrong * @Description 自定义WebConfig * @Date 2023/04/23 10:35:27 **/ @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new IntegerConverter()); registry.addConverter(new LongConverter()); registry.addConverter(new BigDecimalConverter()); } }
当我们实现某个接口时，可能会有很多种不同的实现方式。这些不同的实现方式通过一定的规则可以随意切换使用时，我们就可以考虑使用策略模式来实现。1.首先建一个策略接口去继承InitializingBeanpackage com.example.springbootdemo.strategy; import com.example.springbootdemo.factory.WaitHandleContext; import org.springframework.beans.factory.InitializingBean; /** * @Author LiXiangrong * @Description 策略接口 * @Date 2023/03/13 13:08:20 **/ public interface Strategy extends InitializingBean { /** * @Author LiXiangrong * @Description 要实现的业务方法 * @Date 2023/03/13 13:08:48 * @Return void **/ void method(); @Override default void afterPropertiesSet() { WaitHandleContext.registry(this); } /** * @Author LiXiangrong * @Description getType * @Date 2023/03/13 13:08:43 * @Return java.lang.String **/ String getType(); }2.接下来需要创建一个上下文角色类去注册策略。策略模式的重点是上下文角色类，他封装了对具体策略的调用。上下文角色类中维护着一个抽象角色的引用，高层模块在调用上下文角色类时，通过构造方法或其他方式将具体的策略角色实例赋给该引用，然后调用上下文角色类中的方。通过高层模块在上下文角色类中设置的不同具体策略实例即可执行不同的具体策略。package com.example.springbootdemo.factory; import com.example.springbootdemo.strategy.Strategy; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; public class WaitHandleContext { private WaitHandleContext() { } private static final Map<String, Strategy> strategyMaps = new ConcurrentHashMap<>(); public static Strategy chooseStrategy(String type) { return Optional.ofNullable(strategyMaps.get(type)).orElseThrow(()->new IllegalArgumentException("无对应策略")); } public static void registry(Strategy strategy) { //不注册重复策略 strategyMaps.putIfAbsent(strategy.getType(), strategy); System.out.println("策略"+strategy.getType()+"注册成功！"); } }3.接下来去实现策略接口即可。