一起学SF框架系列5.11-spring-beans-数据校验validation

这篇具有很好参考价值的文章主要介绍了一起学SF框架系列5.11-spring-beans-数据校验validation。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

    在日常的项目开发中,应用在执行业务逻辑之前,为了防止非法参数对业务造成的影响,必须通过校验保证传入数据是合法正确的,但很多时候同样的校验出现了多次,在不同的层,不同的方法上,导致代码冗余,违反DRY原则。
    Java提供了数据校验规范来解决这个问题,可极大的简化校验实现,节省大量的工作量。Spring作为开发框架,支持相关规范,除了规范要求外,并提供了额外的增强。

Java数据校验规范

规范定义

    Java为Bean数据合法性校验提供了标准框架规范,它定义了一套可标注在成员变量,属性方法上的校验注解。最初版本为 Java Bean Validation1.0(JSR-303)、Java Bean Validation1.1(JSR-349),Java Bean Validation2.0(JSR-380),目前最新版本为Java Bean Validation3.0(同2.0最要变化是命名空间变为"jakarta.validation.*)。需要注意的是,JSR只是一项标准,它规定了一些校验注解的规范,但没有实现。
规范官网地址:https://beanvalidation.org/

常见注解

---- Java Bean Validation1.0 ----
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式

---- Java Bean Validation1.1 ----
@Valid 用于嵌套校验,可以对一个对象中的属性进行递归校验。
@ConvertGroup 用于分组校验,可以指定校验的分组,根据不同的分组执行不同的校验规则。
@GroupSequence 用于定义校验分组的顺序,指定不同分组的执行顺序。
增加了允许对任意方法和构造函数的参数和返回值进行约束
提供了一种在进行级联(嵌套)验证时更改目标组的方法。
约束消息可以使用EL表达式进行更灵活的呈现和字符串格式设置。同样,在EL上下文中也可以使用验证值。
支持容器的校验,通过TYPE_USE类型的注解实现对容器内容的约束:List<@Email String>

---- Java Bean Validation2.0 ----
@Negative 被注释的元素必须为 负数
@NegativeOrZero 被注释的元素必须为 负数或0
@Positive 被注释的元素必须为 正数(0为非法值)
@PositiveOrZero 被注释的元素必须为 正数或0
@PastOrPresent 被注释的元素必须是一个过去或当前的日期
@Future OrPresent 被注释的元素必须是一个将来或当前的日期
@NotEmpty 被注释的元素不能为NULL或者是EMPTY
@NotBlank 被注释的元素(字符串)不能为Null且至少包含一个非空格字符(去掉前后空格判断)
@Email 被注释的元素(字符串)必须满足email格式

---- Hibernate Validator增强 ----
@Range(min=, max=) 被注释的元素必须位于(包括)指定的最小值和最大值之间
@Length(min=, max=) 被注释的元素(字符串)长度必须在给定的范围之内,包含两端

---- Spring Validator增强 ----
@Validated Spring Validator提供的增强,支持分组校验

@Valid和@Validated区别

1、@Valid是JSR标准规范,@Validated是Spring Validator提供的。
2、@Valid可支持METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE标注,不支持类上注解;@Validated支持TYPE,METHOD,PARAMETER上注解,支持类上注解,不支持属性上注解。
3、@Valid直接嵌套对象自动校验,@Validated不支持(因为不支持属性上注解)。
4、@Valid 不支持分组校验,@Validated支持。

规范实现

Hibernate Validator是Java规范官方认证的实现(如下图),实现见:https://hibernate.org/validator/。
一起学SF框架系列5.11-spring-beans-数据校验validation,Spring学习系列,spring,python,数据库

Spring Validator

Spring提供了数据校验功能:Spring提供了Validator接口契约,可用于应用程序的每一层。

Validator接口

Spring提供了一个Validator接口(org.springframework.validation.Validator)(代码如下),使用它来验证对象;验证类需实现Validator接口,通过validate方法完成具体验证。

public interface Validator {
	// 是否当前当前验证器支持的类
	boolean supports(Class<?> clazz);
	// 验证给定的对象,如果出现验证错误,则将这些对象注册到给定的errors对象中 注1
	void validate(Object target, Errors errors);
}

注1:Errors是Spring用来存储并公开特定对象的数据绑定和验证错误信息接口,方法较多,默认实现BeanPropertyBindingResult,其类关系图如下:
一起学SF框架系列5.11-spring-beans-数据校验validation,Spring学习系列,spring,python,数据库
另:SmartValidator接口继承于Validator接口,主要增强支持来自验证器外部提示的功能。

工具类

Spring提供实用工具类ValidationUtils,主要处理拒绝空字段的方法。为防止实例化,定义为抽象类,所有实现方法均是静态方法。

public abstract class ValidationUtils {
	private static final Log logger = LogFactory.getLog(ValidationUtils.class);


	/**
	 * 简易方式调用具体验证器进行验证
	 * validator-验证器
	 * target-验证对象
	 * errors-保存错误的errors对象
	 */
	public static void invokeValidator(Validator validator, Object target, Errors errors) {
		invokeValidator(validator, target, errors, (Object[]) null);
	}
	/**
	 * 调用具体验证器进行验证(实现)
	 * validator-验证器
	 * target-验证对象
	 * errors-保存错误的errors对象
	 * validationHints-提示对象(可多个)
	 */
	public static void invokeValidator(
			Validator validator, Object target, Errors errors, @Nullable Object... validationHints) {

		//对应参数不能空,否则程序会直接结束
		Assert.notNull(validator, "Validator must not be null");
		Assert.notNull(target, "Target object must not be null");
		Assert.notNull(errors, "Errors object must not be null");

		if (logger.isDebugEnabled()) {
			logger.debug("Invoking validator [" + validator + "]");
		}
		if (!validator.supports(target.getClass())) {
			throw new IllegalArgumentException(
					"Validator [" + validator.getClass() + "] does not support [" + target.getClass() + "]");
		}

		if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator smartValidator) {
			// 如果校验器是SmartValidator,调用smartValidator.validate
			smartValidator.validate(target, errors, validationHints);
		}
		else {
			// 如果校验器不是SmartValidator,调用validator.validate
			validator.validate(target, errors);
		}

		if (logger.isDebugEnabled()) {
			if (errors.hasErrors()) {
				logger.debug("Validator found " + errors.getErrorCount() + " errors");
			}
			else {
				logger.debug("Validator found no errors");
			}
		}
	}

    //字符串空验证(用错误代码获取错误信息)
	public static void rejectIfEmpty(Errors errors, String field, String errorCode) {
		rejectIfEmpty(errors, field, errorCode, null, null);
	}
    //字符串空验证(defaultMessage-提供默认错误信息)
	public static void rejectIfEmpty(Errors errors, String field, String errorCode, String defaultMessage) {
		rejectIfEmpty(errors, field, errorCode, null, defaultMessage);
	}
    //字符串空验证(errorArgs-格式化错误信息对应参数)
	public static void rejectIfEmpty(Errors errors, String field, String errorCode, Object[] errorArgs) {
		rejectIfEmpty(errors, field, errorCode, errorArgs, null);
	}
    //字符串空验证真正实现
	public static void rejectIfEmpty(Errors errors, String field, String errorCode,
			@Nullable Object[] errorArgs, @Nullable String defaultMessage) {

		Assert.notNull(errors, "Errors object must not be null");
		Object value = errors.getFieldValue(field);
		if (value == null || !StringUtils.hasLength(value.toString())) {
			errors.rejectValue(field, errorCode, errorArgs, defaultMessage);
		}
	}

    //字符串空或仅仅空格 验证
	public static void rejectIfEmptyOrWhitespace(Errors errors, String field, String errorCode) {
		rejectIfEmptyOrWhitespace(errors, field, errorCode, null, null);
	}
    //字符串空或仅仅空格 验证(defaultMessage-提供默认错误信息)
	public static void rejectIfEmptyOrWhitespace(
			Errors errors, String field, String errorCode, String defaultMessage) {

		rejectIfEmptyOrWhitespace(errors, field, errorCode, null, defaultMessage);
	}
    //字符串空或仅仅空格 验证(errorArgs-格式化错误信息对应参数)
	public static void rejectIfEmptyOrWhitespace(
			Errors errors, String field, String errorCode, @Nullable Object[] errorArgs) {

		rejectIfEmptyOrWhitespace(errors, field, errorCode, errorArgs, null);
	}
    //字符串空或仅仅空格 验证实现
	public static void rejectIfEmptyOrWhitespace(
			Errors errors, String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage) {

		Assert.notNull(errors, "Errors object must not be null");
		Object value = errors.getFieldValue(field);
		if (value == null ||!StringUtils.hasText(value.toString())) {
			errors.rejectValue(field, errorCode, errorArgs, defaultMessage);
		}
	}
}

