본문 바로가기

WEB개발

[Spring] 주석 1(Annotation)

@Configuration, @Bean, @EnableAsync, @ASYNC, @ModelAttribute, @RequestBody, @ExceptionHandler, @ResponseStatus, @ControllerAdvice, @Controller,  @Service,  @Repository, @RequestMapping, , @Cookievalue, @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 어노테이션을 사용하기 위해서는 다음의 조건을 충족해야 합니다:

  1. 해당 메소드는 반드시 public이어야 합니다.

  2. 해당 메소드는 클래스 내부에서 호출되어야 합니다. 외부에서 같은 클래스 내의 @Async 메소드를 호출하면 비동기적으로 실행되지 않습니다.

  3. 해당 메소드는 다른 메소드에서 호출되어야 하며, 같은 클래스 내에서 this를 통해 호출되어야 합니다. 클래스 외부에서 같은 클래스의 메소드를 직접 호출하는 경우도 비동기적으로 실행되지 않습니다.

 

@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();
        };
    }
}

 

 

 


 

@ModelAttribute,  @RequestBody

@ModelAttribute 어노테이션은 HTTP 요청의 파라미터를 자바 객체로 변환해줍니다. 주로 HTML 폼을 통해 전달된 데이터를 처리할 때 사용됩니다. Spring MVC에서는 요청의 파라미터를 지정된 객체의 필드에 바인딩하여 Controller 메소드의 매개변수로 전달합니다.

 

@RequestBody 어노테이션은 HTTP 요청의 body 부분을 자바 객체로 변환해줍니다. 주로 POST나 PUT과 같이 요청 본문에 데이터가 포함된 경우에 사용됩니다. Spring MVC에서는 요청의 body를 파싱하고 해당 데이터를 지정된 객체로 변환하여 Controller 메소드의 매개변수로 전달합니다.

 

public class RequestController {


    @RequestMapping("/request")
    @ResponseBody
    public Map request(@ModelAttribute UserDto userDto1, @RequestBody UserDto userDto2) {
        log.debug("userDto1 : " + userDto1);  //request param data
        log.debug("userDto2 : " + userDto2);  //request body data
        HashMap map = new HashMap();
        map.put("RESULT", "SUCCESS");
        return map;
    }
}

 

 

 


 

@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

 

클래스 레벨의 @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,...

 

 

 


 

@Cookievalue

 

@GetMapping("/getCookie")
public String getCookie(HttpServletRequest request,
        @CookieValue(value = "hi") Cookie cookie) {
    Cookie[] cookies = request.getCookies();

    Arrays.asList(cookies).stream()
            .forEach(c -> log.debug(c.getName() + ":" + c.getValue()));

    log.debug("cookie :{}", cookie.getValue());
    return "getCookie";
}

 

@CookieValue를 이용하면 Cookie 객체를 받을 수 있다.

HttpServletRequest를 이용해서 cookie객체를 꺼낼 수도 있다.

 

 

 


 

@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