赞
踩
目录
九、过滤器(Filter)和拦截器(Interceptor)
1:@Resource和@AutoWried
@Resource和@Autowired注解都是用来实现依赖注入的。只是@AutoWried按by type自动注入,而@Resource默认按byName自动注入
@Resource有两个重要属性,分别是name和type
spring将name属性解析为bean的名字,而type属性则被解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,如果使用type属性则使用byType的自动注入策略。如果都没有指定,则通过反射机制使用byName自动注入策略
2:@JsonFormat和@DateTimeFormat
@JsonFormat用于出参时格式换@DateTimeFormat用于入参时格式化
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
3:@PostConstruct和@PreDestroy
javaEE5引入用于Servlet生命周期的注解,实现Bean初始化之前和销毁之前的自定义操作,也可以做初始化init()的一些操作。
使用此注解会影响服务启动时间
- // 用法一:动态变量获取并赋值给静态变量,减少了@Value的多次注入
- @Component
- public class SystemConstant {
- public static String env;
-
- @Value("${spring.profiles.active}")
- public String environment;
-
- @PostConstruct
- public void initialize() {
- System.out.println("初始化环境...");
- env= this.environment;
- }
- }
- // 用法二:将注入的Bean赋值给静态变量,减少依赖注入
- @Component
- public class RedisUtil {
- private static RedisTemplate<Object, Object> redisTemplates;
-
- @Autowired
- private RedisTemplate<Object, Object> redisTemplate;
- @PostConstruct
- public void initialize() {
- redisTemplates = this.redisTemplate;
- }
- }

4:@EnableAspectJAutoProxy(proxyTargetClass = false, exposeProxy = true)
解决同类方法调用时异步@Async和事务@Transactional不生效问题,没有用到代理类导致
参数proxyTargetClass控制aop的具体实现方式cglib或java的Proxy,默认为false(java的Proxy)
参数exposeProxy控制代理的暴露方式,解决内部调用不能使用代理的场景,默认为false
使用AopContext.currentProxy()获取一个代理类,然后用代理类再去调用就好了
- // serviceImpl文件
- @Async
- @TargetDataSource("db1")
- @Override
- public void do(String value) {
- // 动态切换数据源要注意一个问题,就是在开启事物之前要先切换成需要的数据源,不要在开启事物之后在切换数据源不然会切换失败,因为一个事物的开启是建立在与一个数据源建立连接的基础上开启的所以如果先开启事物然后再切换数据源会报错,切换会失败
- ((Service) AopContext.currentProxy()).dosomething(value);
- }
-
- @Transactional
- @Override
- public void dosomething(String value) {
- //dosomething()
- }
-
- // 注入自己的方式无需开启注解
- @Autowired
- Service service;
- @Async
- @TargetDataSource("db1")
- @Override
- public void do(String value) {
- service.dosomething(value);
- }
-
- @Transactional
- @Override
- public void dosomething(String value) {
- //dosomething()
- }
-
- // 获取当前Bean的方式无需开启注解(使用hutool SpringUtil 或自己封装一个SpringUtil)
- @Async
- @TargetDataSource("db1")
- @Override
- public void do(String value) {
- currentBean().dosomething(value);
- }
-
- @Transactional
- @Override
- public void dosomething(String value) {
- //dosomething()
- }
- private Service currentBean() {
- return SpringUtil.getBean(Service.class);
- }
-
- // 反射调用也可用不用开启注解
- @Autowired
- Service service;
- @Async
- @TargetDataSource("db1")
- @Override
- public void do(String value) {
- currentClazz().getMethod("dosomething").invoke(service);
- currentClazz().getMethod("dosomething").invoke(currentBean());
- currentClazz().getMethod("dosomething").invoke(currentProxy());
- }
-
- @Transactional
- @Override
- public void dosomething(String value) {
- //dosomething()
- }
- private Class<Service> currentClazz() {
- return Service.class;
- }

