0830 - 0905


# 0830 - 0905

# 0831 - Spring : consumes์™€ produces์˜ ์ฐจ์ด

Controller์—์„œ Mapping์„ ํ•  ๋•Œ ์ฃผ๊ณ  ๋ฐ›๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ•์ œํ•จ์œผ๋กœ์จ ์˜ค๋ฅ˜์ƒํ™ฉ์„ ์ค„์ธ๋‹ค. ์ด๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ์ค‘ ํ•˜๋‚˜๊ฐ€ Media Types์ด๋‹ค.

# consumes

consumes๋Š” ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ •์˜ํ•  ๋•Œ ์ด์šฉํ•œ๋‹ค.

@PostMapping(path = "/pets", consumes = MediaType.APPLICATION_JSON_VALUE) 
public void addPet(@RequestBody Pet pet) {
    // ...
}
  • ์ด๋ ‡๊ฒŒ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฒŒ๋˜๋ฉด ํ•ด๋‹น uri๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ๋Š” ํ—ค๋”์— ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ json์ด๋ผ๋Š” ๊ฒƒ์„ ๋ช…์‹œํ•ด์•ผ ํ•œ๋‹ค.
Content-Type:application/java

# produces

produces๋Š” ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ •์˜ํ•œ๋‹ค.

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_VALUE) 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
  • ์ด๋Ÿด ๊ฒฝ์šฐ ๋ฐ˜ํ™˜ ํƒ€์ž…์ด json์œผ๋กœ ๊ฐ•์ œ๋œ๋‹ค.
Accept:application/json

# ์š”์•ฝ

  • consumes๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์—๊ฒŒ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋ช…์‹œํ•œ๋‹ค.
  • produces๋Š” ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋ช…์‹œํ•œ๋‹ค.

# 0901 - ๋ฐฐ์—ด ์ •๋ ฌ

# Arrays.sort์— Comparator์‚ฌ์šฉ compare ๋ฉ”์†Œ๋“œ ๊ตฌํ˜„

  • Comparator๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋˜๋ก ํ•ด์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.
  • ์ž๋ฐ” ๊ธฐ๋ณธ์ž๋ฃŒํ˜•์ด ์•„๋‹Œ ์‚ฌ์šฉ์ž ํด๋ž˜์Šค์˜ ๋น„๊ต๋‚˜ ํŠน์ • ๊ทœ์น™์— ์˜ํ•ด ๋น„๊ต๋ฅผ ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์— ๊ตฌํ˜„ํ•œ๋‹ค.

# Arrays.sort์˜ ํ˜•ํƒœ

public static <T> void sort(T[] a, Comparator<? super T> c) {
    if (c == null) {
        sort(a);
    } else {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, c);
        else
            TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
}
  • ์ธ์ž๋กœ Comparator์˜ ํƒ€์ž…์„ ๋„ฃ๊ณ  compare ๋ฉ”์†Œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•œ๋‹ค.

# ์˜ˆ์ œ

String[] arr = new String[N];	// ๋ฐฐ์—ด์— ๋‹จ์–ด๊ฐ€ ์ด๋ฏธ ์ดˆ๊ธฐํ™” ๋˜์—ˆ๋‹ค๊ณ  ๊ฐ€์ •
 
Arrays.sort(arr, new Comparator<String>() {		
	@Override
	public int compare(String s1, String s2) {
		// ๋‹จ์–ด ๊ธธ์ด๊ฐ€ ๊ฐ™์„ ๊ฒฝ์šฐ
		if(s1.length() == s2.length()} {
			return s1.compareTo(s2);	// ์‚ฌ์ „ ์ˆœ ์ •๋ ฌ
		}
		// ๊ทธ ์™ธ์˜ ๊ฒฝ์šฐ
		else {
			return s1.length() - s2.length();
		}
	}
});
  • compare ๋ฉ”์†Œ๋ฅด ๋ฆฌํ„ด ํƒ€์ž…์ด int์ธ ์ด์œ ๋Š” ์–‘์˜ ์ •์ˆ˜, 0, ์Œ์˜ ์ •์ˆ˜์— ๋”ฐ๋ผ ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค.
    • ์–‘์ˆ˜ = ์œ„์น˜ ๋ณ€๊ฒฝ
    • 0 and ์Œ์ˆ˜ = ๊ทธ๋Œ€๋กœ

# 0902 - ์ž๋ฐ” ์ •๊ทœ ํ‘œํ˜„์‹ (Pattern, Matcher)

