[Java] Creation of original annotation

Background

If I implemented it with many annotations in the original model, I got a review saying "It's hard to see, so if you make your own annotations and use it, it will be easier to see", so I tried it.

before controller class

@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping(path = "/sample")
public class SampleController {

/**
   *Sample data acquisition API
   *
   * @param id sample data ID
   * @return Detailed information on sample data
   */
  @GetMapping(path = "{id}", produces = "application/json; charset=UTF-8")
  @ResponseStatus(HttpStatus.OK)
  public Response getSampleDataDetails(@Nonnull @Pattern(regexp = "[0-9]{8}") 
                                       @PathVariable final String id) {
    log.info(String.format("SampleController { id : %s }", id));
    return idUseCase.getSampleDataDetails(id);
  }
}

model class

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ModelSample {

  @NotBlank
  @Size(max = 1024)
  private String name;

  @Digits(integer = 3, fraction = 2)
  private Double percentage;

}

after controller class

@Validated
@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping(path = "/sample")
public class SampleController {

  /**
   *Sample data acquisition API
   *
   * @param id sample data ID
   * @return Detailed information on sample data
   */
  @GetMapping(path = "{id}", produces = "application/json; charset=UTF-8")
  @ResponseStatus(HttpStatus.OK)
  public Response getSampleDataDetails(@Id @Valid @Nonnull
                                       @PathVariable final String id) {
    log.info(String.format("SampleController { id : %s }", id));
    return idUseCase.getSampleDataDetails(id);
  }
}

model class

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Validated
public class ModelSample {

  @Name
  @Valid
  private String name;

  @Percentage
  @Valid
  private Double percentage;

}

Creating annotation class

You can feel that the annotations are refreshing before / after. To realize this, first create an annotation class.

ʻId annotation class`

@Target(ElementType.PARAMETER)
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Pattern(regexp = "[0-9]{8}")
@Nonnull
public @interface Id {

  String message() default "id: don't match format";

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

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

}

Commentary

@Target The argument to @Target specifies where to apply the annotation.

value Where to apply
ElementType.TYPE Class, interface, annotation, enum type
ElementType.FIELD field
ElementType.CONSTRUCTOR constructor
ElementType.METHOD Method

@Retention In the argument of @Retention, specify the range to hold the annotation.

value Range to hold
RetentionPolicy.RUNTIME Hold at run time.
RetentionPolicy.CLASS Keep in class file. (Not retained at runtime.)
RetentionPolicy.SOURCE Keep it in the source file. (It is not retained in the class file.)

If you do not add * @Retention, RetentionPolicy.CLASS will be the default.

@Constraint In the argument of @ Constraint, validateBy attribute, specify the validation to be executed by this annotation. If you want to implement your own validation process, specify the validator class that implements the process here. This time, we will create a combination of existing constraints, so this argument is left empty.

Constraint annotation

Add constraint annotations such as @Pattern and @Nonnull to your own annotation class. The annotation added here is checked when validating the class using the original annotation.

@interface You can define your own annotations by using @ interface.

message For message, specify the message when the constraint is violated.

groups groups specifies an attribute to determine whether to execute a constraint check depending on the situation. The groups attribute allows you to group constraints into any group, and you can specify that group when performing validation to check different constraints for each group. This time, it is not necessary to group them, so it is implemented empty.

payload payload specifies an attribute that gives an arbitrary category, such as severity, for constraint violations. I will use it as needed, but this time there is no particular category, so I am implementing it empty.

List List is a nested annotation and is used when the same constraint is defined multiple times under different conditions. This time, we will not use the same constraint because we will not define it multiple times under different conditions.

Create other annotation classes

Similarly, create annotations for name and percentage.

Annotation class for name

@Target(ElementType.FIELD)
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Size(max = 1024)
@NotBlank
public @interface Name {
  String message() default "name: don't match format";

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

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

`Annotation class for percentage``

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
@Digits(integer = 3, fraction = 2)
public @interface Percentage {
  String message() default "percentage: don't match format";

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

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

Commentary

@Size Indicates that the string length of the target field is within the specified size range.

@Digits For ʻinteger, specify the maximum number of digits for the integer part, and for fraction, specify the maximum number of digits for the decimal part. @Digits (integer = 3, fraction = 2)` means that "of the maximum number of digits of 3, 2 digits after the decimal point are the maximum".

Annotate the class to use

controller class

@Validated
@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping(path = "/sample")
public class SampleController {

  /**
   *Sample data acquisition API
   *
   * @param id sample data ID
   * @return Detailed information on sample data
   */
  @GetMapping(path = "{id}", produces = "application/json; charset=UTF-8")
  @ResponseStatus(HttpStatus.OK)
  public Response getSampleDataDetails(@Id @Valid @Nonnull
                                       @PathVariable final String id) {
    log.info(String.format("SampleController { id : %s }", id));
    return idUseCase.getSampleDataDetails(id);
  }
}

model class

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Validated
public class ModelSample {

  @Name
  @Valid
  private String name;

  @Percentage
  @Valid
  private Double percentage;

}

important point

--Be sure to add @Validated to the class to which you want to add your own annotation. --Add @Valid along with your own annotation.

Bonus: Create your own validation process

You may want to add your own validation process instead of combining existing constraints to create annotations, as in this case. In such a case, implement the validator class. We will implement the id validation process created this time.

ʻId annotation class`

@Target(ElementType.PARAMETER)
@Retention(RUNTIME)
@Constraint(validatedBy = {idValidator.class})
@Pattern(regexp = "[0-9]{8}")
@Nonnull
public @interface Id {

