Spring

@ActiveProfiles

2023. 7. 5. 16:19
728x90

@ActiveProfiles 란 Spring 테스트에서 특정 프로필을 활성화 하기 위해 사용하는 어노테이션이다.

예를 들어 아래와 같은 application.yml 파일이 있다 가정한다.

spring:
    profiles:
        group:
            prod: prodDB, common
            test: testDB, common
        active: prod

위처럼 profiles group을 나누는 이유는 prod 환경과 test 환경을 구분하기 위해서 이다. 예를들어 prod 환경에선 Mysql을 사용한다면, test 환경에서는 h2 인메모리 DB를 사용하는것처럼 서로다른 DB를 사용하는 상황을 대비해 위처럼 group을 나눌수 있다.

그리고 각 프로파일에 대한 설정은 application-{profile}.yml이라는 파일에 저장하게 된다.

이때 {profile} 부분에는 실제 프로파일 이름이 들어가게 되며, 예를 들어, prodDB 프로파일의 설정은 application-prodDB.yml 파일에, testDB 프로파일의 설정은 application-testDB.yml 파일에 저장하게 된다.

이렇게 application.yml 에 여러 test 용 profiles와 각 profiles의 설정을 위한 yml 파일이 준비가 되었다면

@ActiveProfiles annotation 을 사용하여 우리가 원하는 설정정보들을 통해 테스트 환경을 구성할수 있게 된다.

@ActiveProfiles("test")
728x90
728x90

@Valid annotation의 예외 처리하기

@PostMapping("/sign-up")
public String signUp(@Valid UserDTO userDto, BindingResult bindingResult) {
    if(bindingResult.hasErrors()){
        return "user/form";
    }
    userService.addUser(userDto);
    return "redirect:/user/list";
}

기존에는 위와같은 방식으로 request를 UserDTO에 mapping할때 @Valid 과정에서 오류가 나면 bindingResult로 오류를 확인한후 user/form으로 재이동했다. 근데 이왕 @ControllerAdvice를 만들었으니 @Valid과정에서 발생하는 오류또한 @ControllerAdvice에서 처리를 해주고 싶었다.



MethodArgumentNotValidException, BindException

@Valid로 인해 발생하는 오류를 잡는 방법 2가지

  1. @RequestBody로 받은 파라미터에서 오류가 발생하면 MethodArgumentNotValidException이 발생한다.

  2. @ModelAttribute로 받은 파라미터에서 오류가 발생하면 BindException이 발생한다고 한다.



@RequestBody,@ModelAttribute로 request를 받을때 차이점

@RequestBody: 클라이언트가 보낸 HTTP 요청(JSON,XML..)을 Java 오브젝트로 변환한다. 이때 변환은 Spring에서 제공하는 HttpMessageConverter를 통해 타입에 맞는 객체로 변환이 된다. 이때 ObjectMapper를 통해 JSON값을 Java객체로 역직렬화하는데 따라서 dto에 setter(하지만 결국 바인딩을 위해선 setter 혹은 getter중 하나가 있어야한다. 단지 setter가 아닌 getter만 있더라도 데이터 바인딩이 가능하다는 것 이다.)가 없더라도 reflextion을 통해 값을 설정할수 있다.

  • 역직렬화란 생성자를 거치지않고 리플렉션을 통해 객체를 구성하는 매커니즘

❗️직렬화가 가능한 클래스는 기본 생성자가 필수다. 따라서 @Requestbody에 사용하려는 Dto가 기본 생성자를 정의하지 않으면 데이터 바인딩에 실패한다.

@RequestBody 를 사용할땐 기본생성자 + getter 또는 setter 가 필요하다.


@ModelAttribute: HTTP 파라미터들을 특정 Java Object에 바인딩 한다.

쿼리 또는 본문에 삽입되는 Form형태의 데이터를 처리할때 사용한다. @ModelAttribute의 특징으로는 객체에 접근을해 데이터를 바인딩 할 수 있는 생성자 혹은 setter가 필요하다.



ControllerAdvice 에서 서로다른 bindException 처리

따라서 나는 form의 데이터를 받는 형태니까 @ModelAttribute를 사용한다. 근데 이때 post와 sign-up 둘다 bindException이 발생할수 있는데 이걸 어떻게 처리하지?