1:maven方式
mvn spring-boot:run -Dspring-boot.run.profiles=xxx
2:java -jar方式
java -server -Xms128m -Xmx256m -Dserver.port=8080 -jar xxx.jar --spring.profiles.active=dev
3:docker run方式(已经打好镜像)
环境变量:docker run -d -p 8080:8080 --name xx -e "SPRING_PROFILES_ACTIVE=dev" images:tag
启动参数(使用ENTRYPOINT []/CMD []时有效):docker run -d -p 8080:8080 --name xx images:tag --spring.profiles.active=dev
参数加载优先级:配置文件<环境变量<JVM系统变量(-D)<命令行参数(--)
4:docker-compose方式
- #V2.0
- environment:
- TZ:Asia/Shanghai
- JAVA_OPTS: -Dserver.port=8180
- JAVA_OPT_EXT: -server -Xms64m -Xmx64m -Xmn64m
- #V3.0
- environment:
- - TZ=Asia/Shanghai
- - JAVA_OPTS=-server -Dserver.port=8180 -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m -Xms128m -Xmx256m -Xmn128m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC
- # rocketMq配置- JAVA_OPT_EXT= -server -Xms128m -Xmx128m -Xmn128m
1:常用注解
@NotNull 不能为null
@AssertTrue 必须为true
@AssertFalse 必须为false
@Min 必须为数字,其值大于或等于指定的最小值
@Max 必须为数字,其值小于或等于指定的最大值
@DecimalMin 必须为数字,其值大于或等于指定的最小值
@DecimalMax 必须为数字,其值小于或等于指定的最大值
@Size 集合的长度
@Digits 必须为数字,其值必须再可接受的范围内
@Past 必须是过去的日期
@Future 必须是将来的日期
@Pattern 必须符合正则表达式
@Email 必须是邮箱格式
@Length 长度范围
@NotEmpty 不能为null,长度大于0
@Range 元素的大小范围
@NotBlank 不能为null,trim()后字符串长度大于0(限字符串)
2:注解校验
- # 使用
- @RestController
- @Validated
- public class ValidateController {
- @RequestMapping(value = "/test", method = RequestMethod.GET)
- public String paramCheck(@Length(min = 10) @RequestParam String name) {}
-
- @RequestMapping(value = "/person", method = RequestMethod.POST)
- public void add(@RequestBody @Valid Person person) {}
- }
- # 配置快速校验返回
- @Bean
- public Validator validator(){
- ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
- .configure()
- // true 快速失败返回模式 false 普通模式.failFast(true)
- .addProperty( "hibernate.validator.fail_fast", "true" )
- .buildValidatorFactory();
- return validatorFactory.getValidator();
- }
-

3:手动校验
一般我们在实体上加注解就可以实现入参的校验,但是如果在service层来进行校验的话也可以使用如下方式
- // 手动校验入参
- Set<ConstraintViolation<@Valid BudgetGetParamDTO>> validateSet = Validation.buildDefaultValidatorFactory()
- .getValidator()
- .validate(paramDTO, Default.class);
- if (!CollectionUtils.isEmpty(validateSet)) {
- // String messages = validateSet.stream().map(ConstraintViolation::getMessage).reduce((m1, m2) -> m1 + ";" + m2).orElse("参数输入有误!");
- // Iterator<ConstraintViolation<BudgetGetParamDTO>> it = validateSet.iterator();
- // List<String> errors = new ArrayList<>();
- // while (it.hasNext()) {
- // ConstraintViolation<BudgetGetParamDTO> cv = it.next();
- // errors.add(cv.getPropertyPath() + ": " + cv.getMessage());
- // }
- String messages = validateSet.iterator().next().getPropertyPath() + ": " + validateSet.iterator().next().getMessage();
- return ResultUtil.error(0, messages);
- }
4:自定义校验器
- # 使用@Pattern拓展一个校验器
- @ConstraintComposition(CompositionType.OR)
- //@Pattern(regexp = "1[3|4|5|7|8][0-9]\\d{8}")
- @Pattern(regexp = "1\\d{10}")
- @Null
- @Length(min = 0, max = 0)
- @Documented
- @Constraint(validatedBy = {})
- @Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
- @Retention(RUNTIME)
- @ReportAsSingleViolation
- public @interface PhoneValid {
- String message() default "手机号校验错误";
- Class<?>[] groups() default {};
- Class<? extends Payload>[] payload() default {};
- }
- # 自定义一个校验器
- public enum CaseMode {
- UPPER,
- LOWER;
- }
- @Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- @Constraint(validatedBy = CheckCaseValidator.class)
- @Documented
- public @interface CheckCase {
- String message() default "";
- Class<?>[] groups() default {};
- Class<? extends Payload>[] payload() default {};
- CaseMode value();
- }
- public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
- private CaseMode caseMode;
- public void initialize(CheckCase checkCase) {
- this.caseMode = checkCase.value();}
- public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
- if (s == null) {return true;}
- if (caseMode == CaseMode.UPPER) {
- return s.equals(s.toUpperCase());
- } else {
- return s.equals(s.toLowerCase());}
- }
- }

5:分组校验
- @Data
- public class DemoDto {
- public interface Default {}
- public interface Update {}
- @NotEmpty(message = "名称不能为空")
- private String name;
- @Length(min = 5, max = 25, message = "key的长度为5-25" ,groups = Default.class )
- private String key;
- @Pattern(regexp = "[012]", message = "无效的状态标志",groups = {Default.class,Update.class} )
- private String state;
- }
-
- @RequestMapping("test2")
- public String test2(@Validated(value = DemoDto.Default.class) @RequestBody DemoDto dto){}
- @RequestMapping("test4")
- public String test4(@Validated(value = {DemoDto.Default.class,DemoDto.Update.class}) @RequestBody DemoDto dto){}

