SoFunction
Updated on 2025-03-08

How to use SpringBoot for elegant data verification

JSR-303 Specification

Before the program performs data processing, it is something we must consider. Discovering data errors as early as possible can not only prevent the error from spreading to the core business logic, but also this error is very obvious and easy to detect and resolve.

The JSR303 specification (Bean Validation specification) defines the corresponding metadata model and API for JavaBean verification. In the application, you can ensure the correctness of the data model (JavaBean) by using Bean Validation or your own defined constraint, such as @NotNull, @Max, @ZipCode. constraint can be attached to fields, getter methods, classes, or interfaces. For some specific needs, users can easily develop customized constraints. Bean Validation is a runtime data verification framework, and the verified error message will be returned immediately after verification.

About JSR 303 – Bean Validation specification, please refer toOfficial website

For the JSR 303 specification, Hibernate Validator provides reference implementations of it. Hibernate Validator provides all the built-in constraints in the JSR 303 specification, and there are some additional constraints. If you want to learn more about Hibernate Validator, please check it outOfficial website

Constraint Details
@AssertFalse The annotated element must be false
@AssertTrue Same as @AssertFalse
@DecimalMax The commented element must be a number, and its value must be less than or equal to the specified maximum value.
@DecimalMin Same as DecimalMax
@Digits The annotated element must be a number within an acceptable range
@Email As the name implies
@Future Future dates
@FutureOrPresent Now or in the future
@Max The commented element must be a number, and its value must be less than or equal to the specified maximum value.
@Min The commented element must be a number, and its value must be greater than or equal to the specified minimum value.
@Negative The annotated element must be a strictly negative number (0 is an invalid value)
@NegativeOrZero The annotated element must be a strictly negative number (including 0)
@NotBlank same
@NotEmpty same
@NotNull Can't be Null
@Null The element is Null
@Past The commented element must be a past date
@PastOrPresent Past and present
@Pattern The annotated element must comply with the specified regular expression
@Positive The annotated element must have a strict positive number (0 is an invalid value)
@PositiveOrZero The annotated element must have a strict positive number (including 0)
@Szie The annotated element size must be between the specified boundary (including)

Hibernate Validator Attached constraint

Constraint Details
@Email The annotated element must be an email address
@Length The commented string must be within the specified range
@NotEmpty The commented string must be non-empty
@Range The annotated element must be within the appropriate range
CreditCardNumber The annotated element must comply with the credit card format

The Constraints attached to different versions of Hibernate Validator may be different, and you also need to check the version you use. Constraint provided by Hibernate inUnder this bag.

A constraint is usually composed of annotation and corresponding constraint validator, which are a one-to-many relationship. That is to say, there can be multiple constraint validators corresponding to an annotation. At runtime, the Bean Validation framework itself selects the appropriate constraint validator to verify the data based on the type of the annotated element.

Sometimes, some more complex constraints are needed in user applications. Bean Validation provides a mechanism to extend constraints. There are two ways to achieve it, one is to combine existing constraints to generate a more complex constraint, and the other is to develop a brand new constraint.

Use Spring Boot for data verification

Spring Validation secondary encapsulation of hibernate validation allows us to use the data verification function more conveniently. Here we use Spring Boot to reference the verification function.

If you use Spring Boot version less than 2., spring-boot-starter-web will automatically introduce the hibernate-validator dependency. If Spring Boot version is greater than 2., you need to manually introduce dependencies:

<dependency>
 <groupId></groupId>
 <artifactId>hibernate-validator</artifactId>
 <version>6.0.</version>
</dependency>

Direct parameter verification

Sometimes the interface has fewer parameters, only one is alive with two parameters. At this time, there is no need to define a DTO to receive parameters, and you can directly receive parameters.

@Validated
@RestController
@RequestMapping("/user")
public class UserController {

 private static Logger logger = ();

 @GetMapping("/getUser")
 @ResponseBody
 // Note: If you want to use @NotNull annotation verification in the parameters, you must add @Validated to the class; public UserDTO getUser(@NotNull(message = "userId cannot be empty") Integer userId){
 ("userId:[{}]",userId);
 UserDTO res = new UserDTO();
 (userId);
 ("The Road to Freedom of Programmers");
 (8);
 return res;
 }
}

