@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] Request를 처리하는 주석 (Annotation) (0) | 2025.02.21 |
---|---|
Spring Reactive [Mono, Flux, DeferredResult, WebClient, Future] (0) | 2025.02.17 |
하이버네이트(Hibernate) & JPA(Java Persistence API) (0) | 2024.05.23 |
[Spring] MessageSource 다국어 (0) | 2023.01.19 |