# ์ •๊ทœํ‘œํ˜„์‹

์ •๊ทœํ‘œํ˜„์‹(Regular Expression)์ด๋ž€ ์ปดํ“จํ„ฐ ๊ณผํ•™์˜ ์ •๊ทœ์–ธ์–ด๋กœ๋ถ€ํ„ฐ ์œ ๋ž˜ํ•œ ๊ฒƒ์œผ๋กœ ํŠน์ •ํ•œ ๊ทœ์น™์„ ๊ฐ€์ง„ ๋ฌธ์ž์—ด์˜ ์ง‘ํ•ฉ์„ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์“ฐ์ด๋Š” ํ˜•์‹์–ธ์–ด์ด๋‹ค. ์ •ํ•ด์ง„ ํ˜•์‹์— ๋งž๋Š”์ง€ ๊ฒ€์ฆํ•ด์•ผํ• ๋•Œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

# ์ž๋ฐ”์—์„œ์˜ ์ •๊ทœํ‘œํ˜„์‹

  • ์ •๊ทœ ํ‘œํ˜„์‹์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ž๋ฐ” API java.util.regex ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

# Pattern ํด๋ž˜์Šค

import java.util.regex.Pattern;

public class RegexExample {
	public static void main(String[] args)  {
    
            String pattern = "^[0-9]*$"; //์ˆซ์ž๋งŒ
            String val = "123456789"; //๋Œ€์ƒ๋ฌธ์ž์—ด
        
            boolean regex = Pattern.matches(pattern, val);
            System.out.println(regex);
    }
}
  • ๋ฌธ์ž์—ด ํŒจํ„ด ๊ฒ€์ฆ์€, Pattern ํด๋ž˜์Šค์˜ matches() ๋ฉ”์†Œ๋“œ๋ฅผ ํ™œ์šฉ.

# Pattern ํด๋ž˜์Šค์˜ ์ฃผ์š” ๋ฉ”์„œ๋“œ

  • compile(String regex) : ์ฃผ์–ด์ง„ ์ •๊ทœํฌํ˜„์‹์œผ๋กœ๋ถ€ํ„ฐ ํŒจํ„ด์„ ์ƒ์„ฑ
  • matcher(CharSequence input) : ๋Œ€์ƒ ๋ฌธ์ž์—ด์ด ํŒจํ„ด๊ณผ ์ผ์น˜ํ•  ๊ฒฝ์šฐ true๋ฅผ ๋ฐ˜ํ™˜.
  • asPredicate() : ๋ฌธ์ž์—ด์„ ์ผ์น˜์‹œํ‚ค๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ ์–ด๋ฅผ ์ž‘์„ฑ.
  • pattern() : ์ปดํŒŒ์ผ๋œ ์ •๊ทœํ‘œํ˜„์‹์„ String ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜.
  • split(CharSequence input) : ๋ฌธ์ž์—ด์„ ์ฃผ์–ด์ง„ ์ธ์ž๊ฐ’ CharSequence ํŒจํ„ด๋ฐ ๋”ฐ๋ผ ๋ถ„๋ฆฌ.

# Matcher ํด๋ž˜์Šค

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexExample {
	public static void main(String[] args)  {
            Pattern pattern = Pattern.compile("^[a-zA-Z]*$"); //์˜๋ฌธ์ž๋งŒ
            String val = "abcdef"; //๋Œ€์ƒ๋ฌธ์ž์—ด
	
            Matcher matcher = pattern.matcher(val);
            System.out.println(matcher.find());
	}
}
  • Matcher ํด๋ž˜์Šค๋Š” ๋Œ€์ƒ ๋ฌธ์ž์—ด์˜ ํŒจํ„ด์„ ํ•ด์„ํ•˜๊ณ  ์ฃผ์–ด์ง„ ํŒจํ„ด๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํŒ๋ณ„ํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉ.