The following is the unified exception handling class

@RestControllerAdvice
public class GlobalExceptionHandler {

 private static final Logger logger = ();

 @ExceptionHandler(value = )
 public Response handle1(ConstraintViolationException ex){
  StringBuilder msg = new StringBuilder();
 Set&lt;ConstraintViolation&lt;?&gt;&gt; constraintViolations = ();
 for (ConstraintViolation&lt;?&gt; constraintViolation : constraintViolations) {
  PathImpl pathImpl = (PathImpl) ();
  String paramName = ().getName();
  String message = ();
  ("[").append(message).append("]");
 }
 ((),ex);
 // Note: The Response class must have a get and set method, otherwise an error will be reported. return new Response(RCode.PARAM_INVALID.getCode(),());
 }

 @ExceptionHandler(value = )
 public Response handle1(Exception ex){
 ((),ex);
 return new Response((),());
 }
}

Call result

# There is no userId passed hereGET http://127.0.0.1:9999/user/getUser

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 14 Nov 2020 07:35:44 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
 "rtnCode": "1000",
 "rtnMsg": "[userId cannot be empty]"
}

Entity DTO verification

Define a DTO

import ;
import ;

public class UserDTO {

 private Integer userId;

 @NotEmpty(message = "The name cannot be empty")
 private String name;
 @Range(min = 18,max = 50,message = "Age must be between 18 and 50")
 private Integer age;
 //Omit the get and set methods}

Use @Validated for verification when receiving parameters

@PostMapping("/saveUser")
@ResponseBody
//Note: If the parameter in the method is an object type, you must add @Validated in front of the parameter object.public Response&lt;UserDTO&gt; getUser(@Validated @RequestBody UserDTO userDTO){
 (100);
 Response response = ();
 (userDTO);
 return response;
}

Unified exception handling

@ExceptionHandler(value = )
public Response handle2(MethodArgumentNotValidException ex){
 BindingResult bindingResult = ();
 if(bindingResult!=null){
 if(()){
  FieldError fieldError = ();
  String field = ();
  String defaultMessage = ();
  ((),ex);
  return new Response(RCode.PARAM_INVALID.getCode(),field+":"+defaultMessage);
 }else {
  ((),ex);
  return new Response((),());
 }
 }else {
 ((),ex);
 return new Response((),());
 }
}

Call result

### Create a user
POST http://127.0.0.1:9999/user/saveUser
Content-Type: application/json

{
"name1": "Programmer's Road to Freedom",
 "age": "18"
}

# Below is the return result
{
 "rtnCode": "1000",
"rtnMsg": "The name cannot be empty"
}

Verify Service layer method parameters

I don’t like this verification method very much. In half of the cases, the parameters that call the service layer method need to be verified in the controller layer, and there is no need to be verified again. This feature is listed here, just want to say that Spring also supports this.

@Validated
@Service
public class ValidatorService {

	private static final Logger logger = ();

	public String show(@NotNull(message = "Can't be empty") @Min(value = 18, message = "Minimum 18") String age) {
		("age = {}", age);
		return age;
	}

}

Group verification

Sometimes, different verification rules for DTO are required for different interfaces. The UserDTO above is still the column. Another interface may not need to limit the age between 18 and 50, it only needs to be greater than 18.

In this way, the above verification rules will not apply. Group verification is to solve this problem. Different groups adopt different verification strategies for the same DTO.

public class UserDTO {

 public interface Default {
 }

 public interface Group1 {
 }

 private Integer userId;
 //Note: After adding groups attributes to the @Validated annotation, the verification rule without group attributes in DTO will be invalid @NotEmpty(message = "The name cannot be empty",groups = )
 private String name;

 //Note: After adding the groups attribute, the group attribute must be added to the @Validated annotation before the verification rules can take effect, otherwise the following verification limit will be invalid @Range(min = 18, max = 50, message = "Age must be between 18 and 50",groups = )
 @Range(min = 17, message = "Age must be greater than 17", groups = )
 private Integer age;
}