  String message() default "id: don't match format";

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

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

}

Validation processing implementation class

public class IdValidator implements ConstraintValidator<id, String> {

@Override 
   public void initialize(Id id) {} 

@Override 
   public boolean isValid(String value, ConstraintValidatorContext context) { 
     String pattern = "[0-9]{8}"; 
     if (!StringUtils.isEmpty(value)) {
       return value.matches(pattern);
     }
     return false; 
   } 
 } 

id class description

@Constraint Set the validation processing class in @ Constraint.

IdValidator class commentary

ConstraintValidator<A,T> The validator must implement ConstraintValidator <A, T>. A is the constraint annotation and T is the input value type. This time, I created the constraint annotation by myself, so I implemented it on the assumption that A will receive the id class and T will receive the value as a String.

A is initialize and T is the argument type of isValid.

initialize ʻInitialize` allows you to get the attribute value of the constraint annotation set in the JavaBeans property. I don't need it this time, so I created it empty.

isValid If validation is done with ʻisValid` and the return value is false, ConstraintVaiolation will be generated as validation failure. The process will be described here. This time, it determines if the received value matches the regular expression and returns true or false. If it is not included in the if statement in the first place, it can be determined that the received value is not a String, so false is returned.

Annotate the class to use

The usage is the same as when creating by combining existing annotations.

@Validated
@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping(path = "/sample")
public class SampleController {

  /**
   *Sample data acquisition API
   *
   * @param id sample data ID
   * @return Detailed information on sample data
   */
  @GetMapping(path = "{id}", produces = "application/json; charset=UTF-8")
  @ResponseStatus(HttpStatus.OK)
  public Response getSampleDataDetails(@Id @Valid @Nonnull
                                       @PathVariable final String id) {
    log.info(String.format("SampleController { id : %s }", id));
    return idUseCase.getSampleDataDetails(id);
  }
}

Again, please be careful about the following.

--Be sure to add @Validated to the class to which you want to add your own annotation. --Add @Valid along with your own annotation.

Summary

I was told, "Make your own annotations!" And learned that you can make your own annotations. When I actually made it, the model class became much cleaner and easier to see. I thought it was a merit that it became easier to understand what kind of validation was applied to each. Implementing annotations wasn't too difficult, but it took me a while to realize that it wouldn't work without the @Validated and @Valids in Controller and the classes that use annotations.

Reference material

Getting Started with Java EE 7 (23)-Basics of Bean Validation http://enterprisegeeks.hatenablog.com/entry/2016/02/15/072944

Get started with Java EE 7 (24)-Create custom constraints with Bean Validation http://enterprisegeeks.hatenablog.com/entry/2016/02/29/072811

Functional details of TERASOLUNA Global Framework --5.5 Input check https://terasolunaorg.github.io/guideline/public_review/ArchitectureInDetail/Validation.html#validation-basic-validation

Recommended Posts

[Java] Creation of original annotation
History of Java annotation
[Java] Annotation
[Java] Annotation
java file creation
java directory creation
[Java] Overview of Java
Automatic creation of Java unit test result report
Expired collection of java
Predicted Features of Java
[Java] Significance of serialVersionUID
Eclipse ~ Java project creation ~
Utilize original marker annotation
NIO.2 review of java
Review of java Shilber
java --Unification of comments
java (merits of polymorphism)
NIO review of java
[Java] Three features of Java
Summary of Java support 2018
Java Discord Bot creation
[Java] Calculator program creation 1
[In-house study session] Basics of Java annotation (2017/11/02) ~ Under construction ~
Java multi-project creation with Gradle
About an instance of java
[Java] Mirage-Basic usage of SQL
[Java] Practice of exception handling [Exception]
[Java11] Stream Summary -Advantages of Stream-
Basics of character operation (java)
4th day of java learning
[Java] Beginner's understanding of Servlet-①
Java end of month plusMonths
[Java] Summary of regular expressions
[Java] Summary of operators (operator)
[Java] Implementation of Faistel Network
[Java] Comparator of Collection class
Summary of Java language basics
[Java] Summary of for statements
Summary of Java Math class
Enumeration of all combinations Java
java (inheritance of is-a principle)
Implementation of gzip in java
Advantages and disadvantages of Java
Benefits of Java static method
[Java] Summary of control syntax
Implementation of tri-tree in Java
Summary of java error processing
[Java] Summary of design patterns
[Java] Summary of mathematical operations
[Read Effective Java] Chapter 2 Item 5 "Avoid the creation of unnecessary objects"
[Java] Speed comparison of string concatenation
Think of a Java update strategy
[Java] Delete the elements of List
[For beginners] Summary of java constructor
Various methods of Java String class
Root cause of java framework bugs
About fastqc of Biocontainers and Java
[Java version] The story of serialization
About Lambda, Stream, LocalDate of Java8
Play Framework 2.6 (Java) development environment creation
Read CSV in Java (Super CSV Annotation)