- @Data
- @XmlAccessorType(XmlAccessType.FIELD)
- @XmlRootElement(name = "QueryResult")
- public class RetDTO {
- @ApiModelProperty(value = "返回信息", notes = "MSG", example = "MSG")
- private String MSG;
- @ApiModelProperty(value = "是否成功 Y成功 N失败", notes = "Y", example = "Y")
- private String BSUCCESS;
- @ApiModelProperty(value = "结果", notes = "[]", example = "[]")
- private InfosDTO INFOS;
- }
-
- @Data
- @XmlAccessorType(XmlAccessType.FIELD)
- @XmlRootElement(name = "INFO")
- public class InfosDTO {
- @ApiModelProperty(value = "结果集", notes = "[]", example = "[]")
- private List<ItemRetDTO> INFO;
- }
-
- @Data
- @XmlAccessorType(XmlAccessType.FIELD)
- @XmlRootElement(name = "INFO")
- public class ItemRetDTO implements Serializable {
- @ApiModelProperty(value = "号", notes = "code", example = "code")
- @XmlElement(name = "CODE")
- private String code;
- }
-
- // xml转实体
- String objStr = re.getBody().substring(re.getBody().indexOf("<QueryResult>"), re.getBody().indexOf("</QueryResult>")) + "</QueryResult>";
- JAXBContext context = JAXBContext.newInstance(RetDTO.class);
- Unmarshaller unmarshaller = context.createUnmarshaller();
- RetDTO obj = (RetDTO) unmarshaller.unmarshal(new StringReader(objStr));
-
- // 实体转xml
- String result = null;
- JAXBContext context = JAXBContext.newInstance(obj.getClass());
- Marshaller marshaller = context.createMarshaller();
- // 指定是否使用换行和缩排对已编组 XML 数据进行格式化的属性名称。
- marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
- marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
- StringWriter writer = new StringWriter();
- marshaller.marshal(obj, writer);
- result = writer.toString();
- return result;
-

可以把servletRequest转换 HttpServletRequest httpRequest = (HttpServletRequest) servletRequest这样比ServletRequest多了一些针对于Http协议的方法。 例如:getHeader(), getMethod() , getSession()
- package com.bootbase.common.core.util;
-
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.DisposableBean;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.ApplicationContextAware;
- import org.springframework.context.ApplicationEvent;
- import org.springframework.context.annotation.Lazy;
- import org.springframework.stereotype.Service;
-
- /**
- * @Description: 常用工具类
- */
- @Slf4j
- @Service
- @Lazy(false)
- public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
- private static ApplicationContext applicationContext = null;
- /**
- * 取得存储在静态变量中的ApplicationContext.
- */
- public static ApplicationContext getApplicationContext() {
- return applicationContext;
- }
- /**
- * 实现ApplicationContextAware接口, 注入Context到静态变量中.
- */
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) {
- SpringContextHolder.applicationContext = applicationContext;
- }
- /**
- * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
- */
- @SuppressWarnings("unchecked")
- public static <T> T getBean(String name) {
- return (T) applicationContext.getBean(name);
- }
- /**
- * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
- */
- public static <T> T getBean(Class<T> requiredType) {
- return applicationContext.getBean(requiredType);
- }
- /**
- * 清除SpringContextHolder中的ApplicationContext为Null.
- */
- public static void clearHolder() {
- if (log.isDebugEnabled()) {
- log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
- }
- applicationContext = null;
- }
- /**
- * 发布事件
- *
- * @param event
- */
- public static void publishEvent(ApplicationEvent event) {
- if (applicationContext == null) {
- return;
- }
- applicationContext.publishEvent(event);
- }
- /**
- * 实现DisposableBean接口, 在Context关闭时清理静态变量.
- */
- @Override
- public void destroy() throws Exception {
- SpringContextHolder.clearHolder();
- }
- }
- // 使用 (XxxService) SpringContextHolder.getBean("xxxServiceImpl")