WEB项目应用

validation在web项目中的应用是最经典的,这也是validation的初衷。针对spring-boot或springMVC项目,如何使用Spring Validation在网上非常多,在此不赘述。

非WEB项目应用

非web项目应用,网上资料较少,本文着重讲这方面示例。

配置

在pom.xnl引入如下配置:

    <!-- hibernate-validator7对应tomcat-embed-el10支持jakarta;hibernate-validator6对应tomcat-embed-el8支持javax -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>7.0.5.Final</version>
    </dependency>
    <dependency>
	    <groupId>org.apache.tomcat.embed</groupId>
	    <artifactId>tomcat-embed-el</artifactId>
	    <version>10.0.5</version>
    </dependency>

提醒要特别注意版本匹配问题:
hibernate-validator7.0.5对应tomcat-embed-el10.0.5支持jakarta;
hibernate-validator6.2.5对应tomcat-embed-el8.5.29支持javax

原始方式应用

原始方式不是主要的应用方式,但理解原始方式有助于理解Spring实现校验的机制。

校验对象

public class Address {
	String country="CN";
	String province;
	String city;
	String county;
	// 省略get/set方法
}
public class Driver {
	String surname;  // 姓
	String name; // 名
	Integer age;
	Address address;
	// 省略get/set方法
}