# Matcher ํด๋ž˜์Šค ์ฃผ์š” ๋ฉ”์„œ๋“œ

  • matches() : ๋Œ€์ƒ ๋ฌธ์ž์—ด๊ณผ ํŒจํ„ด์ด ์ผ์น˜ํ•  ๊ฒฝ์šฐ true ๋ฐ˜ํ™˜.
  • find() : ๋Œ€์ƒ ๋ฌธ์ž์—ด๊ณผ ํŒจํ„ด์ด ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ , ๊ทธ ์œ„์น˜๋กœ ์ด๋™.
  • find(int start) : start์œ„์น˜ ์ดํ›„๋ถ€ํ„ฐ ๋งค์นญ๊ฒ€์ƒ‰์„ ์ˆ˜ํ–‰.
  • start() : ๋งค์นญ๋˜๋Š” ๋ฌธ์ž์—ด ์‹œ์ž‘์œ„์น˜ ๋ฐ˜ํ™˜.
  • start(int group) : ์ง€์ •๋œ ๊ทธ๋ฃน์ด ๋งค์นญ๋˜๋Š” ์‹œ์ž‘์œ„์น˜ ๋ฐ˜ํ™˜.
  • end() : ๋งค์นญ๋˜๋Š” ๋ฌธ์ž์—ด ๋ ๋‹ค์Œ ๋ฌธ์ž์œ„์น˜ ๋ฐ˜ํ™˜.
  • end(int group) : ์ง€์ •๋˜ ๊ทธ๋ฃน์ด ๋งค์นญ๋˜๋Š” ๋ ๋‹ค์Œ ๋ฌธ์ž์œ„์น˜ ๋ฐ˜ํ™˜.
  • group() : ๋งค์นญ๋œ ๋ถ€๋ถ„์„ ๋ฐ˜ํ™˜.
  • group(int group) : ๋งค์นญ๋œ ๋ถ€๋ถ„์ค‘ group๋ฒˆ ๊ทธ๋ฃนํ•‘ ๋งค์นญ๋ถ€๋ถ„ ๋ฐ˜ํ™˜.
  • groupCount() : ํŒจํ„ด๋‚ด ๊ทธ๋ฃนํ•‘ํ•œ(๊ด„ํ˜ธ์ง€์ •) ์ „์ฒด ๊ฐฏ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜.

# 0903 - Bean Validation ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ”„๋ ˆ์ž„์›Œํฌ ์‚ฌ์šฉํ•˜๊ธฐ.

# ์„ค์น˜

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

# ์ œ์•ฝ ์„ค์ •๊ณผ ๊ฒ€์‚ฌ

public class CreateContact {
    @Length(max = 64) // ์ตœ๋Œ€ ๊ธธ์ด 64
    @NotBlank // ๋นˆ๋ฌธ์ž์—ด์€ ์•ˆ๋จ
    private String uid;
    @NotNull // null ์•ˆ๋จ
    private ContactType contactType;
    @Length(max = 1_600) // ์ตœ๋Œ€ ๊ธธ์ด 1,600
    private String contact;
}
  • ๋„๋ฉ”์ธ ๊ฒ€์ฆ
 @BeforeClass
    public static void beforeClass() {
        Locale.setDefault(Locale.US); // locale ์„ค์ •์— ๋”ฐ๋ผ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค.
    }

    @Test
    public void test_validate() {
        // Given
        final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

        final CreateContact createContact = CreateContact
            .builder()
            .uid(null) // @NotBlank๊ฐ€ ์ •์˜๋˜์–ด ์žˆ๊ธฐ๋•Œ๋ฌธ์— null์ด ์˜ค๋ฉด ์•ˆ๋œ๋‹ค.
            .contact("000")
            .contactType(ContactType.PHONE_NUMBER)
            .build();

        // When
        final Collection<ConstraintViolation<CreateContact>> constraintViolations = validator.validate(createContact);

        // Then
        assertEquals(1, constraintViolations.size());  // ConstraintViolation์— ์‹คํŒจ์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด๋‹ค.
        assertEquals("must not be blank", constraintViolations.iterator().next().getMessage());
    }
  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฒ€์ฆ ์ฝ”๋“œ

# Spring์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ

  • Service๋‚˜ Bean์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” @Validated์™€ @Valid๋ฅผ ์ถ”๊ฐ€
@Validated // ์—ฌ๊ธฐ์— ์ถ”๊ฐ€
@Service
public class ContactService {
    public void createContact(@Valid CreateContact createContact) { // '@Valid'๊ฐ€ ์„ค์ •๋œ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.
        // Do Something
    }
}
  • Controller์—์„œ๋Š” @Validated๊ฐ€ ํ•„์š” ์—†๋‹ค. ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•  ๊ณณ์— '@Valid'๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.
    @PostMapping("/contacts")
    public Response createContact(@Valid CreateContact createContact) { // ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ง„ํ–‰
        return Response
            .builder()
            .header(Header
                .builder()
                .isSuccessful(true)
                .resultCode(0)
                .resultMessage("success")
                .build())
            .build();
    }