当一个接口有2个不同实现时,使用@Autowired注解时会报会报错,此时可以结合@Qualifier("XXX")注解来解决这个问题。@Autowired和@Qualifier结合使用时,自动注入的策略会从byType转变成byName,还有就是使用@Primary可以理解为默认优先选择,同时不可以同时设置多个。
过滤器赖于servlet容器,实例只能在容器初始化时调用一次,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。
拦截器依赖于web框架,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。拦截器可以对静态资源的请求进行拦截处理。
两者的本质区别:拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的
- // 添加依赖
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-cache</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- // 配置
- @EnableCaching // 注解会自动化配置合适的缓存管理器(CacheManager)
- @Configuration
- @AllArgsConstructor
- @AutoConfigureBefore(RedisAutoConfiguration.class)
- public class RedisTemplateConfig {
- private final RedisConnectionFactory factory;
- @Bean
- public RedisTemplate<String, Object> redisTemplate() {
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- redisTemplate.setHashKeySerializer(new StringRedisSerializer());
- redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
- redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
- redisTemplate.setConnectionFactory(factory);
- return redisTemplate;
- }
- @Bean
- public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
- return redisTemplate.opsForHash();
- }
- @Bean
- public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
- return redisTemplate.opsForValue();
- }
- @Bean
- public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
- return redisTemplate.opsForList();
- }
- @Bean
- public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
- return redisTemplate.opsForSet();
- }
- @Bean
- public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
- return redisTemplate.opsForZSet();
- }
- // @Bean // @EnableCaching也会自动注入
- // public CacheManager redisCacheManager() {
- // RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
- // cacheManager.setDefaultExpiration(300);
- // cacheManager.setLoadRemoteCachesOnStartup(true); // 启动时加载远程缓存
- // cacheManager.setUsePrefix(true); //是否使用前缀生成器
- // // 这里可进行一些配置 包括默认过期时间 每个cacheName的过期时间等 前缀生成等等
- // return cacheManager;
- //}
- }
- // 使用
- @Cacheable(value = "models", key = "#testModel.name", condition = "#testModel.address != '' ")
- value (也可使用 cacheNames): 可看做命名空间,表示存到哪个缓存里了。
- key: 表示命名空间下缓存唯一key,使用Spring Expression Language(简称SpEL)生成。
- condition: 表示在哪种情况下才缓存结果(对应的还有unless,哪种情况不缓存),同样使用SpEL
- sync:是否同步
- @CacheEvict(value = "models", allEntries = true)@CacheEvict(value = "models", key = "#name")
- value:指定命名空间
- allEntries:清空命名空间下缓存
- key:删除指定key缓存
- beforeInvocation:bool 在调用前清除缓存
- @CacheEvict和@Scheduled(fixedDelay = 10000)同时用
- 每10s删除一次
- @CachePut(value = "models", key = "#name")刷新缓存
-

1:返回信息国际化
- # Resource 文件夹下添加多语言文件message.propertiies(默认) message_en_US.propertiies message_zh_CN.propertiies
-
- # 在application.yml中添加配置多语言文件地址
- spring:
- messages:
- encoding: UTF-8
- basename: i18n/messages
-
- # 创建读取配置内容的工具类
- @Component
- public class LocaleMessageSourceService {
- @Resource
- private MessageSource messageSource;
- public String getMessage(String code) {
- return this.getMessage(code, new Object[]{});}
- public String getMessage(String code, String defaultMessage) {
- return this.getMessage(code, null, defaultMessage);}
- public String getMessage(String code, String defaultMessage, Locale locale) {
- return this.getMessage(code, null, defaultMessage, locale);}
- public String getMessage(String code, Locale locale) {
- return this.getMessage(code, null, "", locale);}
- public String getMessage(String code, Object[] args) {
- return this.getMessage(code, args, "");}
- public String getMessage(String code, Object[] args, Locale locale) {
- return this.getMessage(code, args, "", locale);}
- public String getMessage(String code, Object[] args, String defaultMessage) {
- Locale locale = LocaleContextHolder.getLocale();
- return this.getMessage(code, args, defaultMessage, locale);}
- public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
- return messageSource.getMessage(code, args, defaultMessage, locale);}
-
- }
-
- # 使用
- @Autowired
- privateLocaleMessageSourceService localeMessageSourceService;
- localeMessageSourceService.getMessage("key"); // 只能读取i18n/messages**文件中的key
-
- # 请求
- 发请求时在请求头加上Accept-Language zh-CN/zh-TW/zh-HK/en-US 和配置文件对应这里就不能为zh或en

2:Vakidator校验国际化
- # 在Resources目录下创建ValidationMessages.properties(默认) ValidationMessages_zh_CN.properties ValidationMessages_en_US.properties
-
- # 默认情况下@Validated的message必须放在Resources下的ValidationMessages名称不可修改,如果想自定义可用通过配置修改
- @Resource
- private MessageSource messageSource;
- @NotNull
- @Bean
- public Validator getValidator() {
- LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
- // 这里使用系统i18n也就是 spring.messages.basename
- validator.setValidationMessageSource(this.messageSource);
- return validator;
- }
- 或(如果配置了校验的快速失败返回应和快速失败返回配置在一起)
- @Bean
- public Validator validator(){
- ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
- .configure()
- .messageInterpolator(new LocaleContextMessageInterpolator(new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(this.messageSource))))
- // true 快速失败返回模式 false 普通模式.failFast(true)
- .addProperty( "hibernate.validator.fail_fast", "true" )
- .buildValidatorFactory();
- return validatorFactory.getValidator();
- }
-
- # 使用
- @NotBlank(message = "{key}")
- private String name;
-
- # 请求
- 发请求时在请求头加上Accept-Language zh-CN/zh-TW/zh-HK/en-US 和配置文件对应这里就不能为zh或en