@ExceptionHandler(BindException.class)
public ModelAndView handleBindException(BindException e, HttpServletRequest request){
    String requestUri = request.getRequestURI();
    if (requestUri.contains("/sign-up")) {
        return createErrorResponseModelAndView("user/form", e,false);
    }
    return createErrorResponseModelAndView("article/form", e,false);
}

위와같이 HttpServletRequest를 통해 uri에 따라 viewName을 구분지었다.



따로 @RequestBody,@ModelAttribute를 명시하지 않을때 바인딩 되는 방식

HTTP 요청의 Content-Type이 application/x-www-form-urlencoded인 경우에는 @ModelAttribute 어노테이션과 동일한 방식으로 매개변수를 바인딩한다. 이 경우에는 HttpServletRequest.getParameter() 메서드를 사용하여 HTTP 요청 매개변수를 가져와서 자바 객체에 바인딩한다.

만약 HTTP 요청의 Content-Type이 application/json인 경우에는 @RequestBody 어노테이션과 동일한 방식으로 매개변수를 바인딩한다. 이 경우에는 HTTP 요청 본문에 포함된 JSON 문자열을 Jackson 등의 JSON 라이브러리를 사용하여 자바 객체로 역직렬화하고, 이 객체를 매개변수로 바인딩한다.

따라서, @ModelAttribute 어노테이션과 @RequestBody 어노테이션을 명시하지 않아도 Spring MVC는 HTTP 요청 매개변수를 자동으로 바인딩한다.

Reference

@Valid 예외처리

728x90

'Spring' 카테고리의 다른 글

@JdbcTest  (0) 2023.07.05
@ActiveProfiles  (0) 2023.07.05
[SpringMVC] DTO 사용이유  (0) 2023.04.09
[SpringMVC] addViewController사용할때 주의할점  (0) 2023.04.09
[SPRING] Bean Scope  (0) 2023.03.05

[SpringMVC] DTO 사용이유

2023. 4. 9. 20:44
728x90

DTO를 사용하는 이유

요청과 응답에 entity를 직접 사용하기 보다는 DTO를 사용하자.

//entity를 직접사용하는 예시
@PostMapping("/article/post")
public String postArticle(Article article) {
    articleService.post(article.toArticle());
    return "redirect:/";
}
//DTO를 직접 사용하는 예시
@PostMapping("/article/post")
public String postArticle(ArticleDTO articleDto) {
    articleService.post(articleDto.toArticle());
    return "redirect:/";
}

위의 예시처럼 domain대신 DTO를 사용하면 얻을수 있는 이점은 다음과 같다.

 

엔티티의 내부 구현을 캡슐화 할수 있다.

entity의 getter와 setter를 사용한다면 데이터 전달 역활을 수행할수 있을지도 모른다. 하지만 이런 방식으로 entity를 사용한다면 controller와 같은 비지니스 로직과 상관없는 곳에서 자원의 속성이 실수로 변경될수 있으며, 엔티티를 UI계층(MVC 패턴에서 view와 연관된 부분)에 노출하는 것은 보안상으로 바람직 하지 못하다.

 

필요한 데이터를 선별할 수 있다.

특정 API 요청에 필요한 데이터만 전송하면 네트워크를 통해 전송해야 하는 데이터의 양을 줄여 응답 시간을 단축할 수 있다.

 

순환참조를 예방할수 있다.

순환참조란(Circular reference) 서로 다른 두개 이상의 객체가 서로를 참조하는 형태를 말한다. 즉 객체 A가 객체 B를 참조하고, 동시에 객체 B가 객체 A를 참조하는것을 순한 참조하란다.

이런 순환참조가 발생한다면 객체A,B는 서로가 서로를 참조하는 형태이므로 GC의 수거 대상이 되지 않아 메모리 누수가 발생할수있다.

또한 객체A,B가 서로를 참조함으로써 무한히 반복되는 무한 루프가 발생할수 있다.

우리가 MVC 모델에서 response를 entity로 한다면 엔티티가 참조하고 있는 객체는 지연 로딩되고, 로딩된 객체는 또 다시 본인이 참조하고 있는 객체를 호출하게 된다. 이렇게 서로 참조하는 객체를 계속 호출하면서 결국 무한 루프에 빠지게 될수있다.