How to use

@PostMapping("/saveUserGroup")
@ResponseBody
//Note: If the parameter in the method is an object type, you must add @Validated in front of the parameter object.//Check group verification, age satisfaction is greater than 17public Response&lt;UserDTO&gt; saveUserGroup(@Validated(value = {UserDTO.}) @RequestBody UserDTO userDTO){
 (100);
 Response response = ();
 (userDTO);
 return response;
}

Use Group1 packet for verification, because in DTO, Group1 packet does not have a verification on the name attribute, so this verification will not take effect.

The advantage of group verification is that different verification rules can be set for the same DTO, but the disadvantage is that for each new verification group, the verification rules for each attribute under this group need to be reset.

Group verification also has a sequential verification function.

Consider a scenario: a bean has 1 attribute (if it is attrA), and 3 constraints are added to this attribute (if it is @NotNull, @NotEmpty, @NotBlank). By default, validation-api's check order for these 3 constraints is random. In other words, it may first verify @NotNull, then @NotEmpty, and finally @NotBlank, or it may verify @NotBlank, then @NotEmpty, and finally @NotNull.

Then, if our requirement is to first verify @NotNull, then verify @NotBlank, and finally verify @NotEmpty. The @GroupSequence annotation can implement this function.

public class GroupSequenceDemoForm {

 @NotBlank(message = "Contains at least one non-empty character", groups = {})
 @Size(min = 11, max = 11, message = "The length must be 11", groups = {})
 private String demoAttr;

 public interface First {

 }

 public interface Second {

 }

 @GroupSequence(value = {, })
 public interface GroupOrderedOne {
 // First calculate the constraints belonging to the First group, and then calculate the constraints belonging to the Second group }


 @GroupSequence(value = {, })
 public interface GroupOrderedTwo {
 // First calculate the constraints belonging to the Second group, and then calculate the constraints belonging to the First group }

}

How to use

// First calculate the constraints belonging to the First group, and then calculate the constraints belonging to the Second group@Validated(value = {}) @RequestBody GroupSequenceDemoForm form

Nested verification

In the previous example, the fields in the DTO class are all types such as basic data types and String.

However, in actual scenarios, it is possible that a certain field is also an object. If we need to verify the data in this object, we can use nested verification.

If there is also a Job object in UserDTO, such as the following structure. It should be noted that the @Valid annotation must be added to the verification of the job class.

public class UserDTO1 {

 private Integer userId;
 @NotEmpty
 private String name;
 @NotNull
 private Integer age;
 @Valid
 @NotNull
 private Job job;

 public Integer getUserId() {
 return userId;
 }

 public void setUserId(Integer userId) {
  = userId;
 }

 public String getName() {
 return name;
 }

 public void setName(String name) {
  = name;
 }

 public Integer getAge() {
 return age;
 }

 public void setAge(Integer age) {
  = age;
 }

 public Job getJob() {
 return job;
 }

 public void setJob(Job job) {
  = job;
 }

 /**
  * This must be set to a static internal class
  */
 static class Job {
 @NotEmpty
 private String jobType;
 @DecimalMax(value = "1000.99")
 private Double salary;

 public String getJobType() {
  return jobType;
 }

 public void setJobType(String jobType) {
   = jobType;
 }

 public Double getSalary() {
  return salary;
 }

 public void setSalary(Double salary) {
   = salary;
 }
 }

}

How to use

@PostMapping("/saveUserWithJob")
@ResponseBody
public Response<UserDTO1> saveUserWithJob(@Validated @RequestBody UserDTO1 userDTO){
 (100);
 Response response = ();
 (userDTO);
 return response;
}

Test results

POST http://127.0.0.1:9999/user/saveUserWithJob
Content-Type: application/json

{
"name": "The Road to Freedom of Programmers",
 "age": "16",
 "job": {
 "jobType": "1",
 "salary": "9999.99"
 }
}

{
 "rtnCode": "1000",
"rtnMsg": ": Must be less than or equal to 1000.99"
}

Nested verification can be used in conjunction with group verification. Also, nested set verification will verify every item in the set, for example, the List field will verify every job object in this list. This point
The difference between @Valid and @Validated is discussed in detail below.

Collection verification

If the request body directly passes the json array to the background and hopes to perform parameter verification on each item in the array. At this time, if we directly use the list or set to receive data, the parameter verification will not take effect! We can use a custom list collection to receive parameters:

Wrapping List type and declaring @Valid annotation

public class ValidationList&lt;T&gt; implements List&lt;T&gt; {