# ์ปจํ…Œ์ด๋„ˆ์—์„œ์˜ ์‚ฌ์šฉ(์ปฌ๋ ‰์…˜, ๋งต, ...)

public class DeleteContacts {
    @Min(1)
    private Collection<@Length(max = 64) @NotBlank String> uids;
}

# ์‚ฌ์šฉ์ž ์ •์˜ ์ œ์•ฝ(Custom Constraint)

  • ์ž„์˜์˜ ์ œ์•ฝ(Constraint)๊ณผ ๊ฒ€์ฆ์ž(Validator)๋ฅผ ๊ตฌํ˜„
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Constraint(validatedBy = NoEmojiValidator.class)
@Documented
public @interface NoEmoji{
    String message() default "Emoji is not allowed";

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

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

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @interface List{
        NoEmoji[] value();
    }
    public class NoEmojiValidator implements ConstraintValidator<NoEmoji, String> {
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
        if (StringUtils.isEmpty(value) == true) {
            return true;
        }

        return EmojiParser.parseToAliases(value).equals(value);
        }
    }
}
public class CreateContact {
    @NoEmoji
    @Length(max = 64)
    @NotBlank
    private String uid;
    @NotNull
    private ContactType contactType;
    @Length(max = 1_600)
    private String contact;
}

# ์ œ์•ฝ ๊ทธ๋ฃน(Grouping)

public class Message {
    @Length(max = 128)
    @NotEmpty
    private String title;
    @Length(max = 1024)
    @NotEmpty
    private String body;
    @Length(max = 32, groups = Ad.class)
    @NotEmpty(groups = Ad.class)  // ๊ทธ๋ฃน์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. (๊ธฐ๋ณธ ๊ฐ’์€ javax.validation.groups.Default)
    private String contact;
    @Length(max = 64, groups = Ad.class)
    @NotEmpty(groups = Ad.class)
    private String removeGuide;
}
public interface Ad {
}
  • 'Ad.class'๋Š” ๋‹จ์ˆœํžˆ ๊ทธ๋ฃน์„ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•œ ๋งˆ์ปค ์ธํ„ฐํŽ˜์ด์Šค(Marker Interface)๋‹ค.
@Validated
@Service
public class MessageService {
    @Validated(Ad.class) // ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ Ad ๊ทธ๋ฃน์ด ์ง€์ •๋œ ์ œ์•ฝ๋งŒ ๊ฒ€์‚ฌํ•œ๋‹ค.
    public void sendAdMessage(@Valid Message message) {
        // Do Something
    }

    public void sendNormalMessage(@Valid Message message) {
        // Do Something
    }

    /**
     * ์ฃผ์˜: ์ด๋ ‡๊ฒŒ ํ˜ธ์ถœํ•˜๋ฉด Spring AOP Proxy ๊ตฌ์กฐ์ƒ @Valid๋ฅผ ์„ค์ •ํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์–ด๋„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค. 
     * Spring์˜ AOP Proxy ๊ตฌ์กฐ์— ๋Œ€ํ•œ ์„ค๋ช…์€ ๋‹ค์Œ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•˜์ž.
     * - https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#aop-understanding-aop-proxies
     */
    public void sendMessage(Message message, boolean isAd) {
        if (isAd) {
            sendAdMessage(message);
        } else {
            sendNormalMessage(message);
        }
    }

# ํด๋ž˜์Šค ๋‹จ์œ„ ์ œ์•ฝ(Class Level Constraint)๊ณผ ์กฐ๊ฑด๋ถ€ ๊ฒ€์‚ฌ(Conditional Validation)

  • ๋„๋ฉ”์ผ ๋ณด๋ธ์˜ ์†์„ฑ๊ธ” ๊ฐ’์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ์œ ํ˜€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ํ•ด์•ผํ•  ๊ฒฝ์šฐ์— ์‚ฌ์šฉ
@AdMessageConstraint // ์ด ์ปค์Šคํ…€ ์ œ์•ฝ์„ ๊ตฌํ˜„ํ•  ๊ฒƒ์ด๋‹ค.
public class Message {
    @Length(max = 128)
    @NotEmpty
    private String title;
    @Length(max = 1024)
    @NotEmpty
    private String body;
    @Length(max = 32, groups = Ad.class)
    @NotEmpty(groups = Ad.class)
    private String contact;
    @Length(max = 64, groups = Ad.class)
    @NotEmpty(groups = Ad.class)
    private String removeGuide;
    private boolean isAd; // ๊ด‘๊ณ  ์—ฌ๋ถ€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์†์„ฑ
}
@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = AdMessageConstraintValidator.class)
@Documented
public @interface AdMessageConstraint {
    String message() default "";

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

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

