DongDD's IT

[Spring] Spring Web Application, Controller, Exception Handler 본문

프로그래밍/Spring

[Spring] Spring Web Application, Controller, Exception Handler

DongDD 2019. 4. 2. 21:04

[Spring] Spring Web Application



Web Application



종류


1. 화면으로 응답하는 어플리케이션

- jsp 등을 이용해 클라이언트에게 동적 웹 페이지 형태로 응답


2. 데이터로 응답하는 어플리케이션

- JSON, xml을 사용해 데이터 형태로 클라이언트에 응답

- RESTful API



Controller



처리


1. 선언형 처리

- method 시그니처를 참조해서 front controller가 하는 일

- 요청 매핑, 요청 데이터 취득, 입력값 검증


2. 프로그래밍형 처리

- controller 클래스의 method 안에 하는 일

- 입력값 검증 확인, 비즈니스 로직 호출, 이동대상 확인/데이터 연계/지정


Controller 작성


1
2
3
4
@Controller
public class TestController {
 
}
cs

- @Controller로 controller 지정

-> @ComponentScan을 통해 DI 컨테이너에 빈으로 등록(Boot에서는 자동)


Component Scan


1
2
3
4
5
6
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.mvc")
public class WebMvcConfig implements WebMvcConfigurer  {
 
}
cs

- @ComponentScan("package_path")를 설정하여 Controller로 지정된 bean을 탐색하게 함


Handler Method


1
2
3
4
5
6
7
@Controller
public class TestController { 
    @RequestMapping(path = "/", method = RequestMethod.GET)
    public String main() {
       return "index";
    }
}
cs

- @RequestMapping으로 메소드를 지정하여 handler method로 인식하게 함


1. Annotation 종류

1) @PathVariable

- URI에 있는 변수 값을 가져옴


2) @RequestParam

- 요청 파라미터 값을 가져옴


3) @RequestHeader

- 요청 헤더 값을 가져옴


4) @RequestBody

- 요청 Body 값을 가져옴


5) @CookieValue

- 쿠키 값을 가져옴


ex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class AnnoController {
    @RequestMapping(path ="/anno/{urlInput}", method = RequestMethod.GET)
    public void anno(@PathVariable String urlInput,
                     @RequestHeader HttpHeaders requestHeaders,
                     @RequestBody String requestBody,
                     @RequestParam("a"int a,
                     @RequestParam("b"String b) {
        System.out.println(urlInput);
        System.out.println(requestHeaders.getHost());
        System.out.println(a);
        System.out.println(b);
    }
}
cs

- brace로 경로에 변수를 넣고 @PathVariable을 이용해 값을 가져옴

- @RequestBody 같은 경우, 일반적으로 객체를 생성하여 받음(String도 가능함)

- @RequestHeader는 HttpHeaders를 사용하여 data 사용

- @RequestParam은 받을 parameter를 명시하고 데이터로 받음

- 결과

1

2. 반환 값

1) Type

- String : View 이름 반환

- Model : 데이터 반환

- ModelAndView : View 이름과 데이터 반환

- void : Response 객체에 데이터를 쓰거나 view 이름을 결정할 때 사용

- ResponseEntity : 응답 헤더와 본문에 직렬화될 객체 반환

- HttpHeaders : 응답 헤더 반환


2) Annotation

- @ModelAttribute : Model에 저장한 객체 반환

- @ResponseBody : 응답 본문에 직렬화한 객체 반환

ex)