 // @Delegate is a lombok annotation // In the implementation of the List interface, a series of methods are required. Using this annotation can be delegated to the ArrayList implementation // @Delegate
 @Valid
 public List list = new ArrayList&lt;&gt;();


 @Override
 public int size() {
 return ();
 }

 @Override
 public boolean isEmpty() {
 return ();
 }

 @Override
 public boolean contains(Object o) {
 return (o);
 }
 //.... The following omits a series of List interface methods, which are actually called ArrayList methods}

Calling methods

@PostMapping("/batchSaveUser")
@ResponseBody
public Response batchSaveUser(@Validated(value = ) @RequestBody ValidationList<UserDTO> userDTOs){
 return ();
}

Call result

Caused by: : Invalid property 'list[1]' of bean class []: Bean property 'list[1]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
 at (:622) ~[spring-beans-5.2.:5.2.]
 at (:839) ~[spring-beans-5.2.:5.2.]
 at (:816) ~[spring-beans-5.2.:5.2.]
 at (:610) ~[spring-beans-5.2.:5.2.]

A NotReadablePropertyException exception will be thrown, and this exception needs to be handled uniformly. I won't post the code here.

Custom checker

Customizing a verification device in Spring is very simple, and it is done in two steps.

Custom constraint annotations

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface EncryptId {

 // Default error message String message() default "Encryption id format error";

 // Grouping Class[] groups() default {};

 // Load Class[] payload() default {};
}

Implement the ConstraintValidator interface to write a constraint verification device

public class EncryptIdValidator implements ConstraintValidator&lt;EncryptId, String&gt; {

 private static final Pattern PATTERN = ("^[a-f\\d]{32,256}$");

 @Override
 public boolean isValid(String value, ConstraintValidatorContext context) {
 // Verification is performed only if it is not null if (value != null) {
  Matcher matcher = (value);
  return ();
 }
 return true;
 }
}

Programming verification

The above examples are all based on annotations to implement automatic verification, and in some cases we may want to call verification programmatically. This time you can inject it
object, then call its api.

@Autowired
private  globalValidator;

// Programming verification@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
 Set&lt;constraintviolation&gt; validate = (userDTO, );
 // If the verification is passed, validate is empty; otherwise, validate contains items that are not validated. if (()) {
 // Only after the verification is passed will the business logic process be performed
 } else {
 for (ConstraintViolation userDTOConstraintViolation : validate) {
  // Check failed, do other logic  (userDTOConstraintViolation);
 }
 }
 return ();
}

Fail Fast configuration

Spring Validation will verify all fields by default before throwing an exception. You can enable Fali Fast mode through some simple configurations and return immediately once the verification fails.

@Bean
public Validator validator() {
 ValidatorFactory validatorFactory = ()
  .configure()
  // Fast failure mode  .failFast(true)
  .buildValidatorFactory();
 return ();
}

Internationalization of verification information

Spring's verification function can return very friendly verification information prompts, and this information supports internationalization.

This function is not commonly used for the time being, please refer to this article for details.article

The difference between @Validated and @Valid

First of all, @Validated and @Valid can implement basic verification functions, that is, if you want to verify whether a parameter is empty and whether the length meets the requirements, you can use any annotation.