    public class AdMessageConstraintValidator implements ConstraintValidator<AdMessageConstraint, Message> {
        private Validator validator;

        public AdMessageConstraintValidator(Validator validator) {
            this.validator = validator;
        }

        @Override
        public boolean isValid(Message value, ConstraintValidatorContext context) {
            if (value.isAd()) {
                final Set<ConstraintViolation<Object>> constraintViolations = validator.validate(value, Ad.class);
                if (CollectionUtils.isNotEmpty(constraintViolations)) {
                    context.disableDefaultConstraintViolation();
                    constraintViolations
                            .stream()
                            .forEach(constraintViolation -> {
                                context.buildConstraintViolationWithTemplate(constraintViolation.getMessageTemplate())
                                        .addPropertyNode(constraintViolation.getPropertyPath().toString())
                                        .addConstraintViolation();
                            });
                    return false;
                }
            }

            return true;
        }
    }
}
@Validated
@Service
public class MessageService {
    /**
     * message.isAd๊ฐ€ true์ด๋ฉด contcat, removeGuide ์†์„ฑ๊นŒ์ง€ ๊ฒ€์‚ฌํ•œ๋‹ค.
     */
    public void sendMessage(@Valid Message message) {
         // Do Something
    }

# ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ(Error Handling)

  • ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹œ ์‹คํŒจ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ConstraintViolationException ๋ฐœ์ƒ. ์ด๋ฅผ @ControllerAdvice ๊ตฌํ˜„ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @ExceptionHandler ํ•ธ๋“ค๋Ÿฌ ๊ตฌํ˜„
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(value = ConstraintViolationException.class) // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ
    @ResponseBody
    protected Response handleException(ConstraintViolationException exception) {
        return Response
            .builder()
            .header(Header
                .builder()
                .isSuccessful(false)
                .resultCode(-400)
                .resultMessage(getResultMessage(exception.getConstraintViolations().iterator())) // ์˜ค๋ฅ˜ ์‘๋‹ต์„ ์ƒ์„ฑ
                .build())
            .build();
    }

    protected String getResultMessage(final Iterator<ConstraintViolation<?>> violationIterator) {
        final StringBuilder resultMessageBuilder = new StringBuilder();
        while (violationIterator.hasNext() == true) {
            final ConstraintViolation<?> constraintViolation = violationIterator.next();
            resultMessageBuilder
                .append("['")
                .append(getPopertyName(constraintViolation.getPropertyPath().toString())) // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์‹คํŒจํ•œ ์†์„ฑ
                .append("' is '")
                .append(constraintViolation.getInvalidValue()) // ์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฐ’
                .append("'. ")
                .append(constraintViolation.getMessage()) // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹คํŒจ ์‹œ ๋ฉ”์‹œ์ง€
                .append("]");

            if (violationIterator.hasNext() == true) {
                resultMessageBuilder.append(", ");
            }
        }

        return resultMessageBuilder.toString();
    }

    protected String getPopertyName(String propertyPath) {
        return propertyPath.substring(propertyPath.lastIndexOf('.') + 1); // ์ „์ฒด ์†์„ฑ ๊ฒฝ๋กœ์—์„œ ์†์„ฑ ์ด๋ฆ„๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค.
    }
}

# ๋™์  ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ(Message Interpolation)

...
public @interface NoEmoji{
    String message() default "Emoji is not allowed";

    ...
  • ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    1. '{}'๋˜๋Š” '${}'๋กœ ๋‘˜๋Ÿฌ์‹ผ๋‹ค.
    2. {,},,$๋Š” ๋ฌธ์ž๋กœ ์ทจ๊ธ‰.
    3. '{'๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ์‹œ์ž‘, '}'๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์˜ ๋, \๋Š” ํ™•์žฅ๋ฌธ์ž, '$'๋Š” ํ‘œํ˜„์‹ ์‹œ์ž‘์œผ๋กœ ์ทจ๊ธ‰.

# ์ฐธ๊ณ 

https://meetup.toast.com/posts/223

Last update: September 13, 2021 02:20
Contributors: ahnjs , jaesungahn91