实现校验类

@Component
public class AddressValidator implements Validator {

	//必须实现的接口方法-指定验证的类
	public boolean supports(Class clazz) {
		return Address.class.equals(clazz);
	}

	//必须实现的接口方法-指定验证的类
	public void validate(Object obj, Errors e) {
		ValidationUtils.rejectIfEmpty(e, "country", "country.empty");
		ValidationUtils.rejectIfEmpty(e, "province", "province.empty");
		ValidationUtils.rejectIfEmpty(e, "city", "city.empty");
		ValidationUtils.rejectIfEmpty(e, "county", "county.empty");
		ValidationUtils.rejectIfEmpty(e, "desc", "desc.empty");
	}
}
@Component
public class DriverValidator  implements Validator {
	@Autowired
	AddressValidator addressValidator;
	
	public boolean supports(Class clazz) {
		return Driver.class.isAssignableFrom(clazz);
	}

	public void validate(Object target, Errors e) {
		ValidationUtils.rejectIfEmptyOrWhitespace(e, "surname", "field.required");
		ValidationUtils.rejectIfEmptyOrWhitespace(e, "name", "field.required");
		Driver driver = (Driver) target;
		if (driver.getAge() < 18) {
			e.rejectValue("age", "negativevalue","未满18岁不能领取驾照");
		} else if (driver.getAge() > 70) {
			e.rejectValue("age", "too.darn.old","超过70岁不能领取驾照");
		}
		try {
			//嵌套验证路径入栈
			e.pushNestedPath("address");
			ValidationUtils.invokeValidator(this.addressValidator, driver.getAddress(), e);
		} finally {
			//嵌套验证路径出栈
			e.popNestedPath();
		}
	}
}

校验

	public void demo() {
		Address addr=new Address();
		addr.setProvince("四川");
		addr.setCity("成都");
		addr.setCounty("高新区");
		addr.setDesc("详细地址");

		Driver driver=new Driver();
		driver.setSurname("wang");
		driver.setName("wang");
		driver.setAge(20);
		driver.setAddress(addr);
		
		// 用DataBinder实现校验 注1
        DataBinder binder = new DataBinder(driver);
        binder.setValidator(driverValidator);
        // 调用校验
        binder.validate();
     	// 获取校验结果
        BindingResult results = binder.getBindingResult();
        
        System.out.println(results);
	}

注1:DataBinder见https://blog.csdn.net/davidwkx/article/details/131913078

注解方式应用

注解方式是应用实现的主要方式,简单高效。

校验对象

public class AddressAnnotateValidator {
	@NotBlank(message = "country 不能为空",groups = ValidGroupUpdate.class)
	String country="CN";
	@NotBlank
	String province;
	@NotBlank
	String city;
	@NotBlank
	String county;
	// 区(county)以下的地址描述
	@NotBlank
	String desc;
	// 省略get/set方法
}
public class DriverAnnotateValidator {
    private Integer id;
    @NotBlank(message = "姓不可以为空")
    @Length(min = 1, max = 20, message = "姓长度需要在20个字以内")
    private String surname;
    @NotBlank(message = "名不可以为空")
    @Length(min = 1, max = 20, message = "名长度需要在20个字以内")
    private String name;
    // 电话号码校验器是自己实现的 注1
	@NotBlank(message = "电话不可以为空")
    @Length(min = 1, max = 11, message = "电话长度需要在11个字以内")
    @MobilePhoneCheck
    private String mobilePhone;
    @NotBlank(message = "邮箱不允许为空")
    @Email(message = "邮箱格式不正确")
    @Length(min = 5, max = 50, message = "邮箱长度需要在50个字符以内")
    private String mail;
    @Max(70)
    @Min(18)
    private int age;
    @NotNull(message = "联系地址不可以为空")
    @Valid
    private AddressAnnotateValidator addr;
    // 省略get/set方法
}

