[Java] Spring DI 생성자 주입(Constructor Injection)과 필드 주입(@Autowired)

Nowod_K·2023년 3월 15일
0

Spring DI 이해하기

목록 보기
2/2

Depndency Injection에 대해 알고싶다면?
1편을 먼저 봐주세요. - Spring DI 이해하기


Spring은 어떻게 DI를 해줄까?

Spring이 관리하는 자바 객체를 Bean이라고 한다. 자바에서는 객체를 생성할 때 new 연산을 통해서 새로운 객체를 만들지만, Spring을 사용하면 Bean으로 등록된 객체들을 Spring이 관리한다.

Bean으로 관리할 객체들은 어노테이션을 통해서 Spring에게 인지를 시켜줄 수 있으며, 많이 사용되는 것은 @Component, @Service, @Controller, @Repository가 있다.


@Component

  • Class 레벨의 어노테이션으로 Spring은 @Component를 인식해서 자동으로 객체를 관리한다.
  • @Service, @Controller, @Repository는 모두 @Component를 기본으로 한다.

@Controller, @Service, @Repository

  • Spring MVC 패턴에 기반한 어노테이션으로 각각 역할을 명시적으로 구분하기 위해서 사용된다.
  • @Controller : controller 클래스로 인식하기 위한 어노테이션
  • @Service : 비즈니스 로직을 담고있는 Service 클래스로 인식하기 위한 어노테이션
  • @Repository : DB 접근을 하는 repository 클래스로 인식하기 위한 어노테이션

Spring DI 방법

Spring은 component scan을 통해 자신이 관리할 객체들을 scan하고 여러가지 방법으로 이를 객체에 주입(Injection)해준다.


필드 주입 (@Autowired - Field Injection)

@Component
class B {
	public funcB() {
    	System.out.println("this is b");
    }
}
class A {
	@Autowired
    private B b;
    
    public funcA() {
    	this.b.funcB;
        ...
    }
}
  • B 클래스를 @Component 어노테이션을 주면 Spring은 이를 인식하여 관리한다.
  • A 클래스에서 @Autowired를 사용하여 B 클래스를 주입받고자 하면 Spring에서 자동으로 객체를 주입해준다.

필드 주입의 문제점

필드 주입은 구현이 간단하며, 에러가 있는 경우 빌드 시 Exception이 떨어진다. 그렇기에 실제로 크게 문제가 없어보인다.

하지만 테스트 코드를 작성해보면, 문제점이 드러난다.
위 A 클래스의 기능을 단위 테스트로 만들어보자.

A a = new A();
a.funcA(); // NullPointerException

A 클래스는 B클래스를 주입받아야만 동작하는데, 테스트 코드에서 new 연산자로 생성해버리면, 자동으로 주입이 안되기에 에러가 발생한다.

그래서 생성자 주입을 권장한다.


생성자 주입 (Constructor Injection)

@Component
class A {
 
  private final B b;
  
  public A(B b) {
    this.b = b;
  }

  public void funcA() {
    b.funcB();
  }
}

얼핏 보면 필드 주입보다 코드가 복잡해진다. 흔히 말하는 boiler plate 코드로 보일 수 있다.

하지만 이 방법에는 굉장한 장점이 있다.

  1. class A는 class B가 있어야만 정상적으로 생성이 가능하다. 이는 필수적인 종속성을 강제하기에 객체를 안전하게 생성할 수 있다.
  1. 필수적인 종속성관계의 객체들을 공개적으로 보여줄 수 있다. A를 단위테스트에서 생성하는 경우 B클래스가 있어야만 생성할 수 있도록 빌드 레벨에서 보여줄 수 있다.
  1. final 변수로 선언하면서, 객체의 변화를 막을 수 있다. 보다 안정적으로 사용할 수 있다.

비록 코드가 조금 더 복잡해졌지만, 필드 주입보다 훨씬 안정적인 객체를 얻을 수 있고, 테스트하기도 더 쉬워졌다.

Lombok 사용하기

생성자 주입의 복잡한 코드를 줄이는 방법으로 Lombok 라이브러리가 있다.

// 롬복 적용 방법
compileOnly 'org.projectlombok:lombok:1.18.26'
annotationProcessor 'org.projectlombok:lombok:1.18.26'
	
testCompileOnly 'org.projectlombok:lombok:1.18.26'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.26'

@RequiredArgsConstructor

Lombok을 적용한 후 위 @RequiredArgsConstructor
어노테이션을 활용하면 이렇게 코드가 바뀐다. 지금은 주입되는 객체가 한개뿐이지만 여러개인 경우 코드가 엄청나게 간소해진다.

@Component
@RequiredArgsConstructor
class A {
 
  private final B b;
  
  public void funcA() {
    b.funcB();
  }
}

Lombok에는 이것 외에도 @Get, @Set 등등 다양한 활용방법이 있다.


결론

생성자 주입을 활용하고, Lombok을 통해 코드를 간소화 하자!

profile
개발을 좋아하는 마음과 다양한 경험을 토대로 좋은 개발자가 되고자 노력합니다.

0개의 댓글