1
2
3
4
5
6
7
8
@RequestMapping(path ="/anno/{urlInput}", method = RequestMethod.GET)
@ResponseBody
public String anno(@PathVariable String urlInput,
                 @RequestHeader HttpHeaders requestHeaders,
                 @RequestParam("a"int a,
                 @RequestParam("b"String b) {
    return urlInput;
}
cs

- handler에 requestmapping 지정 후, @ResponseBody 사용

- Response Body에 urlInput 값을 넣어서 return

- 결과

2

Request Mapping


- 속성 값을 통해 요청 매핑 조건 지정


속성


- value : 요청 경로 지정

- path : 요청 경로 지정

- method : 요청 method 지정

- produces : 특정 media type의 accept를 가진 요청만 받음, 응답을 특정 media type으로 함

- consumes : 특정 media type의 content-type을 가진 요청만 받음

- params : 요청 파라미터 값 지정

- headers : 헤더 값 지정

- name : request mapping의 이름 지정

ex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(path = "/anno/{urlInput}", method = RequestMethod.GET,
                produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.TEXT_PLAIN_VALUE,
                name = "annoController", params = {"a=1","b=2"})
@ResponseBody
public String anno(@PathVariable String urlInput,
                 @RequestHeader HttpHeaders requestHeaders,
                 @RequestParam("a"int a,
                 @RequestParam("b"String b) {
    System.out.println(urlInput);
    System.out.println(requestHeaders.getHost());
    System.out.println(a);
    System.out.println(b);
    return urlInput;
}
cs

- method : GET method 요청을 받음

3

- consumes : 요청 header의 content-type이 text/plain인 것만 받음

- produces : 응답 header의 content-type을 application/json으로 보냄

- name : request mapping의 이름을 지정

- params : parameter가 a=1, b=2인 것만 받음


Binding


- 요청 데이터를 특정 method의 파라미터로 binding

- WebDataBinder를 사용


1. InitBinder 사용

1) addCustomFormatter

1
2
3
4
5
6
public class BinderConfig {
    @InitBinder("dateTime")
    public void initBinder(WebDataBinder webDataBinder) {
        webDataBinder.addCustomFormatter(new DateFormatter("yyyyMMdd"));
    }
}
cs

- dateTime이라는 parameter를 "yyyyMMdd"의 포맷으로 바인딩

2) registerCustomEditor

1
2
3
4
5
6
7
public class BinderConfig {
    @InitBinder("dateTime")
    public void initBinder(WebDataBinder webDataBinder) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
        webDataBinder.registerCustomEditor(Date.class,
new CustomDateEditor(simpleDateFormat, false));
    }
}
cs
- dateTime이라는 parameter를 "yyyyMMdd"의 포맷으로 바인딩


2. Annotation 사용

- @DateTimeFormat을 사용하여 datetime binding

- @NumberFormat을 사용하여 숫자 binding

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping(path = "/anno/{urlInput}", method = RequestMethod.GET)
@ResponseBody
public String anno(@PathVariable String urlInput,
                 @RequestHeader HttpHeaders requestHeaders,
                 @DateTimeFormat(pattern = "yyyyMMdd") @RequestParam Date a,
                 @RequestParam("b"String b) {
    System.out.println(urlInput);
    System.out.println(requestHeaders.getHost());
    System.out.println(a);
    System.out.println(b);
    return a;
}
cs
- 결과

4

Validation



Validation


- 매개변수에 @Validated 또는 @Valid를 사용하여 입력 값 검증

1
2
3
4
5
6
7
@RequestMapping(path = "/anno1", method = RequestMethod.POST)
@ResponseBody
public TestData anno1(@Validated @RequestBody TestData testData) {
    System.out.println(testData.getId());
    System.out.println(testData.getMsg());
    return testData;
}
cs

- TestData 객체의 Id에는 @NotEmpty가 걸려있음

- @Validated를 설정하면, 해당 값이 Empty로 올 경우, 에러 출력


Validation rule annotation


1. Null 검사

- @NotNull

- null이 들어오면 error

- 속성 : message(Null이 들어왔을 때, 해당 메시지로 에러 출력)


2. 빈 값 검사

- @NotEmpty

- null, 빈 값("")이 들어오면 error


3. Blank 검사

- @NotBlank

- null, 빈 값(""), blank(" ")가 들어오면 error


4. Size 검사

- @Size

- 속성 : max, min

- 최대 값, 최소 값을 지정


5. Pattern 검사

- @Pattern

- 특정 패턴을 검사

- 속성 : regexp(정규 표현식)


6. DateTime 검사

- @DateTimeFormat

- 속성 : pattern(해당 패턴의 날짜가 들어와야 함)

- @Past : 과거 날짜여야함


7. Boolean 검사

- @AssertTrue : True 값이 들어와야 함


Spring Validator


- Spring에서 지원하는 Spring Validator의 인터페이스를 이용하여 구현

- method : supports(Class<?> clazz), validate(Object target, Errors errors);

- Validator 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class TestDataValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return TestData.class.isAssignableFrom(clazz);
    }
 
    @Override
    public void validate(Object target, Errors errors) {
        TestData testData = (TestData) target;
 
        if(testData.getId() == null) {
            errors.rejectValue("id""id가 null 입니다.");
        }
    }
}
cs

- supports method에는 원하는 Class를 위와 같이 선언

- validate는 검증할 로직을 구현

- Controller 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private TestDataValidator testDataValidator;
 
@InitBinder()
public void initBinder(WebDataBinder webDataBinder) {
    webDataBinder.addValidators(testDataValidator);
}
 
@RequestMapping(path = "/valid", method = RequestMethod.POST)
public void val(@Validated @RequestBody TestData testData) {
    System.out.println(testData.getId());
    System.out.println(testData.getMsg());
}
cs

- initBinder로 구현한 validator를 추가

- @Validated 어노테이션으로 requestbody(TestData)를 검증

- Validator 사용 시, @NotNull을 사용하지 않아도 Validator에서 검증

- null값을 넣었을 때 결과

5



예외 처리



Spring Exception Handler


- 컨트롤러, 컨트롤러 이후에 발생하는 예외를 처리하기 위해 Spring에서 제공

- DispathcerServlet이 예외를 발견하고 HandlerExceptionResolver에서 예외 처리


1. HandlerExceptionResolver 구현 클래스(예외 처리 순서 : 1 → 2 → 3)

1) ExceptionHandlerExceptionResolver

- @ExceptionHandler 어노테이션을 붙이고 메소드를 구현하여 예외 처리


2) ResponseStatusExceptionResolver

- @ResponseStatus를 붙인 클래스를 작성하여 예외 처리


3) DefaultHandlerExceptionResolver

- Front Controller에서 발생하는 예외 처리


→ 1,2,3에서 처리되지 않는 경우, NestedServletException 예외 발생


Exception Handler


- Exception Handler 구현

1
2
3
4
5
6
7
8
9
@ControllerAdvice
@ResponseBody
public class TestExceptionHandler {
 
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public String handleTypeException() {
        return "exception";
    }
}
cs

- @ControllerAdvice로 Exception Handler 지정

- @ExceptionHandler(예외)로 어떤 exception이 발생했을 때, 해당 method가 실행될지 결정

- 결과

6

- 존재하지 않는 GET handler에 대한 요청 시, ExceptionHandler가 해당 exception을 잡아 "exception" 출력

- 상태 코드는 지정하지 않아 200


ResponseStatus


- 예외 처리한 내용에 원하는 status code를 사용하게 해줌

1
2
3
4
5
6
7
8
9
10
@ControllerAdvice
@ResponseBody
public class TestExceptionHandler {
 
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public String handleTypeException() {
        return "exception";
    }
}
cs

- 해당 예외 발생 시, Status code를 409(CONFLICT)로 설정하여 응답

- 결과

7


- 같은 결과지만, 409 conflict 응답


Comments