注1:利用注解,可处理大量空、必须输入等繁琐校验。但有些校验很复杂,规范中注解不能支持,就需要自定义校验注解。

自定义校验注解

下面以定义移动电话校验注解为例,示例如何自定义校验注解。
1、定义校验注解MobilePhoneCheck

//我们可以直接拷贝系统内的注解如@Min,复制到我们新的注解中,然后根据需要修改。
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
// 关联注解的实现类
@Constraint(validatedBy = {MobilePhoneValidator.class})
public @interface MobilePhoneCheck {
	//校验错误的默认信息
	String message() default "手机号码格式有问题";
	//是否强制校验
	boolean isRequired() default false;
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

2、注解实现类MobilePhoneValidator(class)

// 实现的接口是MobilePhoneCheck注解
@Component
public class MobilePhoneValidator  implements ConstraintValidator<MobilePhoneCheck, String> {	 
	 private boolean required = false;
	 // 用正则表达式判断手机号码格式合法性
	 private static final Pattern mobile_pattern = Pattern.compile("^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$");
	 
	 // 注解接口方法的标准实现(必须)
	 @Override
	 public void initialize(MobilePhoneCheck constraintAnnotation) {
		 required = constraintAnnotation.isRequired();
	 }
	 // 注解接口方法的实现(必须)-验证时真正使用的接口
	 @Override
	 public boolean isValid(String phone, ConstraintValidatorContext constraintValidatorContext) {
		  //是否为手机号的实现
		  if (required) 
			  return isMobile(phone);
		  if (!StringUtils.hasText(phone)) 
			  return true;
		  return isMobile(phone);
	  }

	 //手机号码合法性判断
	 public static boolean isMobile(String src) {
		  if (!StringUtils.hasText(src)) 
			  return false;
		  Matcher m = mobile_pattern.matcher(src);
		  return m.matches();
	 }
}

校验对象

	public void demo() {
		AddressAnnotateValidator addr=new AddressAnnotateValidator();
		addr.setProvince("四川");
		addr.setCity("成都");
//		addr.setCounty("高新区");
		addr.setDesc("详细地址");
		DriverAnnotateValidator driver=new DriverAnnotateValidator();
		driver.setSurname("wang");
		driver.setName("wang");
		driver.setAge(15);
		driver.setMobilePhone("13300333691");
		driver.setAddr(addr);
		
		//获取校验结果(如何集合为空,表示无错误)  注1
		 Map<String,String> validateResult=BeanValidatorUtil.validate(driver);
 		 System.out.println(validateResult);
	}

注1:需项目提供校验方式。附BeanValidatorUtil代码:

public class BeanValidatorUtil {
	//用spring的校验器
//    private static final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
	//用Hibernate的校验器
    private static final ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
            .configure()
            .addProperty( "hibernate.validator.fail_fast", "true" )  //快速检测模式:不检测全部错误,碰到第一个错误就返回
            .buildValidatorFactory();
	
    //单个对象校验
    public static <T> Map<String,String> validate(T t, Class... groups){
    	//取校验器
        Validator validator=validatorFactory.getValidator();
        
        //校验
        Set<ConstraintViolation<T>> validateResult=validator.validate(t,groups);
        //如果为空
        if (validateResult.isEmpty()){
            return Collections.emptyMap();
        }else{
            //不为空时表示有错误(属性为key,错误信息为value的map) 注1
        	return validateResult.stream().collect(Collectors.toMap(k -> k.getPropertyPath().toString(), v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue(), (key1, key2) -> key2));
        }
    }
    //集合对象校验
    public static Map<String,String> validateList(Collection<?> collection){
        if(collection==null || collection.size()<1)
            return Collections.emptyMap();

        Map<String,String> errors=Collections.emptyMap();
        for(Object el:collection) {
        	errors=validate(el,new Class[0]);
        	if(!errors.isEmpty())  //有错
        		break;
        }
        return errors;
    }

