자동으로 의존성을 주입해주는 Spring!

전홍영·2024년 12월 10일
1

Spring

목록 보기
23/26

이전 포스트에서 DI에 대해서 알아봤다. 스프링은 조립기 역할을 한다고 했다. 코드를 보자.

@Configuration
public class AppCtx {
    @Bean
    public ImageDao imageDao() {
        return new ImageDao();
    }

    @Bean
    public ImageLoadingService imageLoadingService() {
        return new ImageLoadingService(imageDao());
    }

	...
}

위 코드를 보면 ImageDao 객체를 빈으로 등록한후 ImageLoadingService에 imageDao()를 통해 의존성을 주입시켜주고 있는 것을 볼 수있다. 이렇게 직접 의존성을 주입시켜주는 방법도 있지만 스프링은 이를 자동으로 주입시켜주는 기능이 있다.
ImageLoadingService 코드를 보면 필드로 ImageDao를 가지고 ImageLoadingService가 생성될때 초기화가 이루어진다. 스프링의 @Autowired를 사용하면 직접 의존성을 주입할 필요없이 스프링이 해당 객체를 찾아 자동으로 의존성을 주입해준다.

public class ImageLoadingService {
    @Autowired
    private ImageDao imageDao;

    ...
}

이렇게 의존성이 필요한 필드에 @Autowired만 붙여주면 스프링이 해당 객체가 빈으로 등록되어 있으면 해당 빈을 찾아서 의존성을 주입해준다. 이렇게 필드를 위에 @Autowired를 붙여도 되고 메서드 위에도 @Autowired를 붙일 수 있다.

public class ImageUpdateService {
    private ImageDao imageDao;

    ...
    
    @Autowired
    public void setImageDao(ImageDao imageDao) {
        this.imageDao = imageDao;
    }
}

이렇게 setter 메서드에 @Autowired를 붙여주면 파라미터 타입과 같은 타입의 빈을 찾아서 의존성을 주입해준다. 이렇게 @Autowired를 붙여준 후 위에서 조립기 역할을 하는 @Configuration 설정 클래스에 직접 의존성을 주입하는 부분을 생략해준다.

@Configuration
public class AppCtx {
    @Bean
    public ImageDao imageDao() {
        return new ImageDao();
    }

    @Bean
    public ImageLoadingService imageLoadingService() {
        return new ImageLoadingService();
    }
}

이렇게 직접 의존성을 주입하는 코드를 제거한 후 테스트를 실행해보면 정상적으로 작동하는 것을 볼 수 있다.

만약 일치하는 빈이 없다면? 똑같은 타입의 빈이 2개 이상이 존재하면?

@Autowired를 붙인 필드나 메서드에 해당하는 빈이 없다면 UnsatisfiedDependencyException이 발생한다. 이는 해당 타입의 빈이 존재하지 않아 의존성을 주입할 수 없다는 것을 의미한다. 따라서 @Autowired를 붙인 필드나 메서드의 타입이 빈이 꼭 필요하다.

그렇다면 똑같은 타입의 빈이 여러 개 있을 때 어떻게 스프링은 어떤것을 선택할까? 일단 이런 경우가 발생되면 오류가 발생한다. 에러 메시지는 해당 빈이 한 개가 아니라 이름이 여러개인 빈이 존재한다고 알려준다. 하지만 자동 주입을 하기 위해서는 해당 타입을 가진 빈이 어떤 빈인지 정확하게 한정할 수 있어야 한다.

그래서 이럴 경우 @Qualifier 어노테이션을 이용해서 의존 객체를 선택하거나, @Primary를 이용해서 식별할 수 있도록 해준다. 이에 대한 내용은 전에 작성한 포스트를 참고하자.

@Autowired 어노테이션을 붙인 타입에 해당하는 빈이 존재하지 않아도 오류가 안나는 방법

@Autowired 어노테이션을 붙인 타입에 해당하는 빈이 존재하지 않으면 오류가 발생한다했다. 오류가 나지 않는 방법은 무엇일까? 크게 3가지 방법이 있다.

