스프링을 이해하려면 POJO(Plain Old Java Object)를 기반으로 스프링 삼각형이라는 애칭을 가진 스프링의 3대 프로그래밍 모델에 대한 이해가 필수다.
스프링 삼각형은 스프링을 지배하는 근원적인 요소이다.
IoC(제어의 역전)은 스프링에서만 사용하는 단어는 아니다.
프로젝트에서는 구현 객체가 스스로 필요한 서버 구현 객체를 생성, 연결 및 실행한다.
그런데, 따로 설정 파일을 만들면 구현 객체는 자신의 로직을 실행하는 역할만 담당하고, 프로그램의 어떤 제어 흐름 자체는 설정 파일이 가져가게 된다.
라이브러리와 프레임워크를 구분할 때 제어의 역전이 중요하다.
DI는 의존성 주입을 말한다.
애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고, 클라이언트에 전달해서 클라이언트와 서버의 실제 의존 관계가 연결되는 것을 의존 관계 주입(Dpendency Injection)이라고 한다.
의존 관계는 정적인 클래스 의존 관계와 실행 시점에 결정되는 동적인 객체 인스턴스 의존 관계를 분리해서 생각해야 한다.
스프링은 자바의 객체들을 어떤 컨테이너 안에 넣어 놓고, 이 안에서 의존 관계를 서로 연결해주고 주입해주는 기능들(DI 컨테이너)을 제공한다.
이로써 클라이언트의 교체 없이 클라이언트 코드의 변경 없이 기능을 확장할 수 있다.
객체를 생성 및 관리하면서 의존 관계를 연결해주는 것을 IoC 컨테이너 혹은 DI 컨테이너라고 한다.
IoC는 여러 군데에서 일어나기 때문에, 너무 범용적인 단어여서 최근에는 주로 DI 컨테이너라고 부른다.
애플리케이션을 하나하나 조립을 한다고 해서 Assembler라고 부르거나, Object를 만들어낸다고 해서 ObjectFactory라고 부르기도 한다.
의존성은 new 다.
예를 들어, 다음과 같은 코드가 있을 때 Car는 Tire에 의존한다고 한다.
public class Car {
Car() {
new Tire();
}
public static void main(String[] args) {
new Car();
}
}
타이어 관련 클래스 생성
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire {
public String getBrand() {
return "한국 타이어";
}
}
public class AmericaTire implements Tire {
public String getBrand() {
return "미국 타이어";
}
}
타이어를 생산(new)하고 사용할 자동차 클래스 생성
public class Car {
Tire tire;
public Car() {
// **의존 관계가 일어나고 있다.**
tire = new KoreaTire();
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
그리고 테스트할 클래스를 생성한다.
public class Driver {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.getTireBrand());
}
}
이때, 각 클래스 간의 관계는 다음과 같이 설명할 수 있다.
주입은 외부에서라는 뜻을 내포하고 있는 단어다.
즉, 자동차 내부에서 타이어를 생성하는 것이 아니라 외부에서 생산한 타이어를 자동차에 장작하는 작업을 말한다.
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire {
public String getBrand() {
return "한국 타이어";
}
}
public class AmericaTire implements Tire {
public String getBrand() {
return "미국 타이어";
}
}
타이어를 생산(new)하고 사용할 자동차 클래스 생성
public class Car {
Tire tire;
// 외부에서 tire 주입
public Car(Tire tire) {
this.tire = tire;
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
테스트할 클래스를 생성한다.
public class Driver {
public static void main(String[] args) {
Tire tire = new KoreaTire();
Car car = new Car(tire);
System.out.println(car.getTireBrand());
}
}
여기서는 디자인 패턴의 꽃이라고 하는 전략 패턴을 응용하고 있다.
전략 패턴의 3요소에 대응되는 요소는 다음과 같다.
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire {
public String getBrand() {
return "한국 타이어";
}
}
public class AmericaTire implements Tire {
public String getBrand() {
return "미국 타이어";
}
}
타이어를 생산(new)하고 사용할 자동차 클래스 생성
public class Car {
Tire tire;
// 접근자 메서드
public Tire getTire() {
return tire;
}
// 외부에서 tire 주입
public Tire setTire(Tire tire) {
this.tire = tire;
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
테스트할 클래스를 생성한다.
public class Driver {
public static void main(String[] args) {
Tire tire = new KoreaTire();
Car car = new Car();
car.setTire(tire);
System.out.println(car.getTireBrand());
}
}
어떤 방법이 더 좋은가에 대한 의견은 분분한데, 최근에는 속성을 통한 의존성 주입보다는 생성ㅈ를 통한 의존성 주입을 선호하는 사람들이 많다고 한다.
스프링은 생성자를 통한 의존성 주입과 속성을 통한 의존성 주입을 모두 지원한다.
여기에선 속성을 통한 의존성 주입만 살펴보도록 한다.
먼저, 이후 살펴 볼 예제 코드들에 앞서 공통적으로 사용하는 코드들을 여기에 정리했다.
Tire 인터페이스와 구현체
Car 클래스 (속성 주입)
Tire 인터페이스와 구현체
public interface Tire {
String getBrand();
}
public class KoreaTire implements Tire {
public String getBrand() {
return "한국 타이어";
}
}
public class AmericaTire implements Tire {
public String getBrand() {
return "미국 타이어";
}
}
타이어를 생산(new)하고 사용할 자동차 클래스 생성
public class Car {
Tire tire;
// 접근자 메서드
public Tire getTire() {
return tire;
}
// 외부에서 tire 주입
public Tire setTire(Tire tire) {
this.tire = tire;
}
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
스프링 없이 사용할 때와 비교했을 때, Driver 클래스가 수정되고 스프링 설정 파일(XML 파일) 하나가 추가되는 것 외에는 다른 점이 없다.
public class Driver {
public static void main(String[] args) {
ApplicatinContext context = new ClassPathXMLApplicationContext("~.xml", Driver.class);
Car car = (Car) context.getBean("car", Car.class);
Tire tire = (Tire) context.getBean("tire", Tire.class);
car.setTire(tire);
System.out.println(car.getTireBrand());
}
}
그리고, XML 파일을 하나 추가해줘야 한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="tire" class="folder.**KoreaTire**"></bean>
<bean id="car" class="folder.Car"></bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="tire" class="folder.**AmericaTire**"></bean>
<bean id="car" class="folder.Car"></bean>
</beans>
public class Driver {
public static void main(String[] args) {
ApplicatinContext context = new ClassPathXMLApplicationContext("~.xml", Driver.class);
Car car = (Car) context.getBean("car", Car.class);
System.out.println(car.getTireBrand());
}
}
<property>
를 통해 Tire 구현체를 주입한다.koreaTire
를 americaTire
로 변경하고 싶다면, <property>
의 ref 속성을 변경해주면 된다.<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="koreaTire" class="folder.KoreaTire"></bean>
<bean id="americaTire" class="folder.AmericaTire"></bean>
<bean id="car" class="folder.Car">
<property name="tire" ref="**koreaTire**"></property>
</bean>
</beans>
위에서는 설정자 메소드를 통하여 속성을 주입시켰다.
그런데, @Autowired
어노테이션을 사용하면 설정자 메서드를 사용하지 않고도 스프링 프레임워크가 설정 파일을 통해 속성을 대신 주입해준다.
public class Car {
@Autowired
Tire tire;
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
<context:annotation-config />
를 추가한다.koreaTire
를 americaTire
로 변경하고 싶다면, <bean id="tire">
의 class를 변경한다.<property>
를 작성하지 않아도, 알아서 tire 객체를 자동 주입해준다.<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config />
<bean id="tire" class="folder.KoreaTire"></bean>
<bean id="car" class="folder.Car"></bean>
</beans>
Autowired
어노테이션은 type 기준 매칭이다.예제 코드를 보면 알겠지만, @Autowired
와 비교해 달라지는 것은 어노테이션 하나 뿐이다.
@Autowired
어노테이션은 스프링의 어노테이션이다.@Resource
어노테이션은 자바 표준 어노테이션이다.public class Car {
@Resource
Tire tire;
public String getTireBrand() {
return "장착된 타이어: " + tire.getBrand();
}
}
Resource
어노테이션은 id로 우선 매칭한다.@Autowired | @Resource | |
---|---|---|
출처 | 스프링 프레임워크 | 표준 자바 |
빈 검색 방식 | byType 먼저, 못 찾으면 byName | byName 먼저, 못 찾으면 byType |
특이사항 | @Qualifier(””) 협업 | name 어트리뷰트 |
byName 강제하기 | @Autowired | |
@Qualifier(”tire”) | @Resource(name=”tire”) |
@Resource
는 <bean>
태그의 자식 태그인 <property>
태그로 해결될 수 있다.@Resource
는 생산성이 좋고, <property>
태그는 유지보수성이 좋다.<property>
태그를 사용하는 쪽을 추천했다.다수의 클래스를 만들고 의존 관계를 지정할 때,
@Resource
<property>
태그다음과 같은 조합 및 방법도 여럿 있는데, 이건 필요할 시 찾아서 공부하면 될 거 같다.
📔 스프링 입문을 위한 자바 객체 지향의 원리와 이해