Spring中的数据校验--进阶

news2024/11/16 17:52:54

分组校验

场景描述

在实际开发中经常会遇到这种情况:添加用户时,id是由后端生成的,不需要校验id是否为空,但是修改用户时就需要校验id是否为空。如果在接收参数的User实体类的id属性上添加NotNull,显然无法实现。这时候就可以定义分组,在需要校验id的时候校验,不需要的时候不校验。

定义分组

校验的分组通过接口的形式定义。

代码准备


/**
 * @author lihz
 * @date 2023/2/18
 */
@RestController
@RequestMapping("/group/validation/")
public class GroupValidationController {

    @PostMapping("/insert")
    public String testInsert(@RequestBody @Validated({UserValidGroup.Insert.class}) UserInfo userInfo) {
        System.out.println(userInfo);
        return "OK";
    }

    @PostMapping("/update")
    public String testUpdate(@RequestBody @Validated({UserValidGroup.Update.class}) UserInfo userInfo) {
        System.out.println(userInfo);
        return "OK";
    }

    @PostMapping("/delete")
    public String testDelete(@RequestBody @Validated({UserValidGroup.Delete.class}) UserInfo userInfo) {
        System.out.println(userInfo);
        return "OK";
    }
}

@Data
class UserInfo {

    @Min(value = 1, message = "ID不能小于1", groups = {UserValidGroup.Delete.class, UserValidGroup.Update.class})
    private int id;

    @NotBlank(message = "用户名不能为空", groups = {UserValidGroup.Update.class, UserValidGroup.Insert.class})
    private String username;

    @NotBlank(message = "密码不能为空", groups = {UserValidGroup.Update.class, UserValidGroup.Insert.class})
    @Length(min = 8, max = 20, message = "密码长度在8-20之间", groups = {UserValidGroup.Update.class, UserValidGroup.Insert.class})
    private String password;
}

class UserValidGroup {

    public interface Insert {
    }

    public interface Update {
    }

    public interface Delete {
    }

    @GroupSequence({Insert.class, Update.class, Delete.class})
    public interface All {
    }
}

数据准备

insert测试

{
  "id": null,
  "username": "demon",
  "password": "123456"
}

输出:

{
    "code": 1,
    "msg": "Validation failed for argument [0] in public java.lang.String com.jurassic.cloud.project.controller.GroupValidationController.testInsert(com.jurassic.cloud.project.controller.UserInfo): [Field error in object 'userInfo' on field 'password': rejected value [12345]; codes [Length.userInfo.password,Length.password,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userInfo.password,password]; arguments []; default message [password],20,8]; default message [密码长度在8-20之间]] ",
    "data": null
}

注意:没有校验 id 属性。

update测试

{
  "id": null,
  "username": "demon",
  "password": "123456"
}

输出:

{
    "code": 1,
    "msg": "Validation failed for argument [0] in public java.lang.String com.jurassic.cloud.project.controller.GroupValidationController.testUpdate(com.jurassic.cloud.project.controller.UserInfo) with 2 errors: [Field error in object 'userInfo' on field 'id': rejected value [0]; codes [Min.userInfo.id,Min.id,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userInfo.id,id]; arguments []; default message [id],1]; default message [ID不能小于1]] [Field error in object 'userInfo' on field 'password': rejected value [123456]; codes [Length.userInfo.password,Length.password,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userInfo.password,password]; arguments []; default message [password],20,8]; default message [密码长度在8-20之间]] ",
    "data": null
}

注意:校验了 id 属性。

delete测试

{
  "id": null,
  "username": "demon",
  "password": "123456"
}

输出:

{
    "code": 1,
    "msg": "Validation failed for argument [0] in public java.lang.String com.jurassic.cloud.project.controller.GroupValidationController.testDelete(com.jurassic.cloud.project.controller.UserInfo): [Field error in object 'userInfo' on field 'id': rejected value [0]; codes [Min.userInfo.id,Min.id,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userInfo.id,id]; arguments []; default message [id],1]; default message [ID不能小于1]] ",
    "data": null
}

注意:仅验证 id 属性。

总结

如果未指定分组,则是Default组,不属于Default组的属性不会验证。

指定了分组,则仅验证指定的分组涉及的约束。

分组的高级特性

见其他JSR 380文档。

i18n

在进行约束声明时,会指定message属性,用于设置约束校验失败之后的提示,如果需要支持多语言,则不能得到期望的结果。

不指定message属性

如果不指定message,则会采用框架的默认值,会提供主流的语言,

框架解析message默认值的相关逻辑在 org.hibernate.validator.internal.engine.ValidationContext

