스프링 입문을 위한 자바 객체 지향의 원리와 이해라는 책에서 스프링의 핵심은 스프링 삼각형이라 나온다. 여기서 스프링 삼각형이란 POJO를 기반으로 한 IoC/DI, AOP, PSA라는 3가지 프로그래밍 모델을 뜻한다.
DI (의존성 주입)
객체는 다른 객체와 의존 관계를 맺을 수 있다. 예를 들어 A와 B객체가 있다고 해보자 A객체가 변할 때 B객체에도 영향을 미친다면 B객체는 A객체에 의존하고 있으며 둘은 의존 관계이다. 그리고 객체가 다른 객체에 의존할 때 즉 의존성을 가지고 있을 때 외부에서 의존성을 주입하는 것이 의존성 주입(Dependency Injection)이다. 아래 두 코드를 비교해보자.
public class Car {
Tire tire;
public Car() {
tire = new KoreaTire();
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
public class Car {
Tire tire;
public Car(Tire tire) {
this.tire = tire;
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
두 코드의 Car 객체는 Tire 인터페이스를 의존하고 있다. 차이점은 첫 번째 코드는 의존성을 직접 해결하며 두 번째 코드는 외부에서 의존성을 주입받는다. 어떤 코드가 더 이점이 많을까? 새로 만들 Car 객체는 KoreaTire가 아닌 AmericaTire를 가져야한다고 가정하자. 첫 번째 코드는 객체 내부를 변경해야되는 반면 두 번째 코드는 객체를 수정하지 않아도 된다. 즉 유연성이 높다. 또한 Tire의 종류가 늘어 ChinaTire, JapanTire, EnglandTire 등 여러 타이어가 나와도 두 번째 코드는 Tire 인터페이스를 구현한 어떤 객체가 들어오던 간에 정상적으로 동작한다. 즉 확장성이 좋다. 따라서 의존성을 외부에서 주입하면 객체의 유연성과 확장성을 높일 수 있게 된다.
외부에서 의존성을 주입하는 2가지 방법이 있다. 첫 번째는 생성자를 통한 의존성 주입이며 두 번째는 속성(멤버변수)를 통한 의존성 주입이다. 생성자를 통해서 의존성을 주입하면 의존 객체를 바꿀수 없는 반면 속성을 통해서 의존성을 주입하면 의존 객체를 바꿀 수 있다. 둘 중 일반적으로 생성자를 통한 의존성 주입이 사용된다. 프로그래밍 세계에서 의존 대상이 바뀌는 경우는 드물기 때문이다.
스프링의 DI
스프링은 자바 또는 XML 등 스프링 설정 파일을 기반으로 스프링 컨테이너에 빈 객체를 생성하고 의존성 주입을 해준다. 마치 종합쇼핑몰과 같다. 자동차를 주문하면 엔진, 타이어, 핸들 등 자동차에 필요한 부품을 모두 결합한 완전한 자동차를 제공한다.
//JAVA Spring 설정
@Configuration
public class AppConfig {
@Bean
public Tire getKoreaTire() {
return new KoreaTire();
}
@Bean
public Tire getAmericaTire() {
return new AmericaTire();
}
@Bean
public Car getCar() {
//KoreaTire 객체를 의존 주입
return new Car(getKoreaTire());
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Car car = context.getBean(Car.class);
System.out.println(car.getTireBrand());
}
}
스프링의 의존성 자동 주입
위에서는 자바 형식의 스프링 설정 파일을 작성하여 직접 빈등록과 의존성 주입을 명시했다. 하지만 객체를 빈으로 등록하고 의존성 주입을 명시하는 작업은 귀찮다. 때문에 스프링에서는 자동으로 빈으로 등록해주는 컴포넌트 스캔, 자동으로 의존성을 주입하는 자동 주입 기능을 제공한다. 자동 주입 기능은 @Autowird 어노테이션으로 사용할 수 있다.
@Autowird 어노테이션을 사용하면 스프링이 알맞은 빈을 찾아 해당 빈을 주입한다. @Autowird는 생성자, setter, 필드(멤버 변수)에 위치할 수 있는데 메서드나 생성자에 붙일 경우 모든 인자에 대해서 자동 주입을 진행한다. 아래의 이점 때문에 왠만하면 생성자에 붙인다.
- 의존 객체가 불변일 경우 의존성을 주입 받을 필드에 final을 붙여 불변임을 확정, null이 아님을 보장
- 순환 참조 방지
- 테스트 코드 작성 용이
@Autowird 어노테이션을 사용하면 스프링은 아래 그림와 같이 타입을 기반으로 빈을 찾아 주입한다.
여기서 타입은 하위 클래스도 포함한다. 예를 들어 Tire 인터페이스를 구현한 2개의 빈 객체가 있고 Tire 타입 필드에 빈 자동 주입 시 2개의 빈 객체가 발견될 것이다. 빈이 여러 개라면 스프링은 어떤 빈을 주입해야 되는지 모르기에 에러를 발생시킨다. 때문에 주입받을 빈을 한정시키기 위해 id를 사용한다. @Qualifier 어노테이션으로 id를 설정 할 수 있다.
public class Car {
private final Tire tire;
@Autowired
public Car(@Qualifier("KoreaTire") Tire tire) {
this.tire = tire;
}
public String getTireBrand() {
return tire.getBrand();
}
}
한편 빈 자동 주입 시 해당 타입의 빈이 없다 해도 에러가 발생하는 걸 방지하려면 @Autowired(required = false) 또는 @Nullable을 사용한다.
public class Car {
private final Tire tire;
private final SunRoof sunRoof;
@Autowired
public Car(@Qualifier("KoreaTire") Tire tire, @Autowired(required = false) SunRoof sunRoof) {
this.tire = tire;
this.sunRoof = sunRoof;
}
public String getTireBrand() {
return tire.getBrand();
}
public String getSunRoofBrand() {
if(sunRoof == null)
return "sunRoof not exist";
return sunRoof.getBrand();
}
}
'Spring > Spring Basic' 카테고리의 다른 글
스프링 컨테이너와 스프링 빈 (0) | 2023.02.08 |
---|