Depndency Injection에 대해 알고싶다면?
1편을 먼저 봐주세요. - Spring DI 이해하기
Spring이 관리하는 자바 객체를 Bean이라고 한다. 자바에서는 객체를 생성할 때 new 연산을 통해서 새로운 객체를 만들지만, Spring을 사용하면 Bean으로 등록된 객체들을 Spring이 관리한다.
Bean으로 관리할 객체들은 어노테이션을 통해서 Spring에게 인지를 시켜줄 수 있으며, 많이 사용되는 것은 @Component, @Service, @Controller, @Repository가 있다.
Spring은 component scan을 통해 자신이 관리할 객체들을 scan하고 여러가지 방법으로 이를 객체에 주입(Injection)해준다.
@Component
class B {
public funcB() {
System.out.println("this is b");
}
}
class A {
@Autowired
private B b;
public funcA() {
this.b.funcB;
...
}
}
필드 주입은 구현이 간단하며, 에러가 있는 경우 빌드 시 Exception이 떨어진다. 그렇기에 실제로 크게 문제가 없어보인다.
하지만 테스트 코드를 작성해보면, 문제점이 드러난다.
위 A 클래스의 기능을 단위 테스트로 만들어보자.
A a = new A();
a.funcA(); // NullPointerException
A 클래스는 B클래스를 주입받아야만 동작하는데, 테스트 코드에서 new 연산자로 생성해버리면, 자동으로 주입이 안되기에 에러가 발생한다.
그래서 생성자 주입을 권장한다.
@Component
class A {
private final B b;
public A(B b) {
this.b = b;
}
public void funcA() {
b.funcB();
}
}
얼핏 보면 필드 주입보다 코드가 복잡해진다. 흔히 말하는 boiler plate 코드로 보일 수 있다.
하지만 이 방법에는 굉장한 장점이 있다.
- class A는 class B가 있어야만 정상적으로 생성이 가능하다. 이는 필수적인 종속성을 강제하기에 객체를 안전하게 생성할 수 있다.
- 필수적인 종속성관계의 객체들을 공개적으로 보여줄 수 있다. A를 단위테스트에서 생성하는 경우 B클래스가 있어야만 생성할 수 있도록 빌드 레벨에서 보여줄 수 있다.
- final 변수로 선언하면서, 객체의 변화를 막을 수 있다. 보다 안정적으로 사용할 수 있다.
비록 코드가 조금 더 복잡해졌지만, 필드 주입보다 훨씬 안정적인 객체를 얻을 수 있고, 테스트하기도 더 쉬워졌다.
생성자 주입의 복잡한 코드를 줄이는 방법으로 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'
Lombok을 적용한 후 위 @RequiredArgsConstructor
어노테이션을 활용하면 이렇게 코드가 바뀐다. 지금은 주입되는 객체가 한개뿐이지만 여러개인 경우 코드가 엄청나게 간소해진다.
@Component
@RequiredArgsConstructor
class A {
private final B b;
public void funcA() {
b.funcB();
}
}
Lombok에는 이것 외에도 @Get, @Set 등등 다양한 활용방법이 있다.
생성자 주입을 활용하고, Lombok을 통해 코드를 간소화 하자!