     // 校验某一对象是否合法
    public static Map<String,String> validateObject(Object first,Object... objects){
        if (objects !=null && objects.length > 0 ){
            return validateList(Arrays.asList(first,objects));
        } else {
            return validate(first , new Class[0]);
        }
    }
}

注1:实际应用最好是抛出ConstraintViolationException异常,然后增加全局异常处理,这样程序处理很简单:每个方法只需要在第一行增加校验参数即可。文章来源地址https://www.toymoban.com/news/detail-617509.html

到了这里,关于一起学SF框架系列5.11-spring-beans-数据校验validation的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • spring-Bean

    在 Spring 中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化、组装和管理的对象。 bean的作用域 概念:在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围。 取值 含义 创建对象的时机 singleton(默认) 在IoC容器中,这个bean的

    2024年02月16日
    浏览(41)
  • Spring-bean的生命周期

    目录 1.什么是bean的生命周期 2.bean的单例与多例选择 1.什么是bean的生命周期: 2.bean的单例与多例选择: 准备好资源:  写好Bean方法: 配置Spring-context.xml  demo测试: 测试结果: 在什么场景下适合用单例和多例模式: 1.什么是bean的生命周期: Bean的生命周期是指在Java中管理

    2024年02月12日
    浏览(34)
  • spring-bean的基础知识

    1.bean的别名 2.bean的作用范围 单例多例,即现在创建的bean对象是一个还是多个,默认情况下是单例,可在配置文件修改 bean scope=\\\"singleton/prototype\\\"/ 适合单例的bean 1.表现层对象 2.业务层对象 3.数据层对象 4.工具对象 不适合单例的bean 封装实体的域对象 3.bean实例化 1.调用无参构造

    2024年02月12日
    浏览(39)
  • spring-Bean管理-springboot原理-Maven高级

    优先级(低→高) application.yaml(忽略) application.yml application.properties java系统属性(-Dxxx=xxx) 命令行参数(–xxx=xxx) 1.获取bean 上述所说的【Spring项目启动时,会把其中的bean都创建好】还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的bean而言。 2.bean作用

    2024年02月06日
    浏览(43)
  • 【Spring框架全系列】方法注解@Bean的使用

    📬📬哈喽,大家好,我是小浪。上篇博客我们介绍了五大类注解的使用方法,以及如何解决Spring使用五大类注解生成bean-Name的问题;那么,谈到如何更简单的读取和存储对象,这里我们还需要介绍另外一个\\\"方法注解@Bean\\\"的使用,快来一起学习叭!🛳🛳 📲目录 一、如何使

    2024年02月04日
    浏览(47)
  • 【Spring教程11】Spring框架实战:IOC/DI注解开发管理第三方bean的全面深入详解

    欢迎大家回到《 Java教程之Spring30天快速入门》,本教程所有示例均基于Maven实现,如果您对Maven还很陌生,请移步本人的博文《 如何在windows11下安装Maven并配置以及 IDEA配置Maven环境》,本文的上一篇为《 纯注解开发模式下的依赖注入和读取properties配置文件》 前面定义bean的时

    2024年02月04日
    浏览(61)
  • 5.11 mybatis之returnInstanceForEmptyRow作用

    mybatis的settings配置中有个属性returnInstanceForEmptyRow,该属性新增于mybatis的3.4.2版本,低于此版本不可用。该属性的作用官方解释为:当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关

    2024年04月24日
    浏览(19)
  • Spring(11) Bean的生命周期

    首先,为什么要学习 Spring 中 Bean 的生命周期呢? 虽然不了解 Bean 的生命周期,并不影响日常工作中的开发。但是如果我们了解了 Bean 的生命周期,可以帮助我们更好地掌握 Spring 框架,并且能够让我们更好地去理解 Spring 容器是如何管理和创建 Bean 示例的。如果以后遇到 B

    2024年02月14日
    浏览(37)
  • Spring框架中的Bean

    在Spring框架中,Bean是指一个由Spring容器管理的对象。这个对象可以是任何一个Java类的实例,例如数据库连接、业务逻辑类、控制器等等。Bean实例的创建和管理是由Spring容器负责的,而不是由应用程序本身负责。 Bean的主要优势是可以将对象的创建和管理与业务逻辑分离。这

    2023年04月09日
    浏览(33)
  • Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石

       BeanDefinition 是 Spring 中一个非常重要的概念,它包含了 Spring 容器用于创建、配置 Bean 所需的所有信息。理解 BeanDefinition 可以帮助我们深入掌握 Spring 的内部工作机制。 首先,让我们来对 BeanDefinition 有一个整体的认识。   对于理解 Spring 框架的概念和组件, Spring 的官方

    2024年02月15日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包