3:统一异常处理
- @Slf4j
- @RestControllerAdvice
- public class GlobalExceptionHandler {
- @ExceptionHandler(Exception.class)
- @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
- public R exception(Exception e) {
- log.error("全局异常信息 ex={}", e.getMessage(), e);
- return R.failed(e.getMessage());}
- @ExceptionHandler(BusinessException.class)
- @ResponseStatus(HttpStatus.OK)
- public R exception(BusinessException e) {
- log.error("业务处理异常 ex={}", e.getMessage(), e);
- return R.restResult(null, e.getCode(), e.getMessage());}
- @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
- @ResponseStatus(HttpStatus.BAD_REQUEST)
- public R bodyValidExceptionHandler(MethodArgumentNotValidException exception) {
- List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
- log.warn(fieldErrors.get(0).getDefaultMessage());
- return R.failed(fieldErrors.get(0).getDefaultMessage());}
- @ExceptionHandler({ConstraintViolationException.class})
- @ResponseStatus(HttpStatus.BAD_REQUEST) // 参数校验错误
- public R paramValidExceptionHandler(ConstraintViolationException exception) {
- Set<ConstraintViolation<?>> fieldErrors = exception.getConstraintViolations();
- List<ConstraintViolation<?>> errors = new ArrayList<>(fieldErrors);
- log.warn(errors.get(0).getMessage());
- return R.failed(errors.get(0).getMessage());}
- @ExceptionHandler(HttpMessageNotReadableException.class)
- @ResponseStatus(HttpStatus.BAD_REQUEST) // 请求方式错误 json少body
- public R normalException(HttpMessageNotReadableException e) {
- String msg = e.getMessage();
- log.warn("校验异常 ex={}", msg);
- return R.failed(msg);}
- }

- @ApiIgnore
- @Controller
- public class ErrorController extends BasicErrorController {
- @Autowired
- public ErrorController(ErrorAttributes errorAttributes,
- ServerProperties serverProperties,
- List<ErrorViewResolver> errorViewResolvers) {
- super(errorAttributes, serverProperties.getError(), errorViewResolvers);
- }
-
- // @Override
- // public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
- // HttpStatus status = getStatus(request);
- // // 获取 Spring Boot 默认提供的错误信息,然后添加一个自定义的错误信息
- // Map<String, Object> model = getErrorAttributes(request,
- // isIncludeStackTrace(request, MediaType.TEXT_HTML));
- // model.put("msg", "出错啦++++++");
- // // resource/templates 目录下创建一个 errorPage.html 文件作为视图页面${code}
- // ModelAndView modelAndView = new ModelAndView("errorPage", model, status);
- // return modelAndView;
- // }
-
- @Override // ajax请求时不再返回错误页面,页面请求还是返回错误页
- public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
- Map<String, Object> errorAttributes = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
- HttpStatus status = getStatus(request);
- // 获取错误信息 message/error
- String message = errorAttributes.get("message").toString();
- String path = errorAttributes.get("path").toString();
- Map<String, Object> retmap = new HashMap<>();
- BeanUtil.copyProperties(R.failed(path, message), retmap);
- return new ResponseEntity<>(retmap, status);
- }
- }

