DongDD's IT

[Spring] Spring MVC - Session, Async, 공통 처리, Locale 본문

프로그래밍/Spring

[Spring] Spring MVC - Session, Async, 공통 처리, Locale

DongDD 2019. 4. 14. 21:18

[Spring] Spring MVC - Session, Async, 공통 처리, Locale




HTTP Session


- 여러 요청이 같은 데이터를 공유할 수 있어야함 → HTTP session을 이용해 해결할 수 있음

- @SessionAttributes, Session Scope, HttpSession API 3가지 방법이 있음


SessionAttributes


- @SessionAttributes 사용

- HttpSession API를 직접 사용하지 않고 HTTP session에서 관리

- 하나의 컨트롤러에서 여러 요청 간 데이터 공유에 효과적


1. Session에서 관리할 대상 객체 지정

1
2
3
4
5
6
@Controller
@RequestMapping("/test1")
@SessionAttributes(types = TestModel.class, names = "id")
public class TestController1 {
    //
}
cs

- types : 세션에 저장할 객체 지정

- names : 세션에 저장할 객체의 변수 지정



2. @ModelAttribute
- Session에서 관리하고 싶은 경우, Model에 저장
- @ModelAttribute가 지정된 method에서 반환된 객체가 Model에 저장
ex)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller
@RequestMapping("/test1")
@SessionAttributes(types = TestModel.class)
public class TestController1 {
    @ModelAttribute("testModel")
    public TestModel createSession() {
        return new TestModel();
    }
    @RequestMapping(value = "/", method = RequestMethod.POST)
    public String create(@ModelAttribute("testModel") TestModel testModel) {
        System.out.println(testModel.getId());
        System.out.println(testModel.getName());
        return "aa";
    }
 
    @RequestMapping(value = "/", method = RequestMethod.GET)
    @ResponseBody
    public String getId(@ModelAttribute("testModel") TestModel testModel) {
        System.out.println(testModel.getId());
        System.out.println(testModel.getName());
        return testModel.getId().toString();
    }
}
cs

- @SessionAttributes를 이용해 Testmodel.class를 세션에 저장

- @ModelAttribute를 이용해 "testModel"이라는 모델에 TestModel 객체 저장

- POST 메소드와 form 형식으로 testModel을 전송하면 "testModel" 모델에 해당 값들이 저장

- 반환 값으로 "aa" 페이지와 Cookie로  sessionId 반환(JSESSIONID)

- GET 방식으로 요청 시, 세션과 모델에 저장된 "testModel"을 불러와 id를 전송

- POST 결과

1

- GET 결과

2


3. Session 삭제

1
2
3
4
5
6
@RequestMapping(value = "/", method = RequestMethod.DELETE)
@ResponseBody
public String deleteSession(SessionStatus sessionStatus) {
    sessionStatus.setComplete();
    return "complete";
}
cs

- handler인자로 SessionStatus를 받음

- setComplete() method로 session을 delete

- 결과

3

4


Session Scope Bean

- 컨트롤러 간 데이터를 공유하는 매개체 역할

- 세션 스코프 빈 객체의 라이프 사이클은 HTTP Session 라이프 사이클과 같음


1. 세션 스코프 Model 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionModel {
    private Long id;
    private String name;
 
    public SessionModel() {
    }
 
    public SessionModel(Long id, String name) {
        this.id = id;
        this.name = name;
    }
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
cs

- @Component를 사용하여 빈 등록

- @Scope를 이용하여 Session Scope로 설정


2. Controller에서 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Controller
@RequestMapping(value = "/session")
public class SessionController {
    @Autowired
    private SessionModel sessionModel;
 
    @RequestMapping(value = "/", method = RequestMethod.POST)
    @ResponseBody
    public String create(@Validated @RequestBody SessionModel model) {
        sessionModel = model;
        System.out.println(sessionModel.getId());
        System.out.println(sessionModel.getName());
        return "complete";
    }
 
    @RequestMapping(value = "/", method = RequestMethod.GET)
    @ResponseBody
    public String getId() {
        return sessionModel.getId().toString();
    }
}
cs

- session scope로 지정하고 bean으로 등록한 SessionModel을 @Autowired로 주입

- POST 메소드를 통해 받은 데이터를 저장

- Response에 Cookie값 전송(JSESSIONID)

- GET 메소드를 통해 저장했던 데이터를 가져옴

- 결과

5

6




Async(비동기 처리)


동작 방식


1. 비동기 실행 종료 후 응답

- Resource가 많이 필요한 작업의 경우, 분리된 스레드에서 해당 작업을 수행하게 함

- 비동기 처리 후, 응답이 가기때문에 Client에는 동기와 비슷하게 느껴짐

1) MVC의 스레드에서 처리