이러한 현상을 방지하기위해 response를 DTO로 두는것이 더 안전하다는 것이다.

 

validation 코드와 모델링 코드를 분리할수 있다.

엔티티 클래스는 DB와 관련된 비지니스 로직이 작성되있는 곳이다. 따라서 이곳에 validation을 위한 코드( ex) @NotNull)가 들어간다면 엔티티가 매우 복잡해질수 있다. 이러한 현상을 방지하기위해 validation을 DTO에서 정의한다면, 엔티티 클래스는 좀더 모델링과 비지니스 로직에만 집중할수 있다.

 

DTO를 사용한다면 어느 계층에서 사용하는것이 좋을까

사실 아직은 잘 모르겠다. 원칙적으론 DTO가 service layer에 들어오면 안되는거 같지만(여러 종류의 컨트롤러가 해당 서비스를 사용할수 없기 때문에) 현업에서는 여러 컨트롤러가 하나의 서비스를 사용하는 경우보단 하나의 컨트롤러가 서비스를 쓴다고한다.

🙏Reference

728x90

'Spring' 카테고리의 다른 글

@ActiveProfiles  (0) 2023.07.05
[Spring] @RequestBody, @ModelAttribute  (0) 2023.04.24
[SpringMVC] addViewController사용할때 주의할점  (0) 2023.04.09
[SPRING] Bean Scope  (0) 2023.03.05
[SPRING] Bean Life cycle, call back  (0) 2023.03.05
728x90
@GetMapping("/user/signUp")
public String showSignUpForm(){
    return "/user/form";
}

@PostMapping("/user/signUp")
public String signUp(UserSignUpDTO userSignUpDto) {
    userService.addUser(userSignUpDto.toUser());
    return "redirect:/users";
}

UserController에 위와같이 매핑을 하는 메서드가 있다. 이때 메서드를 호출하지 않는 Mapping(위에선 showSignUpForm)에 대해서는 controller에 존재하는 showSignUpForm을 삭제하고 아래와같이 MvcConfig에 매핑을 해도 잘 동작을 하는줄알았는데 Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported]오류가 발생했다.

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

        registry.addViewController("/user/signUp").setViewName("/user/form");
    }
}

이유를 찾아보니 아래와 같다.

addviewController

우리가 MvcConfig에서 매핑하려는 URL이 controller에서 RequestMapping 되어있다면 addViewController에서 같은 URL에대한 매핑을 처리할수 없다. 라고 나와있다.

728x90

'Spring' 카테고리의 다른 글

[Spring] @RequestBody, @ModelAttribute  (0) 2023.04.24
[SpringMVC] DTO 사용이유  (0) 2023.04.09
[SPRING] Bean Scope  (0) 2023.03.05
[SPRING] Bean Life cycle, call back  (0) 2023.03.05
[SPRING] 의존관계 자동 주입  (1) 2023.03.05

[SPRING] Bean Scope

2023. 3. 5. 21:00
728x90

✔Bean scope란?

Bean scope란 bean이 존재할수 있는 범위를 의미한다.

 

스프링이 지원하는 Scope 종류 (그대로 복붙임 수정해)

  • 싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
  • 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.
  • 웹 관련 스코프
    • request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다.
    • session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.
    • application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.

 

 

 

 

 

 

✔Prototype Scope

싱글톤 스코프에서 bean조회시 스프링 컨테이너는 항상 같은 인스턴스의 스프링 bean을 반환했지만

prototype scope 를 스프링컨테이너에서 조회한다면 항상 새로운 인스턴스를 반환한다.

  • 즉 스프링 컨테이너는 프로토타입 bean을 생성하고, 의존관계 주입, 초기화까지 처리한후 더이상 생성된 prototype bean을 관리하지 않는다.

❗❗ 스프링 컨테이너가 bean의 생성이후 관리를 하지 않기 떄문에 @PreDestroy 같은 종료 메서드는 호출되지 않는다. 따라서 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 한다. 종료 메서드에 대한 호출도 클라이언트가 직접 해야한다.

 

 

 

 

 

 

✔싱글톤에서 프로토타입 빈을 사용

 

beanscope1