private String interpolate(String messageTemplate,
            Object validatedValue,
            ConstraintDescriptor<?> descriptor,
            Map<String, Object> messageParameters,
            Map<String, Object> expressionVariables) {
        MessageInterpolatorContext context = new MessageInterpolatorContext(
                descriptor,
                validatedValue,
                getRootBeanClass(),
                messageParameters,
                expressionVariables
        );

        try {
            //使用 MessageInterpolator 解析
            return validatorScopedContext.getMessageInterpolator().interpolate(
                    messageTemplate,
                    context
            ); 
        }
        catch (ValidationException ve) {
            throw ve;
        }
        catch (Exception e) {
            throw LOG.getExceptionOccurredDuringMessageInterpolationException( e );
        }
    }

在约束定义时,会设置message的默认值,是个消息插值。例如:@NotNull{javax.validation.constraints.NotNull.message},定义了消息参数,在Resource BundleValidationMessages中作为Key获取 获取属性值。

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {

	String message() default "{javax.validation.constraints.NotNull.message}";

	Class<?>[] groups() default { };

	Class<? extends Payload>[] payload() default { };

	@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
	@Retention(RUNTIME)
	@Documented
	@interface List {

		NotNull[] value();
	}
}

image-20230218150941153

ValidationMessages.properties

javax.validation.constraints.NotBlank.message        = must not be blank
javax.validation.constraints.NotEmpty.message        = must not be empty
javax.validation.constraints.NotNull.message         = must not be null
javax.validation.constraints.Null.message            = must be null

AbstractMessageInterpolator

消息解析主要是通过MessageInterpolator的实现类,默认都继承AbstractMessageInterpolator。 此类默认会根据JVM的Locale来获取对应的i18n消息(源码中的 defaultLocale = Locale.getDefault() ),不能根据request传递来的Locale来显示对应的消息。

缺陷

只能显示JVM的locale对应的消息。

自定义MessageInterpolator

自定义一个 MessageInterpolator 实现并改写其第一个interpolate方法,可以根据request传递的locale进行动态显示。

 
@Configuration
public class I18nConstrainValidator {

  @Bean
  public Validator validator() {
	  return Validation.byDefaultProvider().configure().messageInterpolator(new ParameterMessageInterpolator() {

		  @Override
		  public String interpolate(String message, Context context) {
			  return interpolate(message, context, Locale.getDefault());
		  }

		  @Override
		  public String interpolate(String message, Context context, Locale locale) {
			  // 获取当前请求所指定的语言对应的Locale
			  Locale requestLocale = I18nUtil.getLocaleFromCurrentRequest();
			  if (null == requestLocale) {
				  requestLocale = locale;
			  }

			  return super.interpolate(message, context, requestLocale);
		  }

	  }).buildValidatorFactory().getValidator();
  }

}

缺陷

当request指定了一种框架中不存在的语种时无法得到准确的对应语种的消息而是得到JVM Locale对应的消息。

语种不存在时会获取Locale.getDefault()对应的Locale

ResourceBundleMessageInterpolator

 
@Slf4j
@Configuration
public class ConstrainValidatorConfig {

    @Value("${spring.messages.basename}")
    private String[] baseNames;

    @Bean
    public Validator validator() {
        return Validation.byDefaultProvider().configure().messageInterpolator(new RequesLocaleAwareMessageInterpolator(

                // 提供AggregateResourceBundleLocator使得除了用框架提供的Validation ConstrainViolation
                // Message外,还可以用自己指定的或覆盖框架提供的。
                new AggregateResourceBundleLocator(Arrays.asList(baseNames))

        )).buildValidatorFactory().getValidator();
    }

    /**
     * 自定义ResourceBundleMessageInterpolator的若干方法,使得可根据request指定的语言返回对应语种的Validation
     * ConstrainViolation Message
     */
    public static class RequesLocaleAwareMessageInterpolator extends ResourceBundleMessageInterpolator {
        public RequesLocaleAwareMessageInterpolator(ResourceBundleLocator userResourceBundleLocator) {
            super(userResourceBundleLocator);
        }

        @Override
        public String interpolate(String message, Context context) {
            return interpolate(message, context, Locale.getDefault());
        }

        @Override
        public String interpolate(String message, Context context, Locale locale) {

            // 获取当前请求所指定的语言对应的Locale
             Locale requestLocale =    LocaleContextHolder.getLocale();
            log.debug("locale for javax.validation.Validator resolved: {}", requestLocale);

            if (null == requestLocale) {
                requestLocale = locale;
            }

            return super.interpolate(message, context, requestLocale);
        }
 

    }

}
 