@RunWith(SpringRunner.class)
@SpringBootTest(classes = XxxApplication.class)
public class XxxApplicationTests {
@Test
public void contextLoads() {}
}
- @ControllerAdvice
- public class GlobalControllerAdiviceConfig {
- // GET参数:WebDataBinder是用来绑定请求参数到指定的属性编辑器,可以继承WebBindingInitializer来实现一个全部controller共享的dataBiner Java代码
- @InitBinder
- public void dataBind(WebDataBinder binder) {
- binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
- }
- // 处理Post报文体 没有配置ObjectMapper的Bean时使用此方式
- @Bean
- public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
- return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
- .deserializerByType(String.class, new StdScalarDeserializer<String>(String.class) {
- @Override
- public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
- // 重点在这儿:如果为空白则返回null
- String value = jsonParser.getValueAsString();
- if (value == null || "".equals(value.trim())) {
- return null;
- }
- return value.trim();
- }
- });
- }
- // 存在ObjectMapper的Bean时直接在Bean中配置
- // 此方式或@Bean @Primary会覆盖默认配置
- @Bean
- public ObjectMapper getObjectMapper() {
- ObjectMapper objectMapper = new ObjectMapper();
- JavaTimeModule javaTimeModule = new JavaTimeModule();
- javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")));
- javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
- javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
- javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
- javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
- javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
- javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
- // javaTimeModule只能手动注册,参考https://github.com/FasterXML/jackson-modules-java8
- objectMapper.registerModule(javaTimeModule);
- // 忽略json字符串中不识别的属性
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- // 忽略无法转换的对象
- objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
- // 统一配置接收参数空转null
- SimpleModule strModule = new SimpleModule();
- strModule.addDeserializer(String.class, new StdScalarDeserializer<String>(String.class) {
- @Override
- public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
- // 重点在这儿:如果为空白则返回null
- String value = jsonParser.getValueAsString();
- if (value == null || "".equals(value.trim())) {
- return null;
- }
- return value.trim();
- }
- });
- objectMapper.registerModule(strModule);
- // 对于String类型,使用objectMapper.registerModule(module)的方式;对于POJOs类型,使用objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)的方式;
- // objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
- return objectMapper;
- }
- }
- // 不要覆盖默认配置
- // 我们通过实现Jackson2ObjectMapperBuilderCustomizer接口并注册到容器,进行个性化定制,Spring Boot不会覆盖默认ObjectMapper的配置,而是进行了合并增强,具体还会根据Jackson2ObjectMapperBuilderCustomizer实现类的Order优先级进行排序,因此上面的JacksonConfig配置类还实现了Ordered接口。
- // 默认的Jackson2ObjectMapperBuilderCustomizerConfiguration优先级是0,因此如果我们想要覆盖配置,设置优先级大于0即可
- // 在SpringBoot2环境下,不要将自定义的ObjectMapper对象注入容器,这样会将原有的ObjectMapper配置覆盖!
- @Component
- public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer, Ordered {
- @Override
- public void customize(Jackson2ObjectMapperBuilder builder) {
- // 设置java.util.Date时间类的序列化以及反序列化的格式
- builder.simpleDateFormat(dateTimeFormat);
- // JSR 310日期时间处理
- JavaTimeModule javaTimeModule = new JavaTimeModule();
- DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateTimeFormat);
- javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
- javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
- DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(dateFormat);
- javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
- javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
- DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(timeFormat);
- javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
- javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
- builder.modules(javaTimeModule);
- // 全局转化Long类型为String,解决序列化后传入前端Long类型精度丢失问题
- builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
- builder.serializerByType(Long.class,ToStringSerializer.instance);
- }
-
- @Override
- public int getOrder() {
- return 1;
- }
- }

如果你有探索过这些Starter的原理,那你一定知道Spring Boot并没有消灭这些原本你要配置的Bean,而是将这些Bean做成了一些默认的配置类,同时利用/META-INF/spring.factories这个文件来指定要加载的默认配置。这样当Spring Boot应用启动的时候,就会根据引入的各种Starter中的/META-INF/spring.factories文件所指定的配置类去加载Bean
对于自定义的jar包如果想让引入的工程加载jar中的Bean我们就需要仿照Starter的方式把相关的Bean放入此文件中
Spring Boot2.7发布后不再推荐使用这种方式,3.0后将不支持这种写法
老写法:org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.spring4all.swagger.SwaggerAutoConfiguration
新写法:创建文件/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports内容为com.spring4all.swagger.SwaggerAutoConfiguration
- # 引入依赖
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- # 配置
- spring:
- redis:
- password: redis
- host: redis
- database: 1
- # 序列化
- @Configuration
- public class MyRedisConfig {
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- //参照StringRedisTemplate内部实现指定序列化器
- redisTemplate.setConnectionFactory(redisConnectionFactory);
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- redisTemplate.setHashKeySerializer(new StringRedisSerializer());
- // new JdkSerializationRedisSerializer()或new GenericJackson2JsonRedisSerializer()
- redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
- redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
- return redisTemplate;
- }
- }
- # 使用
- # opsForValue()-操作字符串;opsForHash()-操作hash;opsForList()-操作list;opsForSet()-操作set;opsForZSet()-操作有序zset
- redisTemplate.hasKey(key) // 判断key是否存在
- redisTemplate.expire(key, time, TimeUnit.MINUTES) // 指定key的失效时间DAYS HOURS
- redisTemplate.getExpire(key) // 根据key获取过期时间
- redisTemplate.delete(key) // 根据key删除reids中缓存数据
- redisTemplate.opsForValue().set("key1", "value1", 1, TimeUnit.MINUTES)
- redisTemplate.opsForValue().set("key2", "value2")
- redisTemplate.opsForValue().get("key1").toString()
- redisTemplate.opsForList().leftPush("listkey1", list1)
- (List<String>) redisTemplate.opsForList().leftPop("listkey1")
- redisTemplate.opsForList().rightPush("listkey2", list2)
- (List<String>) redisTemplate.opsForList().rightPop("listkey2")
- redisTemplate.opsForHash().putAll("map1", map)
- redisTemplate.opsForHash().entries("map1")
- redisTemplate.opsForHash().values("map1")
- redisTemplate.opsForHash().keys("map1")
- (String) redisTemplate.opsForHash().get("map1", "key1")
- redisTemplate.opsForSet().add("key1", "value1")
- Set<String> resultSet = redisTemplate.opsForSet().members("key1")
- ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-5", 9.6)
- Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>()
- tuples.add(objectTypedTuple1)