위사진과같이 Singleton인 clientBean에 Prototype인 bean이 있다고가정한다.

이때 클라이언스 A가 특정 logic을 통해 prototype bean에 존재하는 count의 값을 1증가시킨다 가정하자.

그후 새로운 클라이언트 B가 prototype bean에 존재하는 count의 값을 1증가시킨다 가정한다.

이때 clientBean은 Singleton이다 따라서 클라이언트A 와 클라이언트B는 서로같은 clientBean에 접근하는데

클라이언트A 와 클라이언트B가 접근하는 prototype bean또한 같은 bean이다.

따라서 각 클라이언트가 count를 1씩 증가시킨다하면 count는 2가 된다.

근데 이렇게 된다면 사실 prototype bean을 사용하는 이유가 없다, 우리가 원하는 방향은 각각의 클라이언트가 특정 logic(count++)을 실행할때마다 새로운 프로토타입 bean을 만들어 logic을 적용하고싶은 의도와 달라지기 때문이다.(즉 count가 2 가되는게 아닌 각각의 count가 1 이 되도록) 이러한 문제를 해결하는 방법을 소개한다.

 

 

 

  1. 싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청하는 것이다.
	public int logic() {
	    PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
	    prototypeBean.addCount();
	    int count = prototypeBean.getCount();
	    return count;
	}

위 코드처럼 특정 logic을 사용할때마다 PrototypeBean을 스프링 컨테이너에 요청한다. (Dependency Lookup DL)

 

 

  1. ObjectFactory, ObjectProvider
   @Autowired
   private ObjectProvider<PrototypeBean> prototypeBeanProvider;
       public int logic() {
           PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
           prototypeBean.addCount();
           int count = prototypeBean.getCount();
           return count;
}

위 코드에서 우리가 원하는 의도인 항상 새로운 prototype bean이 생성되는것을 알수있다.

ObjectProvider DL 정도의 기능만 제공한다.

 

 

  1. JSR-330 Provider
   @Autowired
   private Provider<PrototypeBean> provider;
       public int logic() {
           PrototypeBean prototypeBean = provider.get();
           prototypeBean.addCount();
           int count = prototypeBean.getCount();
           return count;
       }

JSR-330 Provider 를 사용하려면 따로 library를 추가해줘야한다.

JSR-330 Provider는 자바 표준이라 ObjectProvider 처럼 Spring에 의존적이지 않지만 ObjectProvider와 마찬가지로 DL 정도의 기능만을 제공한다.

 

 

 

 

 

 

✔웹스코프

웹스토프는 웹환경에서 동작을 하며, 프로토타입 스코프와는 다르게 스프링이 해당 스코프의 종료 시점까지 관리를 한다.

 

web scope 종류

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
  • session: HTTP Session과 동일한 생명주기를 가지는 스코프
  • application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

beanscope2

 

@Scope(value = "request") 를 사용할때 만약 우리가 스프링 컨테이너 생성 시점에 request Scope bean을 가져와야한다면, 이때 오류가 발생할수 있다. 그이유는 request Scope bean은 스프링 컨테이너 생성시점이 아닌 고객의 요청이 들어온 이후에 생성이 되기 때문이다. 따라서 이러한 오류를 발생시키지 않기위한 방법을 소개한다.

 

 

  1. Provider 사용
   private final ObjectProvider<MyLogger> myLoggerProvider;
   
   @RequestMapping("log-demo")
   @ResponseBody
   public String logDemo(HttpServletRequest request) {
   	String requestURL = request.getRequestURL().toString();
   	MyLogger myLogger = myLoggerProvider.getObject();
       ...
   }

provider를 통해 bean이 주입 시점이 아니라 실제로 필요할 때 생성되게 할수있다. 따라서 사용자의 요청이 왔을때 bean을 생성하여 원하는 처리를 할수 있게 된다.

 

 

  1. proxyMode 사용
   // @Scope(value = "request") //이전코드
   @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

위와같이 proxyMode 를 사용하면 (provider는 사용안함) HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 , 스프링 컨테이너 생성 시점에 request Scope bean을 가져와 발생하는 오류를 해결할수 있다. (간단히 말해 가짜 객체를 만들고 추후 client의 request가 들어올때 진짜 객체를 찾아와 필요한 처리를 해준다)

 

 

  • 결론 : 사실 Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.

 

