Spring에 대한 공부를 하며, Spring에서 제공하는 DI 덕분에 객체지향 설계 원칙인 SOLID 중 지켜지지 않던 O(OCP), D(DIP)를 지키며 개발할 수 있다는 것에 공감할 수 있었다. 문득, DI에 대해 공부해보며 궁금증이 생긴 부분이 있었고 이를 Spring에서 어떻게 해결해주는지에 대하여 알아본 결과를 정리해보았다.

 

궁금증....만약 인터페이스 1개에 구현체가 2개있는 경우는 의존성 주입이 어떻게 될까??


실제 코드를 통해 알아 보았다. 아래와 같은 Car라는 Interface가 있다.

public interface Car {
    int go();
}

해당 인터페이스를 구현하는 구현체 k5, k7이 아래와 같이 작성되있다고 가정해보자. 참고로 @Compenent의 경우 해당 클래스를 Spring Bean으로 등록한다는 것을 말한다.

@Component
public class k5 implements Car{

    @Override
    public int go() {
        return 5;
    }
}
@Component
public class k7 implements Car{

    @Override
    public int go() {
        return 7;
    }
}

그리고 테스트를 진행해 보았다.

	@Autowired
	private Car car;

참고로, @Autowired의 경우 Car Interface에 맞는 Bean으로 등록 된 객체를 가져와 Spring에서 자동으로 DI를 해달라고 명시해주는 어노테이션이다. 하지만, car 부분에 빨간줄이 생기며 제대로 DI가 안된다는 것을 알 수 있었다.

원인의 에러는 다음과 같았다.

 

Could not autowire. There is more than one bean of 'Car' type.
Beans: k5   (k5.java) 
       k7   (k7.java) 

직역해보면 autowire할 수 없다. 'Car' type의 bean이 한개보다 많다. 라고 한다.

그렇다. 구현체가 두개 이상 있는 경우에는 역시나 DI를 제대로 하지 못한다.

 

 

그렇다면, 해결 방법으로는 어떤 것이 있을까??


  1. 변수 네이밍을 Bean 이름으로 한다.
  2. @qualifier("Bean 객체 이름")을 사용한다.
  3. @primary를 사용한다.
  4. 사용하는 구현체 1개만 @component를 유지하고 나머지 구현체의 @component를 지워 Bean에 구현체 한개만 등록되도록 한다.

4 가지의 방법을 직접 코드로 구현해보며, 각각의 장단점에 대하여 느껴보자.

 

 

1번째 방법을 통해 해결해본 코드이다.

	@Autowired
	private Car k5;

결과적으로 제대로 DI를 통해 객체를 주입받지만, SOLID의 원칙인 OCP와 DIP가 위배된 코드를 확인할 수 있다.

 

 

2번째 방법을 통해 해결해본 코드이다.

	@Qualifier("k5")
	@Autowired
	private Car car;

결과적으로 제대로 DI를 통해 객체를 주입받고, OCP도 지키며 DIP도 지킨다고 볼 수 있다. Spring에서도 @qualifier를 추가하여 문제를 해결하라고 안내하긴 한다. 하지만,, @qualifier("k5")를 사용하다가 후에 k7으로 주입하고 싶으면 @qualifier("k5")

 

 @qualifier("k7") 변경 작업이 불가피하기때문에 결과적으로 보면 OCP와 DIP를 위반한다고 볼 수 있다.

 

3번째 방법을 통해 해결해본 코드이다.

@Component
@Primary
public class k5 implements Car{

    @Override
    public int go() {
        return 5;
    }
}

해당 @primary의 경우 구현체 중 우선순위를 정해준 것이기 때문에, 같은 interface의 구현체 중 1개만 정해야 하는 단점이 있지만, 테스트 클래스에서 수정하는 것이 아닌 구현체 클래스에서 수정하는 것을 볼 수 있다. 또한, OCP와 DIP도 지켜주는 모습이다.

 

4번째 방법을 통해 해결하는 방법은 사용하는 구현체 딱 한 개 말고는 @component를 삭제하여 Bean에 등록을 안시켜주면 된다. Spring에서 Interface에 의존성 주입을 할 때 1개의 구현체만 있는경우는 문제가 생기지 않기 떄문이다.

 

결과적으로, 3번째와 4번째 방법이 좋은 해결 방법이라고 볼 수 있다.

가장 좋은 방법은 4번째 방법으로, 사용할 하나의 구현체만 Bean으로 등록하는 것이 좋은 방법인 것 같다.

하지만, 여러개의 구현체를 두고 상황에 맞춰서 각각 다른 구현체를 사용해야 한다면 2번째 방법을 통해 해결하는 것이 최선인 것 같다.