若注解的message未指定——即用的是框架默认值(如 {javax.validation.constraints.Size.message} ),则对于框架未提供的i18n语种(如 zh_CHS),在你自己项目的i18n文件里补充相应值即可(如在messages_zh_CHS.properties文件里增加 javax.validation.constraints.Size.message = 长度不能超过{max} );

若注解的message不用默认值,而是自己指定message,如 message=“{custom.constraints.Size.message.name}” ,则在你自己项目的i18n文件里补充相应值即可(如 custom.constraints.Size.message.name = 姓名的长度不能超过{max} )。此时,注解的message仍支持EL表达式。实际使用中推荐用此方案,因为这种方案不仅支持EL表达式、i18n消息、还支持返回可直接弹窗显示给用户的i18n字段名。

设置的properties文件,可以不叫 ValidationMessages,可以是任何文件名。

设置语言的方式

1、设置header: Accept-Language,基于 AcceptHeaderLocaleResolver 实现

2、设置session / cookie:基于 CookieLocaleResolverSessionLocaleResolver 实现。自定义参数的名称,需要用到LocaleChangeInterceptor(需要启用,默认参数名为locale),用于监控哪个属性(可自定义)切换语言。

public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".LOCALE";
public static final String LOCALE_REQUEST_ATTRIBUTE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";

CookieLocaleResolver,会把locale放到cookie中,cookieName:org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE

3、固定locale。FixedLocaleResolver

spring.mvc.locale=zh_CN
//或者
spring:
  web:
    locale: zh_CN
    locale-resolver: fixed
    

spring.web.locale-resolver 优先级比 spring.mvc.locale-resolver 高一些。

spring.web.localespring.mvc.locale 这两个配置属性,假如存在,就会成为AcceptHeaderLocaleResolver 的默认的Locale 区域对象。 并在请求响应的请求头中没有Accept-Language这个属性时,成为AcceptHeaderLocaleResolver返回的Locale 区域对象。

Spring实现原理

Spring会在启动时通过AOP对使用@Validated@Valid的类或其子类的对象生成一个代理对象。在代理对象中调用目标hanlder方法前后会分别进行参数、返回值的JSR校验。

MethodValidationPostProcessor

切面创建的相关逻辑在MethodValidationPostProcessor

//org.springframework.validation.beanvalidation
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
		implements InitializingBean {

	private Class<? extends Annotation> validatedAnnotationType = Validated.class;

	@Nullable
	private Validator validator;
 
	public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) {
		Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
		this.validatedAnnotationType = validatedAnnotationType;
	}
	public void setValidator(Validator validator) {
		// Unwrap to the native Validator with forExecutables support
		if (validator instanceof LocalValidatorFactoryBean) {
			this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
		}
		else if (validator instanceof SpringValidatorAdapter) {
			this.validator = validator.unwrap(Validator.class);
		}
		else {
			this.validator = validator;
		}
	}
 
	public void setValidatorFactory(ValidatorFactory validatorFactory) {
		this.validator = validatorFactory.getValidator();
	}


    //此方法在bean自身初始化时会创建一个DefaultPointcutAdvisor用于向符合条件的对象添加进行方法验证的AOP advise
	@Override
	public void afterPropertiesSet() {
		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
	}
    //如果有 
	protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
		return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
	}

}

MethodValidationPostProcessor实现了接口BeanPostProcessor定义的方法postProcessAfterInitialization(从父类AbstractAdvisingBeanPostProcessor继承),该方法会检查每个bean的创建(在该bean初始化之后),如果检测到该bean符合条件,会向其增加上述AOP advise

MethodValidationPostProcessor是被ValidationAutoConfiguration自动配置到IoC容器的。

ValidationAutoConfiguration

  package org.springframework.boot.autoconfigure.validation;

  @Configuration
  @ConditionalOnClass(ExecutableValidator.class)
  @ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
  @Import(PrimaryDefaultValidatorPostProcessor.class)
  public class ValidationAutoConfiguration {
 
      @Bean    
      @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
      @ConditionalOnMissingBean(Validator.class)
      public static LocalValidatorFactoryBean defaultValidator() {
          LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
          MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
          factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
          return factoryBean;
      }
  
      // 向容器注册一个 bean MethodValidationPostProcessor  
      @Bean
      @ConditionalOnMissingBean
      public static MethodValidationPostProcessor methodValidationPostProcessor(
              Environment environment, @Lazy Validator validator) {
          MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
          boolean proxyTargetClass = environment
                  .getProperty("spring.aop.proxy-target-class", Boolean.class, true);
          processor.setProxyTargetClass(proxyTargetClass);
          processor.setValidator(validator);
          return processor;
      }
  
  }
 