However, these two annotations are different in terms of functions such as grouping, where the annotation works, and nesting verification. The main differences between these two annotations are listed below.

  • @Valid annotation is an annotation of the JSR303 specification, and @Validated annotation is an annotation that comes with Spring framework;
  • @Valid does not have group verification function, @Validate has group verification function;
  • @Valid can be used on methods, constructors, method parameters and member properties (fields), and @Validated can be used on types, methods and method parameters. However, it cannot be used on member attributes (fields). Whether the two can be used on member attributes (fields) directly affects whether nested verification functions can be provided;
  • @Valid added to member attributes can be nested to verify member attributes, while @Validate cannot be added to member attributes, so this function does not exist.

Here we explain what nested verification is.

We now have an entity called Item:

public class Item {

 @NotNull(message = "id cannot be empty")
 @Min(value = 1, message = "id must be a positive integer")
 private Long id;

 @NotNull(message = "props cannot be empty")
 @Size(min = 1, message = "At least one attribute")
 private List&lt;Prop&gt; props;
}

Item has many attributes, including: pid, vid, pidName and vidName, as shown below:

public class Prop {

 @NotNull(message = "pid cannot be empty")
 @Min(value = 1, message = "pid must be a positive integer")
 private Long pid;

 @NotNull(message = "vid cannot be empty")
 @Min(value = 1, message = "vid must be a positive integer")
 private Long vid;

 @NotBlank(message = "pidName cannot be empty")
 private String pidName;

 @NotBlank(message = "vidName cannot be empty")
 private String vidName;
}

The attribute entity also has its own verification mechanism, such as pid and vid cannot be empty, pidName and vidName cannot be empty, etc.
Now we have an ItemController accepts an entry parameter of an Item and want to verify the Item, as shown below:

@RestController
public class ItemController {

 @RequestMapping("/item/add")
 public void addItem(@Validated Item item, BindingResult bindingResult) {
 doSomething();
 }
}

In the above figure, if the props attribute of the Item entity is not annotated, only @NotNull and @Size, whether the parameter is @Validated or @Valid validation, the Spring Validation framework will only perform non-null and quantity verification on the Item id and props, and will not perform field verification on the Prop entity in the props field. That is, before @Validated and @Valid are added to the method parameters, the parameters will not be nested. In other words, if there is a propagated list with a Prop pid that is empty or negative, the entry parameter verification will not be detected.

In order to be able to perform nested verification, it is necessary to manually specify on the props field of the Item entity that the entities in this field must also be verified. Since @Validated cannot be used on member attributes (fields), but @Valid can be added on member attributes (fields), and the @Valid class annotation also shows that it supports nested verification function, we can infer that: @Valid cannot be automatically nested verification when added to method parameters, but is used on the corresponding fields of the class that need to be nested verification, and is used to cooperate with @Validated or @Valid on method parameters for nested verification.

We modify the Item class as follows:

public class Item {

 @NotNull(message = "id cannot be empty")
 @Min(value = 1, message = "id must be a positive integer")
 private Long id;

 @Valid // Nested verification must be @Valid @NotNull(message = "props cannot be empty")
 @Size(min = 1, message = "Props must have at least one custom property")
 private List&lt;Prop&gt; props;
}

Then we can use @Validated or @Valid on the addItem function of the ItemController to perform nested verification of the entry parameters of the Item. If the corresponding field of the props in the Item is empty, the Spring Validation framework will detect it and the bindingResult will record the corresponding error.

A brief analysis of the principle of Spring Validation

Now let’s briefly analyze the principle of Spring verification function.

Method-level parameter verification implementation principle

The so-called method-level verification refers to directly adding constraints such as @NotNull and @NotEmpty to the method parameters.

for example

@GetMapping("/getUser")
@ResponseBody
public R getUser(@NotNull(message = "userId cannot be empty") Integer userId){
 //
}

or

@Validated
@Service
public class ValidatorService {

	private static final Logger logger = ();

	public String show(@NotNull(message = "Can't be empty") @Min(value = 18, message = "Minimum 18") String age) {
		("age = {}", age);
		return age;
	}

}

All are method-level verification. This method can be used on any Spring Bean method, such as Controller/Service, etc.