혹시나, 나중에 구현체를 두개이상 두는 경우 해당 방법으로 해결하면 좋을 것 같다.

'spring > 정리' 카테고리의 다른 글

로그  (0) 2022.06.04

사용하는 이유

로그를 사용하고 싶을 때, System.out.println()을 사용해도 원하는 기능을 얻을 수 있을 것이다. 그럼에도 Slf4j를 사용하여 얻을 수 있는 이점이 있다.

 

1. 더 많은 내용을 얻을 수 있다.

System.out.println()
Slf4j

System.out.println()과 Slf4j를 사용했을 때의 로그 차이이다. 차이를 보면 다음과 같다.

1. 찍히지 않는 실행한 스레드

2. 클래스 이름

3. message가 출력이 된다.

 

 

2. 로그의 레벨을 정할 수 있다.

log는 trace, debug, info, warn, error를 제공합니다.

 

예를들어, 아래와 같은 기준을 두며 로그의 레벨을 정할 수 있습니다.

debug는 개발서버에서 보는 것이다.
info는 비즈니스로직이기 때문에 운영시스템에서 봐야한다.

 

또한, application.properties 설정에서 패키지별로 로그의 레벨을 따로 정할 수 있습니다.

# hello.springmvc 패키지와 그 하위 로그 설정
logging.level.hello.springmvc=trace

위와같이 코드를 작성하면, hello.springmvc 하위 패키지에 있는 log는 trace로그부터 찍히도록 할 수 있습니다.

 

참고로 기본은 info이고, trace -> debug -> info -> warn -> error 순으로 찍을 수 있습니다.

 

예를들어, trace로 설정하면 trace, debug, info, warn, error의 모든 로그가 찍히고,

debug로 설정하면 debug, info, warn, error의 모든 로그가 찍힙니다.

 

그렇기때문에 spring boot 로그는 기본을 info로 두고있습니다. 실제로 trace로 변경해보면 기존에 보이지않던 수 많은 로그가 찍히는 것을 볼 수 있습니다.

 

 

3. 콘솔에만 출력하는 것이 아닌, 파일이나 네트워크 등 별도의 위치에 남길 수 있다.

 

4. 성능이 우세하다.

 

 

 

 

사용하는 방법

어노테이션 X

package hello.springmvc.basic;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogTestController {

    @GetMapping("/log-test")
    public String logTest() {
        String name = "spring";

        log.trace("trace log={}", name);
        log.debug("debug log={}", name);
        System.out.println("info log =" + name);
        log.info("info log= {}", name);
        log.warn("warn log={}", name);
        log.error("error log= {}", name);
        return "ok";
    }
}

 

어노테이션 O

package hello.springmvc.basic;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LogTestController {

    @GetMapping("/log-test")
    public String logTest() {
        String name = "spring";
        
        log.trace("trace log={}", name);
        log.debug("debug log={}", name);
        System.out.println("info log =" + name);
        log.info("info log= {}", name);
        log.warn("warn log={}", name);
        log.error("error log= {}", name);
        return "ok";
    }
}

 

어노테이션을 사용한 코드와 사용하지 않은 코드의 차이점을 보면

private final Logger log = LoggerFactory.getLogger(getClass());

가 있습니다. 실제로 @Slf4j의 기능은 위의 한줄의 코드를 줄여준다고 보면 됩니다.

 

 

"trace log ="+name으로 로그를 찍지 않는 이유       

 

만약 로그 레벨을 info로 설정한 경우 trace와 debug의 로그는 출력이 되지 않을 것 입니다.

 

하지만 로그를 "info log ={}", name의 방식이 아닌 "trace log ="+name으로 사용하는 경우, java는 "trace log ="+name를 읽고 cpu와 memory를 할당하지만, 실제로는 로그가 찍히지 않기 때문에 불필요한 cpu와 메모리를 낭비하게 됩니다.

 

반면, "info log = {}", name 방식을 사용하는 경우는 cpu와 메모리를 미리 할당하지 않는다고 합니다. 그렇기때문에 "info log ={}", name과 같은 방식을 사용합시다!

 

 

 

 

나중 공부를 위한 링크

1. slf4j

 

SLF4J

Simple Logging Facade for Java (SLF4J) The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framewor

www.slf4j.org

2. logback

 

Logback Home

Logback Project Logback is intended as a successor to the popular log4j project, picking up where log4j 1.x leaves off. Logback's architecture is quite generic so as to apply under different circumstances. At present time, logback is divided into three mod

logback.qos.ch

3. 스프링부트가 제공하는 로그 기능

 

Core Features

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, an

docs.spring.io

 

+ Recent posts