첫번째는, @Autowired 어노테이션에 required 속성을 false로 지정하면 매칭되는 빈이 없어도 익셉션이 발생하지 않는다.

public class ImageUpdateService {
    private ImageDao imageDao;

    ...

    @Autowired(required = false)
    public void setImageDao(ImageDao imageDao) {
        this.imageDao = imageDao;
    }
}

이렇게 required 속성을 false로 하면 매칭되는 빈이 없어도 익셉션이 발생하지 않고 메서드를 실행하지 않는다. 그렇게 되면 imageDao는 null이 될 것이다.

이 경우 문제가 되는 것이 null인데, 스프링 5버전 이후부터는 required 속성을 false로 하는 것보다 Optional을 사용하는 것이 좋다. 이 방법이 2번째 방법인데 의존성을 주입받을 객체가 null이 되면 NullPointException이 발생할 수 있다. 그래서 Optional을 활용해서 일치하는 빈이 존재하지 않아도 오류가 발생하지 않고 해당하는 빈이 있으면 의존성을 주입한다.

public class ImageRegisterService {
    private ImageDao imageDao;

    ...

    @Autowired
    public void setImageDao(Optional<ImageDao> imageDaoOptional) {
        if (imageDaoOptional.isPresent()) {
            this.imageDao = imageDaoOptional.get();
        } else {
            imageDao = null;
        } 
    }
}

마지막 방법은 @Nullable을 활용하는 방법이다. @Nullable 어노테이션을 의존 주입 대상 파라미터에 붙이면, 스프링 컨테이너는 해당 메서드를 호출할 때 자동 주입할 빈이 존재하면 해당 빈을 인자로 전달하고, 존재하지 않으면 null을 전달한다.

public class ImageUpdateService {
    private ImageDao imageDao;

    ...

    @Autowired
    public void setImageDao(@Nullable  ImageDao imageDao) {
        this.imageDao = imageDao;
    }
}

그러면 @Autowired의 required 속성을 false로 할 때와 @Nullable 어노테이션의 차이점은 required가 false면 해당 메서드를 호출 자체를 안하고 @Nullable은 메서드가 호출은 된다는 점이다. 이 방법은 필드나 메서드 모두 같은 방식으로 사용해도 된다.

만약 생성자에서 의존성 미리 주입했다면 어떻게 될까?

public class ImageUpdateService {
    private ImageDao imageDao;

    ...

    public ImageUpdateService() {
        this.imageDao = new ImageDao();
    }

    @Autowired
    public void setImageDao(@Nullable  ImageDao imageDao) {
        this.imageDao = imageDao;
    }
}

위의 코드를 보면 해당 객체를 생성할 때 의존 주입 객체를 초기화 했다. @Nullable 붙은 세터 메서드를 호출한다. 만약 세터 파라미터가 null이라면 생성자에서 초기화한 의존 객체 ImageDao가 null이 된다. 만약 @Nullable 대신 @Autowired의 required가 false면 호출자체를 안하기 때문에 초기화한 의존 객체는 그대로 유지된다. 이러한 차이점이 있기 때문에 유의해서 사용해야한다.

그러면 직접 의존 객체 주입하는 방법이 나을까? 자동 의존 주입이 나을까?

만약 직접 의존성을 주입한 객체에 @Autowired가 붙어 있으면 어떻게 될까? 이럴 경우는 @Autowired 어노테이션이 붙은 타입의 객체로 의존성이 주입된다. 따라서 @Autowired 어노테이션 붙어있는 것이 직접 의존 객체를 주입한 것보다 우선순위가 높다. 따라서 자동 주입을 하는 코드와 수동으로 주입하는 코드가 섞어 있으면 주입을 제대로 하지 않아서 NullPointException이 발생했을 때 오류 지점을 찾기가 어려울 수 있다. 그래서 자동 주입을 사용할 거면 자동으로 하고 수동으로 할거면 수동으로 의존성을 주입하자.

참고 : 스프링 프로그래밍 입문 5 - 최범균
같이 볼 포스트
예시 코드

profile
Don't watch the clock; do what it does. Keep going.

0개의 댓글