@Configuration, @Bean,
@EnableAsync, @Async,
@ExceptionHandler, @ResponseStatus, @ControllerAdvice,
@Controller, @Service, @Repository,
@RequestMapping, @GetMapping, @PostMapping ,
@DateTimeFormat
@Configuration, @Bean
@Configuration은 Spring Framework에서 사용되는 어노테이션 중 하나로, 해당 클래스가 Spring 애플리케이션 컨텍스트를 구성하는 데 사용된다는 것을 나타냅니다.
- @Configuration이 붙은 클래스는 Spring의 Java 기반 구성 방식 중 하나인 Java Config를 사용하여 Bean을 정의하고 구성할 수 있습니다.
- 이것은 XML 파일 대신 Java 클래스를 사용하여 애플리케이션 컨텍스트를 설정하는 방법입니다.
- @Configuration 클래스 내부에는 @Bean 어노테이션을 사용하여 Bean 정의를 포함할 수 있습니다. @Bean 어노테이션을 사용하면 해당 메서드가 생성한 객체가 Spring 컨테이너의 Bean으로 등록됩니다
- @Configuration없이 @Bean만 사용해도 스프링 빈으로 등록이 된다. 대신 메소드 호출을 통해 객체를 생성할 때 싱글톤을 보장하지 못합니다.
- @Configuration 어노테이션도 내부적으로 @Component를 가지고 있어 @Configuration이 붙은 클래스도 빈으로 등록됩니다.
@Configuration public class MvcConfig implements WebMvcConfigurer { /** * 기존 Spider WebInitialze에 있던 로직을 IBKServletContextListener로 변경 * * @return */ @Bean ServletListenerRegistrationBean<ServletContextListener> servletListener() { ServletListenerRegistrationBean<ServletContextListener> srb = new ServletListenerRegistrationBean<>(); srb.setListener(new DemoListener()); return srb; } }
.
@EnableAsync, @ASYNC
@EnableAsync로 @Async를 쓰겠다고 스프링에게 알립니다.
> executeAsync 호출 시 선 리턴 후 executeAsync() 개별 수행, executeSync 호출 시 메소드수행 후 리턴
Spring의 @Async 어노테이션은 메소드를 비동기적으로 실행할 수 있게 해주는 기능을 제공합니다. 이 어노테이션을 사용하면 메소드 호출이 별도의 스레드에서 비동기적으로 실행되며, 호출자는 해당 메소드의 실행 완료를 기다리지 않고 다른 작업을 계속할 수 있습니다.
@Async 어노테이션을 사용하기 위해서는 다음의 조건을 충족해야 합니다:
- 해당 메소드는 반드시 public이어야 합니다.
- 해당 메소드는 클래스 내부에서 호출되어야 합니다. 외부에서 같은 클래스 내의 @Async 메소드를 호출하면 비동기적으로 실행되지 않습니다.
- 해당 메소드는 다른 메소드에서 호출되어야 하며, 같은 클래스 내에서 this를 통해 호출되어야 합니다. 클래스 외부에서 같은 클래스의 메소드를 직접 호출하는 경우도 비동기적으로 실행되지 않습니다.
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Service; import java.util.concurrent.CompletableFuture; @SpringBootApplication @EnableAsync // 비동기 작업 활성화 @Slf4j public class AsyncExampleApplication implements CommandLineRunner { private final AsyncService asyncService; public AsyncExampleApplication(AsyncService asyncService) { this.asyncService = asyncService; } public static void main(String[] args) { SpringApplication.run(AsyncExampleApplication.class, args); } @Override public void run(String... args) throws Exception { log.info("Main thread starts."); // 비동기 메서드 호출 CompletableFuture<String> result = asyncService.performAsyncTask(); // 다른 작업 수행 log.info("Main thread continues to do other work..."); // 비동기 작업의 결과 대기 log.info("Async result: {}", result.get()); log.info("Main thread ends."); } } @Service @Slf4j class AsyncService { @Async public CompletableFuture<String> performAsyncTask() throws InterruptedException { log.info("Async task starts."); Thread.sleep(3000); // 3초 대기 (예: 네트워크 호출 시뮬레이션) log.info("Async task ends."); return CompletableFuture.completedFuture("Task Completed!"); } }
@Configuration @EnableAsync @Log4j2 public class AsyncConfig { @Bean(name = "threadPoolTaskExecutor", destroyMethod = "destroy") public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); //작업이 큐에 추가되면 스레드 풀에서 바로 작업을 수행할 수 있는 스레드 executor.setMaxPoolSize(10); //최대 쓰레드 수 executor.setQueueCapacity(500); //큐에 대기가능한 작업 수 executor.setThreadNamePrefix("demo-async-"); executor.setTaskDecorator(new AsyncDecorator()); executor.initialize(); return new HandlingExecutor(executor); } public class HandlingExecutor implements AsyncTaskExecutor { private AsyncTaskExecutor executor; public HandlingExecutor(AsyncTaskExecutor executor) { this.executor = executor; } @Override public void execute(Runnable task) { executor.execute(createWrappedRunnable(task)); } @Override public void execute(Runnable task, long startTimeout) { executor.execute(createWrappedRunnable(task), startTimeout); } @Override public Future<?> submit(Runnable task) { // TODO Auto-generated method stub return executor.submit(createWrappedRunnable(task)); } private Runnable createWrappedRunnable(final Runnable task) { return new Runnable() { HashMap map; public void run() { try { task.run(); FutureTask<HashMap> future = (FutureTask<HashMap>) task; if (future.isDone()) { map = future.get(); } log.debug("async end... map : " + map); } catch (Exception e) { e.printStackTrace(); } finally { MDC.clear(); } } }; }// end of createWrappedRunnable public void destroy() { if (executor instanceof ThreadPoolTaskExecutor) { ((ThreadPoolTaskExecutor) executor).shutdown(); } } } }
- FutureTask
FutureTask는 RunnableFuture의 구현체 이며 RunnableFuture는 Runnalble과 Future의 결합체 이다. 말 그대로 Future와 Task를 합친 말로 Task를 수행하고 결과를 Future로 받을 수 있습니다.
즉, 수행 Thread와 결과를 받는 Thread가 분리 되어 있는 구조를 가집니다.
- TaskDecorator
Spring 4.3 이상부터 제공되는 TaskDecorator 를 이용해서 비동기처리하는 taskExecutor 생성시 커스터마이징이 가능하다는 것을 찾게 되었습니다.
ThreadLocal Value 공유예시
package com.example.async; import lombok.extern.log4j.Log4j2; import org.slf4j.MDC; import org.springframework.core.task.TaskDecorator; import java.util.Map; @Log4j2 public class AsyncDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable task) { Map logCtx = MDC.getCopyOfContextMap(); log.info("############## logCtx : " + logCtx); return () -> { if (logCtx != null) MDC.setContextMap(logCtx); task.run(); }; } }
스프링은 @Async를 사용한 메서드를 별도의 프록시 객체로 생성하여 비동기 처리할 수 있도록 합니다.
그러나 같은 클래스 내부에서 @Async 메서드를 호출하면 프록시가 아닌 원본 객체가 호출되므로 비동기 처리가 되지 않습니다.
1. 예외 처리의 복잡성
- 설명: @Async 메서드에서 발생하는 예외는 호출한 쪽에서 바로 감지되지 않으며, 비동기 작업의 Future 객체를 통해 접근해야 합니다.
- 해결책: 예외가 발생했을 때 로깅을 남기거나, AsyncUncaughtExceptionHandler를 사용하여 처리할 수 있습니다.
@Async public CompletableFuture<String> asyncMethodWithException() { return CompletableFuture.supplyAsync(() -> { if (true) throw new RuntimeException("예외 발생!"); return "Success"; }).exceptionally(ex -> { log.error("예외 처리: {}", ex.getMessage()); return "Fallback Value"; }); }
@Component @Slf4j public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable ex, Method method, Object... params) { log.error("비동기 메서드 {} 실행 중 예외 발생: {}", method.getName(), ex.getMessage(), ex); } }
2. 동일 클래스 내 비동기 호출 제한
- 설명: @Async 메서드는 AOP 방식으로 프록시를 사용하기 때문에, 같은 클래스 내에서 @Async 메서드를 호출할 경우 비동기로 실행되지 않습니다.
- 해결책: 같은 클래스 내 호출이 필요한 경우 @Async 메서드를 외부 클래스에 분리하거나, ApplicationContext를 주입받아 자기 자신의 @Async 메서드를 호출해야 합니다.
@Component @Slf4j public class AsyncService { @Autowired private ApplicationContext context; public void callAsyncMethod() { AsyncService self = context.getBean(AsyncService.class); self.asyncMethod(); } @Async public void asyncMethod() { log.info("비동기 실행 중..."); } }
4. 스레드 풀 설정 필요
- 설명: @Async의 기본 스레드 풀은 SimpleAsyncTaskExecutor로 설정되어 있어, 스레드 관리가 효율적이지 않을 수 있습니다. 기본 설정을 사용하면 많은 비동기 작업이 발생할 경우 성능 저하가 일어날 수 있습니다.
5. 트랜잭션 전파 문제
- 설명: @Async 메서드는 호출하는 쪽과 다른 스레드에서 실행되므로 트랜잭션이 자동으로 전파되지 않습니다. 따라서 트랜잭션이 필요한 작업을 비동기로 실행할 때는 트랜잭션 관리에 주의가 필요합니다.
6. 모니터링 및 디버깅 어려움
- 설명: 비동기 메서드는 다른 스레드에서 실행되므로, 호출 흐름을 추적하거나 디버깅하기가 어려울 수 있습니다. 예를 들어, 로깅의 순서가 다르게 나타나거나 디버깅 중에 예상치 못한 스레드 전환이 발생할 수 있습니다.
- 해결책: 로깅 및 트레이싱을 적극적으로 활용하고, 상황에 따라 AOP로 메서드 호출을 모니터링하는 방법을 사용할 수 있습니다.
@Aspect @Component @Slf4j public class AsyncLoggingAspect { @Before("@annotation(org.springframework.scheduling.annotation.Async)") public void logAsyncMethodExecution(JoinPoint joinPoint) { log.info("비동기 메서드 실행: {}", joinPoint.getSignature().toShortString()); } }
7. 메모리 관리
- 설명: 비동기 작업이 많아지면 많은 스레드가 생성되어 메모리를 많이 사용하게 됩니다. 이는 메모리 사용량이 높은 애플리케이션에서는 문제가 될 수 있습니다.
@ExceptionHandler, @ControllerAdvice
@ExceptionHandler
- @Controller, @RestController에만 적용할 수 있습니다. (@Service, @Component는 안 됨)
- 리턴 타입은 자유롭게 설정할 수 있습니다. 에러가 발생할 수 있는 메소드와 다른 리턴 타입이어도 상관없습니다.
- @ExceptionHandler를 등록한 Controller에만 적용됩니다. (@ControllerAdvice를 통해 전역으로 관리가능)
- @ResponseStatus를 이용해 HTTP 상태코드를 리턴하지 않으면, HTTP 상태 코드를 모두 200으로 리턴합니다.
- @ExceptionHandler({ Exception1.class, Exception2.class}) 이런식으로 두 개 이상 등록도 가능합니다.
- 다양한 응답타입으로 (View페이지, Josn Data등 리턴 가능..)
@ControllerAdvice
: 모든 @Controller 즉, 전역에서 발생할 수 있는 예외를 잡아 처리해주는 annotation입니다.
@ControllerAdvice // 전역 예외 처리를 위한 어노테이션 public class DemoExceptionHandler { @ExceptionHandler(Exception.class) // 예외 타입을 지정하여 처리할 메소드를 정의합니다. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 응답 상태 코드를 설정합니다. @ResponseBody // 응답 본문을 설정합니다. public String handleException(Exception e) { return "예외가 발생했습니다: " + e.getMessage(); } }
@RestController public class MyRestController { ... ... @ExceptionHandler(NullPointerException.class) public Object nullex(Exception e) { System.err.println(e.getClass()); return "myService"; } }
@ResponseStatus
ResponseStatus는 Controller나 Exception에 사용하여 status 정보를 설정하여 리턴해 준다.
@ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(NotFoundException.class) public ModelAndView handleNotFound(Exception exception) { log.error("Handling not found Exception"); log.error(exception.getMessage()); ModelAndView mav = new ModelAndView(); mav.setViewName("404error"); mav.addObject("exception", exception); return mav; } }
@Controller, @Service, @Repository
공통점은 @Controller, @Service, @Repository은 @Component의 구체화된 형태로 해당 어노테이션이 사용된 곳은 @Component와 마찬가지로 자동으로 스프링 빈으로 등록됩니다. 따라서 @Controller 자리에 @Component를, @Service와 @Repository 자리에도 마찬가지로 @Component를 사용할 수 있습니다. 각 클래스의 역할을 명확하게 구분 지을 수 있어서 좋습니다.
- @Repository
특정 예외를 잡아, 스프링의 unchecked 예외로 다시 던집니다. PersistenceExceptionTranslationPostProcessor를 구현하여야 합니다. 따라서 플랫폼 상세 예외를 잡으면, 스프링의 DataAccessException로 다시 던질 수 있습니다.
DDD(Evans, 2003)에서 정의된 Repository는 "저장, 검색, 객체 컬렉션을 에뮬레이트하는 검색 행위를 캡슐화하는 메커니즘"이라고 합니다
메소드에서 발생하는 예외를 데이터 액세스 예외로 자동으로 전환해줍니다.
구조정의
- Controller -> Service -> Repository -> mapper.xml
- Controller -> Service -> Repository -> Mapper -> mapper.xml
- Controller -> Service -> Mapper -> mapper.xml
- @Service
어노테이션은 비즈니스 로직을 수행하는 서비스 클래스를 지정할 때 사용됩니다. 이 어노테이션이 붙은 클래스는 주로 비즈니스 로직을 구현하고, 트랜잭션 처리 등과 같은 서비스 관련 기능을 수행합니다.
- @Controller
이 어노테이션이 붙은 클래스는 HTTP 요청을 처리하고 적절한 응답을 반환합니다. 주로 웹 애플리케이션에서 사용됩니다.
RequestingMapping 어노테이션과 같은 핸들러 메서드에 같이 사용됩니다.
@Controller(Spring MVC Controller)
전통적인 Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용합니다.하지만 @ResponseBody 어노테이션과 같이 사용하면 RestController와 똑같은 기능을 수행할 수 있습니다.
예시코드
-
@RestController(Spring Restful Controller)Permalink
RestController는 Controller에서 @ResponseBody 어노테이션이 붙은 효과를 지니게 됩니다.
즉 주용도는 JSON/XML형태로 객체 데이터 반환을 목적으로 합니다.
@RequestMapping, @GetMapping, @PostMapping
클래스 레벨의 @RequestMapping
- 클래스 레벨에서도 @RequestMapping 설정이 가능하다
- 클래스 설정시 메소드에 설정한 URL은 모두 클래스의 서브 URL이 된다
@Controller @RequestMapping("/user") public class UserListController{ @RequestMapping public String getAllUser(Model model){ ... @RequestMapping("/userId:[0-9]{3}") public String getUserById(@PathVariable("userId") int id,...
@Controller public class LoginController { @GetMapping("/login") public String loginForm() { return "login"; // 로그인 폼 화면 반환 } @PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password) { // 단순 파라미터 처리 System.out.println("Username: " + username); System.out.println("Password: " + password); return "dashboard"; } }
@DateTimeFormat
@DateTimeFormat 활용하면, 다양한 형식으로 customizing하여 데이터를 입력 받아올 수 있다.
public class testDate{ @DateTimeFormat(pattern = "yyyy-MM-dd") private DateTime testDate1; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private DateTime testDate2; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ssZ") private DateTime testDate3; }
※ 주의 ※
마지막 3번째로 사용된 TimeZone까지 입력 받는 경우엔 반드시 Encoder를 거쳐야 한다.
(Encoder를 거치지 않을 경우엔 +를 space bar로 인식하여 typeMismatch와 같은 error 발생)
Decoder Ver : 2019-04-17 02:00:00+09:00
Encoder Ver : 2019-04-17+02%3A00%3A00%2B09%3A00
출처: https://syundev.tistory.com/268
출처: https://mangkyu.tistory.com/75 [MangKyu's Diary]
출처: https://jeong-pro.tistory.com/187
출처 : https://dydtjr1128.github.io/java/2019/12/23/JAVA-FutureTask.html
출처 : https://blog.gangnamunni.com/post/mdc-context-task-decorator/
ModelAttribute - https://developer-joe.tistory.com/197
Component (Controller, Service, Repository) - https://escapefromcoding.tistory.com/732
ExceptionHandler - https://kchanguk.tistory.com/64
requestmapping - https://heeestorys.tistory.com/373
'WEB개발 > Spring' 카테고리의 다른 글
[Spring] Spring Security (0) | 2024.08.23 |
---|---|
하이버네이트(Hibernate) & JPA(Java Persistence API) (0) | 2024.05.23 |
[SpringBoot] 스프링부트 오류모음 (0) | 2024.03.27 |
[SPRING]DI (MAP, SET, LIST), Root Application Context, Servlet Application Context (0) | 2023.03.07 |
[Spring] MessageSource 다국어 (0) | 2023.01.19 |