🙏Reference

728x90

'Spring' 카테고리의 다른 글

[SpringMVC] DTO 사용이유  (0) 2023.04.09
[SpringMVC] addViewController사용할때 주의할점  (0) 2023.04.09
[SPRING] Bean Life cycle, call back  (0) 2023.03.05
[SPRING] 의존관계 자동 주입  (1) 2023.03.05
[SPRING] Component scan  (0) 2023.03.03
728x90

📌 커넥션풀, 소켓같은 애플리케이션 시작시점에 필요한 연결을 미리 해둔뒤, 애플리케이션 종료시점에 모든 연결을 종료하는 작업을 하기위해선 객체의 초기화 작업 및 종료 작업이 필요하다. 이를 Spring을 통해 할수 있는 방법을 알아본다.

✔ Spring Bean Life Cycle

스프링 빈은 다음과 같은 라이프 사이클을 가진다.

  • 객체생성 ▶ 의존관계 주입

따라서 의존관계를 주입한뒤 개발자가 필요한 초기화 작업을 진행해야한다. 이때 Spring은 개발자에게 의존관계 주입이 완료되었다는것을 알려주기위한 초기화 콜백 을 제공하며, bean이 소멸되기 직전 즉 스프링 컨테이너가 종료되기 직전에 소멸전 콜백 을 알려주는 기능을 제공한다.

 

 

✔ Spring의 call back

  1. 인터페이스를 통한 콜백
    • InitializingBean 인터페이스를 implements하면 아래 코드와같은 메서드를 오버라이드 할수있다.
      @Override
          public void afterPropertiesSet() throws Exception {
             ...
          }
    이 메서드를 통해 의존관계가 끝난시점에 수행할 동작을 지정해 줄수 있다.
    • DisposableBean 인터페이스를 implements하면 아래 코드와같은 메서드를 오버라이드 할수있다.
      @Override
          public void destroy() throws Exception {
              ...
          }
    이 메서드를 통해 스프링 컨테이너가 종료되기 직전 수행할 동작을 지정해 줄수 있다.

❗ 하지만 인터페이스를 통한 콜백은 메서드의 이름을 변경할수 없으며, 스프링 전용 인터페이스에 의존하며 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다 이러한 여러 단점들로 현재는 거의 사용하지 않는다.

  1. 빈 등록 초기화, 소멸 메서드 지정
    • 설정정보에 @Bean(initMethod = "init", destroyMethod = "close") 을 추가해주면 초기화메서드는 init(사용자가 만든 메서드 이름), 소멸 메서드는 close(사용자가 만든 메서드 이름)로 간편하게 지정할수있다.
    • 빈 등록을통해 메서드를 지정하면 인터페이스를 통한 콜백에서의 단점들을 극복할수 있으며 추가적으로 destroyMethod의 default 값은 inferred 인데 이렇게 default값으로 놓으면 알아서 close, shutdown이라는 이름의 메서드를 호출해준다.
    • 이기능은 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할때 대부분의 외부 라이브러리들의 종료 메서드의 이름이 close 나 shutdown 이기때문에 유용하게 사용될수 있다.
  1. 애노테이션 @PostConstruct, @PreDestroy
    • 의존관계 주입후에 실행시킬 메서드에 @PostConstruct 소멸직전에 실행시킬 메서드에 @PreDestroy를 적어주면 끝난다.
    • 매우 편리하게 사용할수 있지만 단점이있다면 외부 라이브러리에서 사용할수 없다는 점이다.

📌결론적으로 평소에는 @PostConstruct, @PreDestroy 애노테이션 을 사용하고 외부라이브러리를 초기화 또는 종료해야 한다면 빈 등록 초기화, 소멸 메서드 지정을 사용하자.

 

 

🙏Reference

728x90

'Spring' 카테고리의 다른 글

[SpringMVC] addViewController사용할때 주의할점  (0) 2023.04.09
[SPRING] Bean Scope  (0) 2023.03.05
[SPRING] 의존관계 자동 주입  (1) 2023.03.05
[SPRING] Component scan  (0) 2023.03.03
[SPRING] Singleton  (0) 2023.02.26

+ Recent posts