- # 引入依赖
- <!-- WebSocket -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-websocket</artifactId>
- </dependency>
-
- # 配置文件
- @Configuration
- @Slf4j
- public class WebSocketConfiguration implements ServletContextInitializer {
- /**
- * 这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket,如果你使用外置的tomcat就不需要该配置文件
- */
- @Bean
- public ServerEndpointExporter serverEndpointExporter() {
- return new ServerEndpointExporter();
- }
- @Override
- public void onStartup(ServletContext servletContext) {
- log.info("WebSocket:::startup");
- }
- }
-
- # 操作接口类
- public interface SocketSeverService {
- /**
- * 建立WebSocket连接
- * @param session session
- * @param userId 用户ID
- */
- void onOpen(Session session, String userId);
- /**
- * 发生错误
- * @param throwable e
- */
- void onError(Throwable throwable);
- /**
- * 连接关闭
- */
- public void onClose();
- /**
- * 接收客户端消息
- * @param message 接收的消息
- */
- void onMessage(String message);
- /**
- * 推送消息到指定用户
- * @param userId 用户ID
- * @param message 发送的消息
- */
- void sendMessageByUser(Integer userId, String message);
- /**
- * 群发消息
- * @param message 发送的消息
- */
- void sendAllMessage(String message);
- }
-
- # 操作实现
- @ServerEndpoint("/websocket/{userId}")
- @Component
- @Slf4j
- public class WebSocketSeverService implements SocketSeverService {
- // 与某个客户端的连接会话,需要通过它来给客户端发送数据
- private Session session;
- // session集合,存放对应的session
- private static final ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
- // concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
- private static final CopyOnWriteArraySet<WebSocketSeverService> webSocketSet = new CopyOnWriteArraySet<>();
- @OnOpen
- @Override
- public void onOpen(Session session, @PathParam(value = "userId") String userId) {
- log.info("WebSocket:::建立连接中,连接用户ID:{}", userId);
- try {
- Session historySession = sessionPool.get(userId);
- // historySession不为空,说明已经有人登陆账号,应该删除登陆的WebSocket对象
- if (historySession != null) {
- webSocketSet.remove(historySession);
- historySession.close();
- }
- } catch (IOException e) {
- log.error("WebSocket:::重复登录异常,错误信息:" + e.getMessage(), e);
- }
- // 建立连接
- this.session = session;
- webSocketSet.add(this);
- sessionPool.put(userId, session);
- log.info("WebSocket:::建立连接完成,当前在线人数为:{}", webSocketSet.size());
- }
- @OnError
- @Override
- public void onError(Throwable throwable) {
- log.info("WebSocket:::error:{}", throwable.getMessage());
- }
- @OnClose
- @Override
- public void onClose() {
- webSocketSet.remove(this);
- log.info("WebSocket:::连接断开,当前在线人数为:{}", webSocketSet.size());
- }
- @OnMessage
- @Override
- public void onMessage(String message) {
- log.info("WebSocket:::收到客户端发来的消息:{}", message);
- }
- @Override
- public void sendMessageByUser(Integer userId, String message) {
- log.info("WebSocket:::发送消息,用户ID:" + userId + ",推送内容:" + message);
- Session session = sessionPool.get(userId);
- try {
- session.getBasicRemote().sendText(message);
- } catch (IOException e) {
- log.error("WebSocket:::推送消息到指定用户发生错误:" + e.getMessage(), e);
- }
- }
- @Override
- public void sendAllMessage(String message) {
- log.info("WebSocket:::发送消息:{}", message);
- for (WebSocketSeverService webSocket : webSocketSet) {
- try {
- webSocket.session.getBasicRemote().sendText(message);
- } catch (IOException e) {
- log.error("WebSocket:::群发消息发生错误:" + e.getMessage(), e);
- }
- }
- }
- }
-
- # 使用
- @Autowired
- private SocketSeverService webSocketSeverService;
- webSocketSeverService.sendAllMessage(JSONUtil.toJsonStr(msg));
-
- # 前台连接-微信小程序
- data: {wx: null,pingInterval: null, lockReconnect: false, timer: null, limit: 0}
- reconnect(){
- if (this.lockReconnect) return;
- this.lockReconnect = true;
- clearTimeout(this.timer)
- if (this.data.limit<12){
- this.timer = setTimeout(() => {
- this.linkSocket();
- this.lockReconnect = false;
- }, 5000);
- this.setData({
- limit: this.data.limit+1
- })
- }
- },
- onShow: function() {
- let ws = wx.connectSocket({
- url: 'wss://api.shoutouf.com/testapi/websocket/'+this.data.apiData.query.userId,
- header:{
- 'content-type': 'application/json',
- 'Authorization': 'Bearer ' + wx.getStorageSync('access_token')
- },
- success: (e) => {console.log('ws连接成功', e)},
- fail: (e) => {console.log('ws连接失败', e)}
- })
- ws.onOpen((res) => {
- console.log(res)
- clearInterval(this.data.pingInterval)
- let pingInterval = setInterval(()=> {
- let msg={msg:'ping',toUser:'root'}
- wx.sendSocketMessage({data:JSON.stringify(msg)})
- }, 10000)
- this.setData({pingInterval: pingInterval})
- })
- ws.onMessage((res) => {console.log(222);console.log(res)})
- ws.onClose((res) => {console.log(333);this.reconnect()})
- ws.onError((res) => {console.log(22);heartCheck.reset();this.reconnect()})
- this.setData({ws: ws})
- }
- }
- onUnload: function () {
- this.data.ws.close()
- clearInterval(this.data.pingInterval)
- }
- # 前台连接-vue
- npm i sockjs-client stompjs
- import SockJS from 'sockjs-client';
- import Stomp from 'stompjs';
- connection() {
- let headers = {Authorization: 'Bearer ' + "4cf7d2df-f4a2-4295-b267-03dca1910459"};
- // 建立连接对象,这里配置了代理
- this.socket = new SockJS('/other/ws', null, {timeout: 10000});
- // 连接服务端提供的通信接口,连接以后才可以订阅广播消息和个人消息
- // 获取STOMP子协议的客户端对象
- this.stompClient = Stomp.over(this.socket);
- // 向服务器发起websocket连接
- this.stompClient.connect(headers, () => {
- this.stompClient.subscribe('/app/topic/pc', function(greeting) {
- console.log(greeting, 668);
- });
- }, err => {console.log("订阅失败")});
- }