- Callable, WebAsyncTask 타입 반환


2) MVC외의 스레드에서 처리

- DeferredResult, ListenableFuture, CompletableFuture 타입 반환


2. 비동기 실행 중 응답

- 임의의 타이밍에 데이터를 전송하기 싶은 경우에 사용

- 비동기 처리 시점에 응답을 한번한 후에 처리 중 응답 데이터를 전송

- Client가 'Transfer-Encoding: chunked'를 지원해야함

1) Long Polling 비동기 처리

- ResponseBodyEmitter 타입 반환


2) SSE 비동기 처리

- SseEmitter 타입 반환

- Client가 SSE를 지원해야함

- SSE(Server Sent Event)(12,b) : Client가 요청없이 서버로부터 자동으로 Data를 받을 수 있음


Async


- method에 @Async를 사용하여 비동기 처리를 하도록 만듬

- 같은 클래스에 있는 method에 @Async 사용 시, 동작하지 않음


1. Bean 정의

- @Async 사용을 위한 Bean 정의

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(3);
        threadPoolTaskExecutor.setMaxPoolSize(5);
        threadPoolTaskExecutor.setQueueCapacity(10);
        return threadPoolTaskExecutor;
    }
}
cs

- @Configuration을 설정하고 @EnableAsync로 @Async 활성화

- TaskExecutor를 Bean으로 지정해 요청마다 thread pool을 생성

- setCorePoolSize : 기본 thread pool size

- setMaxPoolSize : 최대 thread pool size

- setQueueCapacity : MaxPoolSize로 동작하는 경우, 대기하는 queue size


2. Async 처리 후 응답

1) Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping(value = "/async")
public class AsyncController {
 
    @Autowired
    private AsyncService asyncService;
 
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public CompletableFuture<String> get() {
        System.out.println(Thread.currentThread().getName());
        try {
            return asyncService.sleep();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
cs

- AsyncService를 Autowired로 주입받고 해당 service에 있는 sleep method 실행

- 반환 값으로 CompletableFuture를 사용하고 최종적으로 보내질 응답 값이 String이기 때문에 Completable<String> 반환

- return asyncService.sleep()으로 service 수행


2) Service

1
2
3
4
5
6
7
8
9
@Service
public class AsyncService {
    @Async
    public CompletableFuture<String> sleep() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName());
        return CompletableFuture.completedFuture("complete");
    }
}
cs

- Thread.sleep(3000)으로 3초를 기다린 후, CompletableFuture<String> 반환

7

- 다른 thread에서 수행되지만, 해당 thread의 return 값을 기다린 후, 응답 값을 보냄


3. SseEmitter


- SseEmitter에 send를 통해 client의 요청없이 server에서 지속적으로 data를 내려줌

- AsyncController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping(value = "/async")
public class AsyncController {
 
    @Autowired
    private AsyncService asyncService;
     
    @RequestMapping(value = "/",
method = RequestMethod.GET,
produces= MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter sseEmitter() {
        SseEmitter sseEmitter = new SseEmitter();
        asyncService.emit(sseEmitter);
        return sseEmitter;
    }
}
cs

- SseEmitter를 반환 값으로 함

- SseEmitter를 생성하고 AsyncService의 emit method 실행

- AsyncService - emit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class AsyncService {
 
    @Async
    public void emit(ResponseBodyEmitter emitter) {
        try {
            emitter.send("aaa");
            Thread.sleep(2000);
            emitter.send("bbb");
            Thread.sleep(2000);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        emitter.complete();
    }
}
cs

- "aaa"를 보내고 2초간 sleep

- "bbb"를 보내고 2초간 sleep 후 complete를 이용해 emitter 종료    

- 결과

8

9

- spring mvc에서 SseEmitter 사용 시, String 변환에 문제가 있었음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class WebMvcConfig implements WebMvcConfigurer {
 
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(jackson2HttpMessageConverter());
        converters.add(stringHttpMessageConverter());
    }
 
    @Bean
    public StringHttpMessageConverter stringHttpMessageConverter() {
        return new StringHttpMessageConverter();
    }
}
cs

- StringHttpMessageConverter Bean을 생성하고

- converter에 추가하여 해결


Async 공통 처리


- CallableProcessingInterceptor, DeferredResultProcessingInterceptor 구현체를 이용


1. method

- beforeConcurrentHandling : Async 실행 전 호출

- preProcess : Async 실행 후 호출

- postProcess : Async 처리 결과/예외 후 호출

- handleTimeout : Async가 Timeout되면 호출

- afterCompletion : Async 종료 시 호출

- handleError : Async Error 발생 시 호출


2. Interceptor 작성

1) CallableProcessingInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AsyncCallable implements CallableProcessingInterceptor {
    @Override
    public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception {
    }
 
    @Override
    public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
    }
 
    @Override
    public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception {
    }
 
    @Override
    public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
        return null;
    }
 
    @Override
    public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
        return null;
    }
 
    @Override
    public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
    }
}
cs


2) DeferredResultProcessingInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AsyncDeferred implements DeferredResultProcessingInterceptor {
    @Override
    public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
    }
 
    @Override
    public <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
    }
 
    @Override
    public <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object concurrentResult) throws Exception {
    }
 
    @Override
    public <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
        return false;
    }
 
    @Override
    public <T> boolean handleError(NativeWebRequest request, DeferredResult<T> deferredResult, Throwable t) throws Exception {
        return false;
    }
 
    @Override
    public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
    }
}
cs


3. Config 추가

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class WebMvcConfig implements WebMvcConfigurer {
 
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.registerCallableInterceptors(new AsyncCallable());
        configurer.registerDeferredResultInterceptors(new AsyncDeferred());
    }
}
cs

- 작성한 interceptor들을 등록




공통 처리


- Controller의 handler 호출 전후의 공통 처리


Servlet Filter


- DispathcerServlet 호출 전후의 공통 처리

- Spring에서 GenericFilterBean, OncePerRequestFilter 지원

1
2
3
4
5
6
7
8
9
public class CustomFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response,
                                    FilterChain filterChain) 
                                    throws ServletException, IOException {
        // implement
    }
}
cs

- OncePerRequestFilter를 상속받아 구현


HandlerInterceptor


- Controller에서 처리되는 내용들 중 공통 처리가 필요할 때 사용

- 요청 경로에 대한 handler method 결정된 후 호출


1. method

- preHandle : handler method 실행 전 호출

- postHandle : handler method 정상 종료 후 호출

- afterCompletion : handler method 종료 후 호출


2. interceptor 생성

1
2
3
4
5
6
7
8
9
10
11
public class CommonInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, 
                                                   HttpServletResponse response, 
                                                   Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        System.out.println(method.getName());
        return super.preHandle(request, response, handler);
    }
}
cs

- HandlerInterceptorAdapter를 상속받아 구현

- preHandle을 이용하여 handler 실행 전에 실행


3. Interceptor 등록

1
2
3
4
5
6
7
8
9
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CommonInterceptor());
    }
}
cs

- webmvc config 파일에 interceptor 추가

- 결과

10


ControllerAdvice


- Controller에 대한 특수한 method를 정의할 수 있음

- 일반적으로 ExceptionHandler 용도로 사용


1. RestControllerAdvice

1
2
3
4
5
6
7
8
@RestControllerAdvice
public class TestExceptionHandler {
 
    @ExceptionHandler(value = TestException.class)
    public ResponseEntity handleException(TestException e) {
        return ResponseEntity
.status(e.getHttpStatus())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(e.getMsg());
    }
}
cs

- 지난 번에 사용했던 exception, exception handler 사용


2. Option

11

- annotations : 해당 annoation이 붙은 controller에 대한 처리

- assignableTypes : 지정한 클래스나 인터페이스로 할당 가능한 controller에 대한 처리

- value : basePackages와 같음

- basePackages : package 경로 내에 있는 controller에 대한 처리

- basePackageClasses : 지정한 클래스나 인터페이스가 저장된 package 경로 내에 있는 controller에 대한 처리




Locale


- 웹 서비스에서 다국어를 지원하고 싶을 때 사용


LocaleResolver


- LocaleResolver의 구현 클래스를 이용해 locale 사용


1. 구현 클래스

- AcceptHeaderLocaleResolver : HTTP request의 Accept-Language 헤더 정보 이용

- SessionLocaleResolver : HTTP session의 locale 정보 이용

- CookieLocaleResolver : Cookie의 locale 정보 이용

- FixedLocaleResolver : Server의 Locale 설정을 따름(LocaleResolver default -> JVM -> OS)


2. Locale 사용

1
2
3
4
5
6
7
8
@RestController
@RequestMapping(value = "/locale")
public class AsyncController { 
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String locale(Locale locale) {
        return locale.getLanguage();
    }
}
cs

- handler의 인자로 Locale을 받아서 사용

- 결과

12


Comments