The underlying implementation principle is AOP. Specifically, it is to dynamically register the AOP section through the MethodValidationPostProcessor, and then use MethodValidationInterceptor to weave and enhance the point-cutting method.

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean {
 @Override
 public void afterPropertiesSet() {
 //Create facets for all beans marked with `@Validated` Pointcut pointcut = new AnnotationMatchingPointcut(, true);
 //Create Advisor for enhancement  = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice());
 }

 //Creating Advice is essentially a method interceptor protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
 return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
 }
}

Then take a look at MethodValidationInterceptor:

public class MethodValidationInterceptor implements MethodInterceptor {
 @Override
 public Object invoke(MethodInvocation invocation) throws Throwable {
 //Skip directly without enhancing methods if (isFactoryBeanMetadataMethod(())) {
  return ();
 }
 //Get grouping information Class[] groups = determineValidationGroups(invocation);
 ExecutableValidator execVal = ();
 Method methodToValidate = ();
 Set&lt;constraintviolation&gt; result;
 try {
  //The method is used to verify the parameters, and it is ultimately entrusted to the Hibernate Validator for verification.  result = (
  (), methodToValidate, (), groups);
 }
 catch (IllegalArgumentException ex) {
  ...
 }
 // Throw it directly if there is an exception if (!()) {
  throw new ConstraintViolationException(result);
 }
 //Real method call Object returnValue = ();
 //Check the return value, and ultimately, it is entrusted to Hibernate Validator for verification result = ((), methodToValidate, returnValue, groups);
 // Throw it directly if there is an exception if (!()) {
  throw new ConstraintViolationException(result);
 }
 return returnValue;
 }
}

DTO level verification

@PostMapping("/saveUser")
@ResponseBody
//Note: If the parameter in the method is an object type, you must add @Validated in front of the parameter object.public R saveUser(@Validated @RequestBody UserDTO userDTO){
 (100);
 return (userDTO);
}

This kind of verification belongs to the DTO level. In spring-mvc, RequestResponseBodyMethodProcessor is used to parse the parameters of the @RequestBody annotation and to process the return value of the @ResponseBody annotation method. Obviously, the logic for performing parameter verification is definitely in resolveArgument(), the method of parsing parameters.

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
 @Override
 public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
     NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

 parameter = ();
 //Embroidery the requested data into the DTO object Object arg = readWithMessageConverters(webRequest, parameter, ());
 String name = (parameter);

 if (binderFactory != null) {
  WebDataBinder binder = (webRequest, arg, name);
  if (arg != null) {
  // Perform data verification  validateIfApplicable(binder, parameter);
  if (().hasErrors() &amp;&amp; isBindExceptionRequired(binder, parameter)) {
   throw new MethodArgumentNotValidException(parameter, ());
  }
  }
  if (mavContainer != null) {
  (BindingResult.MODEL_KEY_PREFIX + name, ());
  }
 }
 return adaptArgumentIfNecessary(arg, parameter);
 }
}

As you can see, resolveArgument() calls validateIfApplicable() for parameter verification.

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
 // Get parameter annotations, such as @RequestBody, @Valid, @Validated Annotation[] annotations = ();
 for (Annotation ann : annotations) {
 // Try to get the @Validated annotation first Validated validatedAnn = (ann, );
 //If @Validated is marked directly, then check directly. //If not, then determine whether there is annotation starting with Valid before the parameter. if (validatedAnn != null || ().getSimpleName().startsWith("Valid")) {
  Object hints = (validatedAnn != null ? () : (ann));
  Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
  //Perform verification  (validationHints);
  break;
 }
 }
}

After seeing this, you should understand why the two annotations @Validated and @Valid can be mixed in this scenario. Let's continue to look at the implementation of () next.

Finally, it was found that the underlying layer finally called Hibernate Validator for real verification.

Unified handling of 404 and other errors

refer toblog

refer to

Spring Validation implementation principle and how to apply it

SpringBoot parameter checksum international use

The difference between @Valid and @ValidatedS

Pring Validation best practices and implementation principles, parameter verification is not that simple!

This is the end of this article about how to use SpringBoot for elegant data verification. For more related SpringBoot data verification content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!