- # 引入排除高版本的springdata引入和自己springboot相对于的版本否则报错
- # 引入redisson后本身包含redis-starter因此可以不用再引入了
- <dependency>
- <!--redisson, 包含redis-->
- <groupId>org.redisson</groupId>
- <artifactId>redisson-spring-boot-starter</artifactId>、、
- <version>3.23.4</version>
- <exclusions>
- <exclusion>
- <groupId>org.redisson</groupId>
- <artifactId>redisson-spring-data-31</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>org.redisson</groupId>
- <artifactId>redisson-spring-data-24</artifactId>
- <version>3.23.4</version>
- </dependency>
- # 配置-方式有yaml和文件两种方式
- # yaml中redisson其实是字符串所以不支持ENC但是支持表达式,这里的password和host其实是为了加密可以不要直接放在redisson中
- # codec是redis的序列化方式因为之前redis时配置的是默认jdk所以redisson也使用jdk,这里也可以使用json不过对于java来说使用redisson默认的Kryo5Codec和Jdk方式比json方式性能要高很多
- spring:
- redis:
- password: ENC(83PKsY3qVKiIX9AehVyniQ==)
- host: localhost
- redisson:
- # 或 file: classpath:xxx.yml
- config: |
- singleServerConfig:
- address: redis://${spring.redis.host}:6379
- password: ${spring.redis.password}
- codec:
- class: "org.redisson.codec.SerializationCodec"
- transportMode: "NIO"
- lockWatchdogTimeout: 10000 # 看门狗默认30s这里配置10s
- @Bean
- public RedisTemplate<String, Object> redisTemplate() {
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- redisTemplate.setConnectionFactory(factory);
- // redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- redisTemplate.setHashKeySerializer(new StringRedisSerializer());
- redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
- redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
- return redisTemplate;
- }
- # 当使用CacheManager操作缓存时一定要配置序列化方式一致不然oauth2会抛出[no body]错误
- @Bean
- public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
- // 解决Redisson引入后查询缓存转换异常的问题
- // ObjectMapper om = new ObjectMapper();
- // om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- // om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
- // ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-
- RedisCacheConfiguration config =
- RedisCacheConfiguration.defaultCacheConfig().serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer())); //设置序列化器
- return RedisCacheManager.builder(redisConnectionFactory)
- .cacheDefaults(config)
- .build();
- }
- # 使用
- @Autowired
- private RedissonClient redissonClient;
- RLock lock = redissonClient.getLock("key");
- try {
- lock.lock();
- } finally {
- if (lock.isLocked()) {
- lock.unlock();
- } else {
- // 执行超时看门狗释放锁抛异常回滚事务
- throw new BusinessException("操作失败请稍后重试");
- }
- }

- # 遇到一个问题,使用Logback时在logback.xml中使用${spring.application.name}获取配置文件属性时无法获取到,查阅资料后找到解决方案。
- 加载顺序logback.xml--->application.properties--->logback-spring.xml,所以需要把logback.xml修改为logback-spring.xml
- 在logback-spring.xml中用<springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>这个来获取spring配置文件中的属性
-
- # 日志从高到地低有 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL
- 日志输出规则 根据当前ROOT 级别,日志输出时,级别高于root默认的级别时会输出
- 属性描述
- scan:设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false
- <configuration scan="true" scanPeriod="60 seconds" debug="false"></configuration>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。