MethodValidationInterceptor

切面中进行参数验证、返回值验证的相关逻辑在MethodValidationInterceptor

@Override
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable {
	  // Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
	  if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
		  return invocation.proceed();
	  }
	  //获取对哪些组进行校验
	  Class<?>[] groups = determineValidationGroups(invocation);

	  // Standard Bean Validation 1.1 API
	  ExecutableValidator execVal = this.validator.forExecutables();
	  Method methodToValidate = invocation.getMethod();
	  Set<ConstraintViolation<Object>> result;

	  try {
		  result = execVal.validateParameters(
				  invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
	  }
	  catch (IllegalArgumentException ex) {
		  // Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
		  // Let's try to find the bridged method on the implementation class...
		  methodToValidate = BridgeMethodResolver.findBridgedMethod(
				  ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
		  result = execVal.validateParameters(
				  invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
	  }
	  if (!result.isEmpty()) {
		  throw new ConstraintViolationException(result);
	  }

	  Object returnValue = invocation.proceed();

	  result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
	  if (!result.isEmpty()) {
		  throw new ConstraintViolationException(result);
	  }

	  return returnValue;
}


附录

Spring MVC localeResolver


@Configuration
public class WebMvcConfig implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }



    /**
     * Cookie方式
     *
     * @return
     */
    @Bean
    public LocaleResolver localeResolver() {
        return new CookieLocaleResolver();
    }

    /**
     * 切换语言按钮URL?language=zh_CN,切换后将语言信息存入cookie;
     *
     * @return
     */
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        //不设置,默认为locale。
        lci.setParamName("language");
        return lci;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【飞桨AI-Python小白逆袭大神课程】作业3-《青春有你2》选手数据分析

目录 一、数据准备 1、文件数据以json文件格式保存&#xff1a; 二、数据分析 2、数据分析四剑客&#xff1a; &#xff08;1&#xff09;Numpy &#xff08;2&#xff09;pandas &#xff08;3&#xff09;Matplotlib &#xff08;4&#xff09;PIL &#xff08;5&#x…

操作系统题目收录(十一)

1、操作系统采用分页存储管理方式&#xff0c;要求&#xff08;&#xff09;。 A&#xff1a;每个进程拥有一张页表&#xff0c;且进程的页表驻留在内存中B&#xff1a;每个进程拥有一张页表&#xff0c;但只有执行进程的页表驻留在内存中C&#xff1a;所有进程共享一张页表&a…

django项目实战(django+bootstrap实现增删改查)

目录 一、创建django项目 二、修改默认配置 三、配置数据库连接 四、创建表结构 五、在app当中创建静态文件 六、页面实战-部门管理 1、实现一个部门列表页面 2、实现新增部门页面 3、实现删除部门 4、实现部门编辑功能 七、模版的继承 1、创建模板layout.html 1&…

Django框架之模型视图--Session

Session 1 启用Session Django项目默认启用Session。 可以在settings.py文件中查看&#xff0c;如图所示 如需禁用session&#xff0c;将上图中的session中间件注释掉即可。 2 存储方式 在settings.py文件中&#xff0c;可以设置session数据的存储方式&#xff0c;可以保存…

基于springboot的网上图书商城的设计与实现(程序+详细设计文档)

大家好✌&#xff01;我是CZ淡陌。在这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路&#xff01; &#x1f345;更多优质项目&#x1f447;&…

Rust学习入门--【17】Rust Slice(切片)类型

系列文章目录 Rust 语言是一种高效、可靠的通用高级语言&#xff0c;效率可以媲美 C / C 。本系列文件记录博主自学Rust的过程。欢迎大家一同学习。 Rust学习入门–【1】引言 Rust学习入门–【2】Rust 开发环境配置 Rust学习入门–【3】Cargo介绍 Rust学习入门–【4】Rust 输…

RocketMQ云服务器和本地基础安装搭建及可视化控制台安装使用

一起学编程&#xff0c;让生活更随和&#xff01; 如果你觉得是个同道中人&#xff0c;欢迎关注博主gzh&#xff1a;【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享&#x1f433;。偶尔认知思考、日常水文&#x1f40c;。 目录一、RocketMQ 介绍1、Ro…

分布式事务--理论基础

1、事务基础 1.1、什么是事务 事务可以看做是一次大的活动&#xff0c;它由不同的小活动组成&#xff0c;这些活动要么全部成功&#xff0c;要么全部失败。 1.2、本地事务 在同一个进程内&#xff0c;控制同一数据源的事务&#xff0c;称为本地事务。例如数据库事务。 在计…

PyTorch 并行训练 DistributedDataParallel完整代码示例

使用大型数据集训练大型深度神经网络 (DNN) 的问题是深度学习领域的主要挑战。 随着 DNN 和数据集规模的增加&#xff0c;训练这些模型的计算和内存需求也会增加。 这使得在计算资源有限的单台机器上训练这些模型变得困难甚至不可能。 使用大型数据集训练大型 DNN 的一些主要挑…

SpringBoot监控

文章目录一、PrometheusGrafana监控Springboot1、简介2、SpringBoot应用镜像搭建2.1 springboot应用创建2.2 镜像创建3、Prometheus3.1 概述3.2 Prometheus创建4、Grafana可视化监控4.1 可视化4.2 告警设置二、轻量级日志系统Loki1、简介1.1 介绍1.2 与ELK差异2、grafana loki日…

linux宝塔安装和部署node全栈项目

使用服务器:阿里云ECS系列 服务器操作系统: Alibaba Cloud Linux 2.1903 LTS 64位 连接服务器方式: Workbench远程连接 使用公网IP登录 Workbench远程桌面&#xff0c;使用命令安装linux宝塔面板操作服务器: 1.登录linux宝塔面板&#xff0c;使用终端命令安装linux宝塔 yum i…

【操作系统】计算机系统概述

文章目录操作系统的概念、功能和目标熟悉的操作系统计算机系统的层次结构操作系统的概念操作系统的功能和目标作为系统资源的管理者作为用户和计算机之间的接口作为最接近硬件的层次操作系统的四个特征并发共享并发和共享的关系虚拟异步操作系统的发展和分类手工操作阶段单道批…

1207. 大臣的旅费/树的直径【AcWing】

1207. 大臣的旅费 很久以前&#xff0c;T王国空前繁荣。 为了更好地管理国家&#xff0c;王国修建了大量的快速路&#xff0c;用于连接首都和王国内的各大城市。 为节省经费&#xff0c;T国的大臣们经过思考&#xff0c;制定了一套优秀的修建方案&#xff0c;使得任何一个大…

使用Docker-Compose搭建Redis集群

1. 集群配置3主3从由于仅用于测试&#xff0c;故我这里只用1台服务器进行模拟redis列表2.编写redis.conf在server上创建一个目录用于存放redis集群部署文件。这里我放的路径为/root/redis-cluster 在/opt/docker/redis-cluster目录下创建redis-1,redis-2,redis-3,redis-4,redis…

Python 使用 pip 安装 matplotlib 模块(秒解版)

长话短说&#xff1a;本人下载 matplotlib 花了大概三个半小时屡屡碰壁&#xff0c;险些暴走。为了不让新来的小伙伴走我的弯路&#xff0c;特意创作本片文章指明方向。 1.首先需要下载 python 我直接是在电脑自带的软件商店里下载的&#xff0c;图方便&#xff0c;当然在官网下…

操作系统 四(设备管理)

I/O系统功能 隐藏I/O设备的细节&#xff1b;保证设备无关性&#xff1b;提高处理机和I/O设备的利用率&#xff1b;对I/O设备进行控制&#xff1b;确保对设备的正确共享&#xff1b;处理错误。中断、通道、DMA概念 中断&#xff1a;CPU对I/O设备发来的中断信号的一种响应DMA&am…

【配电网优化】基于串行和并行ADMM算法的配电网优化研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

超详细讲解文件函数

超详细讲解文件函数&#xff01;&#xff01;&#xff01;&#xff01;字符输入/输出函数fgetcfputc文本行输入/输出函数fgetsfputs格式化输入/输出函数fscanffprintf二进制输入/输出函数freadfwrite打开/关闭文件函数fopenfclose字符输入/输出函数 fgetc fgetc函数可以从指定…

个人谈谈对ThreadLocal内存泄露的理解

个人谈谈对ThreadLocal内存泄露的理解ThreadLocal作用ThreadLocalMap内存泄露解释为什么要这样设计ThreadLocalMap的实现思路ThreadLocal作用 平时我们会使用ThreadLocal来存放当前线程的副本数据&#xff0c;让当前线程执行流中各个位置&#xff0c;都可以从ThreadLocal中获取…

Java SPI 机制详解

在面向对象的设计原则中&#xff0c;一般推荐模块之间基于接口编程&#xff0c;通常情况下调用方模块是不会感知到被调用方模块的内部具体实现。一旦代码里面涉及具体实现类&#xff0c;就违反了开闭原则。如果需要替换一种实现&#xff0c;就需要修改代码。 为了实现在模块装…