[Spring] 토비의 스프링 Vol.2 1장 IoC컨테이너와 DI (상)

Shiba·2023년 10월 12일
0

🍀 스프링 정리

목록 보기
12/21
post-thumbnail

📗 IoC컨테이너와 DI (상)

📖 IoC 컨테이너: 빈 팩토리와 애플리케이션 컨텍스트

스프링 컨테이너(IoC 컨테이너)빈 팩토리 또는 애플리케이션 컨텍스트라고 부름
- 오브젝트의 생성과 런타임 관계 설정을 위한 DI관점에서 볼때는 빈 팩토리
- 빈 팩토리에 애플리케이션을 개발하는데 필요한 여러가지 컨테이너 기능을 추가한 것애플리케이션 컨텍스트

📝 IoC 컨테이너를 이용해 애플리케이션 만들기

IoC 컨테이너를 사용하는 애플리케이션을 만드는 방법과 동작원리를 먼저 살펴보자

가장 간단하게 IoC컨테이너를 만드는 방법은 ApplicationContext를 구현하는 클래스의 인스턴스를 만드는 것이다.

StaticApplicationContext ac = new StaticApplicationContext(); //애플리케이션 컨택스트
// 아직은 아무것도 없는 빈(bean) 컨테이너. POJO클래스와 설정 메타정보 필요

◼ POJO 클래스

POJO원칙에 따라 애플리케이션 코드를 작성한다.
- 각각의 코드는 독립적이며, 다른 POJO와 느슨한 결합을 갖도록 해야함

예제) 지정된 사람에게 인사를 하는 Hello클래스, 이를 출력하는 Printer클래스

//Hello 클래스
public class Hello {
	String name;
    Printer printer;
    
    public String sayHello() { //프로퍼티로 DI 받은 이름을 이용해 간단한 인사문구 만들기
    	return "Hello " + name;
    }
    
    public void print() { // Printer타입의 오브젝트에게 출력작업 위임
    	System.out.print(sayHello());
    }
    
    public void setName(String name) { // 이름을 DI받을 수 있음
    	this.name = name;
    }
    
    public void setPrinter(Printer printer) { // 출력을 위해 사용할 Printer를 DI받음
    	this.printer = printer;
    }
}
//Printer 인터페이스
public interface Printer {
	void print(String message);
}

//StringBuffer를 이용한 구현 클래스
public class StringPrinter implements Printer {
	private StringBuffer buffer = new StringBuffer();
    
    public void print(String message) {
    	this.buffer.append(message);
    }
    
    public String toString() {
    	return this.buffer.toString(); //내장 버퍼에 추가해둔 메시지를 스트링으로 가져옴
    }
}

//Console 출력방식을 이용한 구현 클래스
public class ConsolePrinter implements Printer {
	public void print(String message) {
    	System.out.println(message);
    }
}

◼ 설정 메타정보

POJO클래스 중 애플리케이션에서 사용할 것을 선정하고 이를 IoC가 제어할 수 있도록 적절한 메타정보를 만들어 제공해야한다

스프링의 IoC컨테이너의 가장 기초적인 역할?
- 오브젝트(빈)를 생성하고 이를 관리하는 것

빈을 어떻게 만들고 어떻게 동작하게 할 것인가?
- 메타정보에 저장되어 있음
스프링의 메타정보는 XML로 되어있는 것이 아님!
- XML에 담긴 내용을 메타정보로 사용하기는 하지만 스프링이 XML로 된 메타정보를 가지지는 않음.
- BeanDefinition으로 만들어진 메타정보를 담은 오브젝트를 사용해 IoC/DI 수행

▪ BeanDefinition 인터페이스로 정의되는 IoC가 사용하는 빈 메타정보

  • 빈 아이디, 이름, 별칭 : 빈 오브젝트를 구분할 수 있는 식별자
  • 클래스 또는 클래스 이름 : 빈으로 만들 POJO 클래스 또는 서비스 클래스 정보
  • 스코프 : 싱글톤, 프로토타입과 같은 빈의 생성 방식과 존재 범위
  • 프로퍼티 값 또는 참조 : DI에 사용할 프로퍼티 이름과 값 또는 참조하는 빈의 이름
  • 생성자 파라미터 값 또는 참조 : DI에 사용할 생성자 파라미터 이름과 값 또는 참조할 빈의 이름
  • 지연된 로딩 여부, 우선 빈 여부, 자동와이어링 여부, 부모 빈 정보, 빈팩토리 이름

스프링 IoC컨테이너메타정보를 읽은 뒤, 이를 참고해 빈 오브젝트를 생성하고 프로퍼티나 생성자를 통해 DI작업을 수행.
DI로 연결된 오브젝트들이 모여 하나의 애플리케이션으로 동작함!

DI 정보 테스트

@Test
public void registerBeanWithDependency() {
	StaticApplicationContext ac = new StaticApplicationContext();
    
    //StringPrinter클래스 타입이며 printer라는 이름을 가진 빈을 등록
    ac.registerBeanDefinition("printer",
    	new RootBeanDefinition(StringPrinter.class)); 
        
    BeanDefinition helloDef = new RootBeanDefinition(Hello.class);
    
    //단순 값을 갖는 프로퍼티 등록
    helloDef.getPropertyValues().addPropertyValue("name", "Spring"); 
    //아이디가 printer인 빈에 대한 레퍼런스를 프로퍼티로 등록
    helloDef.getPropertyValues().addPropertyValue("printer",
    	new RuntimeBeanReference("printer"));
        
    ac.registerBeanDefinition("hello", helloDef);
    
    Hello hello = ac.getBean("hello", Hello.class);
    hello.print();
    
    //Hello 클래스의 print() 메소드는 DI된 Printer 타입의 오브젝트에게 요청해 인삿말 출력
    assertThat(ac.getBean("printer").toString(), is("Hello Spring"));
}

📝 IoC 컨테이너의 종류와 사용 방법

스프링이 제공하는 ApplicationContext 구현 클래스에는 어떤 종류가 있고 어떻게 사용되는지 살펴보자

◼ StaticApplicationContext

BeanDefinition을 이용해 코드를 통해 빈 메타정보 등록
- 학습테스트를 만들 때를 제외하면 잘 사용하지 않음

◼ GenericApplicationContext

가장 일반적인 애플리케이션 컨텍스트의 구현 클래스
- XML과 같은 외부의 리소스에 있는 빈 설정 메타정보를 리더를 통해 읽어들여 메타정보로 전환해 사용
- 실제로 직접 사용하지는 않음. - 테스트 시에 JUnit이 자동으로 만들어서 사용함

◼ GenericXmlApplicationContext

코드에서 GenericApplicationContext와 XmlBeanDefinitionReader를 사용하는 경우
두 클래스가 결합된 GenericXmlApplicationContext을 사용하면 편리

◼ WebApplicationContext

스프링 애플리케이션에서 가장 많이 사용되는 애플리케이션 컨텍스트
- 웹 환경에서 사용할 때 필요한 기능이 추가된 애플리케이션 컨텍스트
- 가장 많이 사용되는 것은 XmlWebApplicationContext.
- 애노테이션을 사용한다면 AnnotationConfigWebApplicationContext.

웹 환경에서는 main()의 역할을 하는 서블릿을 두고 미리 애플리케이션 컨택스트를 생성해둔 후, 요청이 들어올 때마다 getBean()으로 필요한 빈을 가져와 사용

📝 IoC 컨테이너 계층구조

◼ 부모 컨텍스트를 이용한 계층구조 효과

모든 애플리케이션 컨텍스트는 부모 애플리케이션 컨텍스트를 가질 수 있음
- 이를 이용해 트리 구조의 컨텍스트 계층을 만들 수 있음

  • 계층 안 모든 컨텍스트각자 독립적인 설정정보를 이용해 빈 오브젝트 제작,관리
  • 여러 애플리케이션 컨텍스트공유하는 설정을 만들기 위해 사용
  • DI로 빈을 찾을 때는 부모에게 요청해 없다면 부모의 부모, 루트 컨텍스트까지 검색
    자식 컨텍스트에게는 검색을 요청하지 않음

📝 웹 애플리케이션의 IoC 컨테이너 구성

서버에서 동작하는 애플리케이션에서 스프링 IoC컨테이너를 사용하는 방법

  • 웹 모듈 안에 컨테이너를 두는 것
  • 엔터프라이즈 애플리케이션 레벨에 두는 것

◼ 프론트 컨트롤러 패턴

서버에 대표 서블릿을 등록해두고 공통 작업을 수행한 후, 핸들러각각의 요청을 처리하는 방식

◼ 웹 애플리케이션의 컨텍스트 계층구조

루트 웹 애플리케이션 컨텍스트 : 웹 애플리케이션 레벨에 등록되는 컨테이너

일반적인 경우, 서블릿을 하나 만들어 프론트 컨트롤러 패턴 사용

  • 하나만 사용하는데 계층구조를 만드는 이유?
    • 전체 애플리케이션에서 웹 기술에 의존적인 부분, 아닌 부분을 구분하기 위함

서블릿을 한 개 이상 사용하는 방식
- 기존에 만들어진 설정을 그대로 유지하면서 새 기술을 추가하고 싶을 때 사용

  • 하나 이상의 프론트 컨트롤러 역할을 하는 서블릿이 등록될 수 있음
    • 각각의 독립적인 애플리케이션 컨텍스트가 만들어짐.
    • 각각의 컨텍스트에서 공유하게 되는 공통 빈들을 웹 애플리케이션 레벨의 컨텍스트에 등록

◼ 웹 애플리케이션의 컨텍스트 구성 방법

웹 애플리케이션의 애플리케이션 컨텍스트를 구성하는 방법으로 세가지 방법 고려

서블릿 컨텍스트와 루트 애플리케이션 컨텍스트 계층구조

가장 기본적인 구성 방법
스프링 웹 기술을 사용하는 경우, 웹 관련 빈들은 서블릿의 컨텍스트에 두고 나머지는 루트 애플리케이션 컨텍스트에 등록

루트 애플리케이션 컨텍스트 단일구조

스프링 웹 기술을 사용하지 않고 서드파티 웹 프레임워크나 서비스 엔진만을 사용해서 프레젠테이션 계층을 만듦.
- 서블릿이 필요가 없어 루트 애플리케이션 컨텍스트만 등록해줌

서블릿 컨텍스트 단일구조

스프링 웹 기술을 사용하면서 스프링 외의 프레임워크나 서비스 엔진에서 스프링 빈을 사용하지 않는다면 루트 애플리케이션 컨텍스트 생략가능. 대신, 서블릿에서 만들어지는 컨텍스트에 모든 빈을 등록해야함.

◼ 루트 애플리케이션 컨텍스트 등록

루트 웹 애플리케이션 컨텍스트를 등록하는 가장 간단한 방법서블릿의 이벤트 리스너를 이용하는 것!
- 스프링은 웹 애플리케이션의 시작과 종료시 발생하는 이벤트를 처리하는 리스너인 ServletContextListener 사용
- 이를 이용해 스프링컨택스트를 시작과 함께 만들고 종료시 함께 종료되는 리스너인 ContextLoaderListener를 제공

// ContextLoaderListener 등록
<listener>
  	<listener-class>org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

◼ 서블릿 애플리케이션 컨텍스트 등록

스프링의 웹 기능을 지원하는 프론트 컨트롤러 서블릿DispatcherServlet이다.
- 서블릿 이름을 다르게 지정해주면 하나의 웹 애플리케이션여러 개의 DispatcherServlet을 등록할 수도 있다.
- 서블릿이 초기화 될 때 자신만의 컨텍스트를 생성하고, 루트 애플리케이션 컨텍스트를 찾아 자신의 부모 컨텍스트로 지정

// 서블릿 컨텍스트를 위한 서블릿 등록
<servlet>
	<servlet-name>spring</servlet-name>//서블릿 이름 네임스페이스는 spring-servlet이 됨
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet
  	</servlet-class>
  	<load-on-startup>1</load-on-startup>//서블릿을 언제, 몇번째로 만들고 초기화할지 지정
</servlet>

📖 IoC/DI를 위한 빈 설정 메타정보 작성

컨테이너는 어떻게 자신이 만들 오브젝트가 무엇인지 알 수 있을까?
- 앞서 살펴본 빈 설정 메타정보(BeanDefinition)를 컨테이너가 활용

📝 빈 설정 메타정보

BeanDefinition에는 IoC 컨테이너빈을 만들 때 필요한 핵심 정보가 담겨있다.
- 몇가지 필수항목을 제외하면 컨테이너에 미리 설정된 디폴트 값이 그대로 적용
- BeanDefinition은 여러 개의 빈을 만드는데에 재사용 가능 - 이름만 다른 빈 생성

◼ 빈 설정 메타정보 항목

빈을 사용하기 위해 각 빈을 정의하는 핵심항목이 어떤 것인지 알고 있어아함!

이름내용디폴트 값
beanClassName빈 오브젝트의 클래스 이름. 빈 오브젝트는 이 클래스의 인스턴스가 됨.없음.
필수항목
parentName빈 메타정보를 상속받을 부모 BeanDefinition의 이름.
빈의 메타정보는 계층구조로 상속 가능
없음
factoryBeanName팩토리 역할을 하는 빈을 이용해 빈 오브젝트를 생성하는 경우에
팩토리 빈의 이름을 지정
없음
factoryMethodName다른 빈 또는 클래스의 메소드를 통해 빈 오브젝트를 생성하는 경우,
그 메소드 이름을 지정
없음
scope빈 오브젝트의 생명주기를 결정하는 스코프를 지정.
싱글톤과 비싱글톤 스코프로 구분
싱글톤
lazyInit빈 오브젝트의 생성을 최대한 지연할 것인지를 지정.
true이면 컨테이너는 빈 오브젝트의 생성을 꼭 필요한 시점까지 미룸.
false
dependsOn먼저 만들어져야 하는 빈을 지정.
빈 오브젝트의 생성 순서가 보장돼야 하는 경우 이용.
하나 이상의 빈 이름 지정 가능
없음
autowireCandidate명시적인 설정이 없어도
미리 정해진 규칙을 가지고 자동으로 DI 후보를 결정하는
자동 와이어링의 대상으로 포함시킬지의 여부
true
primary자동 와이어링 작업 중에 DI 대상 후보가 여러 개가 발생하는 경우,
최종 선택의 우선권을 부여할지 여부.
primary가 지정된 빈이 없이 여러 개의 후보가 존재하면
자동 와이어링 예외 발생
false
abstract메타정보 상속에만 사용할 추상 빈으로 만들지의 여부.
추상 빈이 되면 그 자체는 오브젝트가 생성되지 않고
다른 빈의 부모 빈으로만 사용
false
autowireMode오토와이어링 전략. 이름, 타입, 생성자, 자동인식 등없음
dependencyCheck프로퍼티 값 또는 레퍼런스가 모두 설정되어 있는지를 검증체크X
initMethod빈이 생성되고 DI를 마친 뒤에 실행할 초기화 메소드의 이름없음
destoryMethod빈의 생명주기가 다 되어 제거하기 전에 호출할 메소드의 이름없음
propertyValues프로퍼티의 이름과 설정 값 또는 레퍼런스.
수정자 메소드를 통한 DI 작업에서 사용
없음
constructorArgumentValues생성자의 이름과 설정 값 또는 레퍼런스.
생성자를 통한 DI 작업에서 사용
없음
annotationMetadata빈 클래스에 담긴 애노테이션과 그 애트리뷰트 값.
애노테이션을 이용하는 설정에서 활용
없음

📝 빈 등록 방법

스프링에서 자주 사용되는 빈의 등록 방법은 크게 다섯 가지가 존재.

◼ XML: <bean> 태그

<bean> 태그를 사용하는 가장 단순하면서 가장 강력한 설정 방법.
- 스프링 빈 메타정보의 거의 모든 항목을 지정할 수 있으므로 세밀한 제어가 가능
- 다른 빈의 <property> 태그 안에 정의할 수도 있음. - 내부 빈

//<bean>태그를 활용한 빈 생성
<bean id="mypointcut"
      class="org.springframework.aop.aspectj.AspectJExpressionPointcut">
	<property name="expression"
              value="execution(* *..*ServiceImpl.upgrade*(..))" />
</bean>

- <bean>과 <property> 태그로 선언되어 의도가 한눈에 파악되지 않음

◼ XML: 네임스페이스와 전용 태그

스키마에 정의된 전용 태그를 사용해 빈을 등록하는 방법.

//위의 내용을 네임스페이스와 태그를 가진 설정방법으로 변경
<aop:pointcut id="mypointcut"
              expression="execution(* *..*ServiceImpl.upgrade*(..))" />
  • 전용 태그를 사용하여 의도가 한눈에 파악됨.

◼ 자동인식을 통한 빈 등록 : 스테레오타입 애노테이션과 빈 스캐너

모든 빈을 일일히 XML 선언하는 것귀찮고 번거로울 수도 있다. 또한 여러 개발자와 설정파일을 공유하여 사용 시, 충돌이 발생할 수도 있다.
=> 애노테이션을 부여해주어 자동으로 빈을 찾아 등록해주는 빈 스캐닝 기술 활용

  • 빈 스캐너 : 자동으로 빈을 찾아 등록하는 빈 스캐닝 기술을 담은 오브젝트
  • 스테레오타입 애노테이션 : 빈 스캐너에 내장된 디폴트 필터에 적용되는 애노테이션 ex) @Component
package springbook.learningtest.spring.ioc.bean;

import org.springframework.stereotype.Component;

@Component
public class AnnotatedHello { // 클래스이름을 빈 아이디로 사용. - annotatedHello
	...
}

@Component("myAnnotatedHello") //디폴트 값을 이용해 빈 이름 지정
public class AnnotatedHello { // 클래스 이름이 아닌 myAnnotatedHello가 빈 아이디가 됨.

자동인식을 통한 빈 등록 방법

  • XML을 이용한 빈 스캐너 등록
    • XML 설정 파일 안에 context 스키마에 전용 태그를 넣어 간단히 빈 스캐너 등록
<context:component-scan
    base-package="springbook.learningtest.spring.ioc.bean" />
  • 빈 스캐너를 내장한 애플리케이션 컨텍스트 사용
    • XML에 빈 스캐너를 지정하는 대신 아예 빈 스캐너를 내장한 컨텍스트를 사용
      - AnnotationConfigApplicationContext 사용
▼ 스테레오타입 애노테이션의 종류
스테레오타입 애노테이션적용 대상
@Repository데이터 액세스 계층의 DAO 또는 레포지토리 클래스에 사용.
DataAccessException 자동변환과 같은 AOP의 적용 대상을 선정하기 위해서도 사용
@Service서비스 계층의 클래스에 사용
@Controller프레젠테이션 계층의 MVC 컨트롤러에 사용.
스프링 웹 서블릿에 의해 웹 요청을 처리하는 컨트롤러 빈으로 선정

◼ 자바 코드에 의한 빈 등록 : @Configuration 클래스의 @Bean 메소드

스프링은 코드를 이용해서 오브젝트를 생성하고 DI를 진행하는 방식으로 만들어진 오브젝트빈으로 쓸 수 있는 방법을 제공
- <bean>을 이용해 빈으로 등록되어야 하는 팩토리 빈은 하나의 빈만 정의가능
자바 코드에 의한 빈 등록 기능은 하나의 클래스에 여러 개의 빈 생성 가능
또한, 애노테이션을 통해 메타정보를 추가할 수도 있음

@Configuration //자바 코드로 빈을 정의하기 위해 사용하는 애노테이션
public class AnnotatedHelloConfig { // 자기 자신도 빈으로 등록됨!
	@Bean //빈 선언
    public AnnotatedHello annotatedHello() { //메소드 이름이 빈의 아이디
    	return new AnnotatedHello(); // 리턴되는 오브젝트를 빈으로 활용함!
    }
}

위에서 만든 빈들은 빈 설정 메타정보의 디폴트 값(scope)에 의해 싱글톤으로 만들어짐!


자바 코드에 의한 설정이 XML과 같은 외부 설정파일을 이용하는 것 보다 유용한 점

  • 컴파일러나 IDE를 통한 타입 검증이 가능
    • XML은 텍스트 문서이기 때문에 검증이 쉽지 않음.
  • 자동완성과 같은 IDE 지원 기능을 최대한 이용 가능
    • XML보다 작성해야하는 양이 많아보일지라도 자동완성 기능을 통해 더 빠르게 작성 가능
  • 이해가 쉬움
    • XML에 익숙하지 않은 개발자의 경우 XML 설정은 이해하기 어려움
  • 복잡한 빈 설정이나 초기화 작업을 손쉽게 적용할 수 있음
    • 팩토리 빈보다 더욱 간편하게 빈을 생성해낼 수 있다.

◼ 자바 코드에 의한 빈 등록 : 일반 빈 클래스의 @Bean 메소드

@Configuration을 사용하지 않고 @Bean을 통해 빈 생성이 가능

public class HelloService {
	...
    @Bean
    public Hello hello() {
    	Hello hello = new hello();
        hello.setPrinter(printer());
        return hello;
    }
    
    @Bean
    public Hello hello2() {
    	Hello hello = new Hello();
        hello.setPrinter(printer());
        return hello;
    }
    
    @Bean
    public Printer printer() { return new StringPrinter(); }
}

@Configuration이 붙지 않으면 생성되는 빈들은 모두 싱글톤이 아님!!

매번 같은 오브젝트를 사용하기 위해서는 다음과 같이 코드를 수정해야 함

public class HelloService {
	private Printer printer;
    
    public void setPrinter(Printer printer) { //내부에서 정의되는 빈을 DI받기
    	this.printer = printer;
    }

	...
    @Bean
    public Hello hello() {
    	Hello hello = new hello();
        hello.setPrinter(this.printer); //DI받은 printer 사용
        return hello;
    }
    
    @Bean
    public Hello hello2() {
    	Hello hello = new Hello();
        hello.setPrinter(this.printer); //DI받은 printer 사용
        return hello;
    }
    
    @Bean
    public Printer printer() { return new StringPrinter(); }
}

◼ 빈 등록 메타정보 구성 전략

빈 등록 방법으로 꼭 한 가지를 선택해야하는 것이 아님. 한 가지 이상의 방법을 조합해 사용할 수도 있음. 여기서는 자주 사용되는 설정 방법을 알아볼 것이다.

XML 단독 사용

모든 빈을 명시적으로 XML에 등록하는 방법
- 모든 빈을 XML에서 확인하고 관리할 수 있음
- 모든 빈이 XML에 있어서 관리하기가 어려움
- 모든 설정정보를 자바 코드에서 분리하고 순수한 POJO코드를 만들고 싶다면 채택

XML과 빈 스캐닝의 혼용

XML과 빈 스캐너에 의한 자동인식 방법을 함께 사용
- 복잡하지 않은 빈들은 빈 스캐너로, 자동등록이 힘든 기술 서비스, 기반 서비스, 컨테이너 설정 등은 XML을 사용
- 각 방법의 장점만을 살릴 수 있다면 가장 효과적인 방법

XML 없이 빈 스캐닝 단독 사용

아예 모든 빈의 등록을 XML없이 자동스캔만으로 가져가는 방식
- 애플리케이션 컴포넌트, 각종 기술 서비스와 컨테이너 설정용 빈 모두 자동등록
- 모든 빈의 정보가 자바 코드에 담겨 있으므로 빈의 설정정보를 타입에 안전한 방식으로 작성 가능
- 스프링이 제공하는 스키마에 정의된 전용 태그(aop, tx ...)를 사용할 수 없다..

📝 빈 의존관계 설정 방법

빈 오브젝트 사이의 DI를 위한 의존관계 메타정보를 작성하는 방법을 알아보자

◼ XML: <property>, <constructor-arg>

<bean>을 이용해 빈을 등록했다면 프로퍼티와 생성자 두 가지 방식으로 DI 지정
- 프로퍼티는 수정자 메소드, 생성자는 빈 클래스의 생성자를 이용

<property>: 수정자 주입

수정자를 통해 의존관계의 빈을 주입하려면 <property> 태그를 사용할 수 있다.
- 하나의 프로퍼티가 하나의 빈 또는 값을 DI하는 데 사용될 수 있음

<constructor-arg>: 생성자 주입

독립적인 수정자 메소드를 이용하는 수정자 주입 방식과 달리 한 번에 여러 개의 오브젝트를 주입할 수 있다

◼ XML: 자동 와이어링

자동으로 관계가 맺어져야 할 빈을 찾아서 연결해줌

byName: 빈 이름 자동와이어링

보통 빈의 이름은 인터페이스나 클래스 이름을 따라감. 이 관례를 이용하는 방법
- 변수가 많을 경우, 모든 이름을 규칙에 따라 부여하는 것이 부담

<bean id="hello" ...>
	<property name="printer" ref="printer" /> //프로퍼티 이름 = 참조 빈 이름
</bean>

<bean id="printer" class="...StringPrinter" />

//이름을 이용한 자동와이어링 적용 
<bean id="hello" class="...Hello" autowire="byName">
	<property name="name" value="Spring" /> //이름을 선언하는 부분이 생략됨
</bean>

<bean id="printer" class="...StringPrinter" />

byType: 타입에 의한 자동와이어링

프로퍼티의 타입과 각 빈의 타입을 비교해서 자동으로 연결해주는 방법
- 이전에 만든 클래스 재사용 가능
- 같은 타입이 두 개 이상 존재하면 자동와이어링이 적용되지 못하는 단점
- 이름을 통한 자동와이어링보다 느린 편이다

<bean id="hello" class="...Hello" autowire="byType">...</bean>
<bean id="mainPrinter" class="...StringPrinter" /> //같은타입의 다른이름 빈 생성가능

XML 내부 자동와이어링 방법의 단점

  • XML만 보고는 빈 사이의 의존관계를 파악하기 힘들다
    - 컨테이너가 런타임 시에 자동으로 의존관계 정보를 코드를 통해 생성해냄
    - 코드와 XML을 함께 보지 않으면 정보를 얻기 힘듦
  • 이름을 이용한 자동와이어링의 경우 이름에서 오타가 난다면 DI가 되지않음
  • 한 가지 빈에 하나의 자동와이어링 방식밖에 적용하지 못함
    - 자동와이어링의 한계

◼ XML: 네임스페이스와 전용 태그

전용 태그에 의해 자동으로 등록되는 빈이 <bean>으로 선언되는 다른 빈의 프로퍼티에 DI되거나 반대로 전용 태그의 빈이 다른 빈을 참조하기도 한다.

전용 태그에 의해 만들어지는 빈을 다른 빈이 참조할 경우

관례적으로 id 애트리뷰트를 이용해 빈의 아이디를 지정

<oxm:jaxb2-marshaller id="unmarshaller" contextPath="..." /> //

//위 빈을 다른 빈에 DI할 때 다음과 같이 넣어줌
<bean>
  <property name="unmarshaller" ref="unmarshaller" />
  <property name="sqlRegistry" ref="sqlRegistry" />
</bean>

전용 태그에서 다른 빈을 참조하는 경우

일반적으로 스프링의 전용 태그는 -ref로 끝나는 애트리뷰트를 이용해 DI할 빈 지정

<aop:config>
	<aop:advisor advice-ref="transactionAdvice" pointcut="bean(*Service)" />
</aop:config>

<bean id="transactionAdvice" ...>

◼ 애노테이션: @Resource

<property>와 비슷하게 주입할 빈을 아이디로 지정하는 방법
- 자바 클래스의 수정자 뿐만 아니라 필드에도 붙일 수 있음

수정자 메소드

가장 대표적인 DI 방법.

public class Hello {
	private Printer printer;
    ...
    @Resource(name="printer") //<property name="printer" ref="printer" />와 동일
    public void setPrinter(Printer printer) {
    	this.printer = printer;
    }
}

애노테이션으로 된 의존관계 정보를 이용해 DI가 이뤄지게 하려면 다음 세 가지 방법 중 하나를 선택해야 한다.

  • XML의 <context:annotation-config />
    • 애노테이션 의존관계 정보를 읽어 메타정보를 추가해주는 기능을 가진 빈 후처리기를 등록해주는 전용 태그

  • XML의 <context:component-scan />
    • 빈 스캐닝을 통한 빈 등록 방법을 지정하는 태그

  • AnnotationConfigApplicationContext 또는 AnnotationConfigWebApplicationContext

필드

@Resource는 필드에도 붙을 수 있다.

@Component
public class Hello {
	
    @Resource(name="printer") // 참조할 빈의 이름 지정. 생략도 가능
    private Printer printer; //생략시에는 필드의 이름 = 빈의 이름
    
    //setPrinter() 메소드 없음
    //수정자 메소드가 없어도 스프링이 필드에 직접 DI를 해줌 - 필드 주입
    ...
}

@Resource와 XML의 이름을 이용한 자동와이어링의 차이점

  • XML의 자동와이어링은 각 프로퍼티에 주입할 후보 빈이 없으면 그냥 넘어감
    - @Resource참조할 빈이 반드시 존재해야한다. 없을 시 예외 발생.

◼ 애노테이션: @Autowired/@Inject

타입에 의한 자동와이어링 방식으로 동작
- @AutowiredXML의 타입에 의한 자동와이어링의 방식을 확장한 것

수정자 메소드와 필드

@Resource와 같은 방식으로 사용하지만 작동되는 원리가 다를 뿐
- @Resource는 이름을 이용, @Autowired는 타입을 이용
- @Resource와 같이 일단 지정하면 반드시 DI할 후보 빈이 존재해야 한다.

public class Hello{ // 수정자/필드 중 한 가지 방법 사용
	@Autowired //필드에 주입.
    private Printer printer; 
	@Autowired //수정자 메소드에 주입.
	public void setPrinter(Printer printer){
    	this.printer = printer;
    }

생성자

생성자에도 @Autowired 부여 가능.
- 생성자의 모든 파라미터타입에 의한 자동와이어링이 적용
- 단 하나의 생성자에만 @Autowired를 사용해야한다. - DI해야 할 생성자를 지정

public class BasSqlService implements SqlService {
	protected SqlReader sqlReader;
    protected SqlRegistry sqlRegistry;
    
    @Autowired //하나의 생성자에만 부여할 수 있음
    public BasSqlService(SqlReader sqlReader, SqlRegistry, sqlRegistry) {
    	this.sqlReader = sqlReader;
        this.sqlRegistry = sqlRegistry;
    }
    ...
}

일반 메소드

수정자 메소드 주입생성자 주입은 각각 장단점이 존재. 그래서 일반 메소드를 사용하는 DI 방법이 등장
- 파라미터를 가진 메소드를 만들고 @Autowired를 붙여주면 각 파라미터의 타입을 기준으로 자동와이어링해서 DI해줄 수 있음.
- 생성자 주입과 달리 여러 개를 만들어도 된다.
- 단, XML을 통해서는 의존관계를 설정할 방법이 없다는 단점 존재

public class BasSqlService implements SqlService {
	protected SqlReader sqlReader;
    protected SqlRegistry sqlRegistry;
    
    @Autowired //한 개 이상의 설정용 메소드에 부여 가능
    public void config(SqlReader sqlReader, SqlRegistry, sqlRegistry) {
    	this.sqlReader = sqlReader;
        this.sqlRegistry = sqlRegistry;
    }
}

컬렉션과 배열

같은 타입의 빈이 하나 이상 존재할 때, 모두 DI받도록 만들 수 있다.
@Autowired의 대상이 되는 필드나 프로퍼티, 메소드의 파라미터를 컬렉션이나 배열로 선언하면 된다.

@Autowired
Collection<Printer> printers; // Set<Printer>, List<Printer>로 선언해도 된다

@Autowired
Printer[] printers; // 배열을 사용해도 된다.

@Autowired
Map<>String, Printer> printerMap; //String이 빈의 아이디/이름, Printer가 값

@Qualifier

타입 외의 정보를 추가해서 자동와이어링을 세밀하게 제어할 수 있는 보조적인 방법
- 같은 타입의 빈이 하나 이상일 때, @Qualifier를 선언해 자동와이어링 대상을 제한

@Autowired
@Qualifier("mainDB") //빈 중에서 <qualifier> 태그가 존재하고, 값이 mainDB인 빈을 DI
DataSource dataSource;
<bean id="oracleDataSource" class="...XxxDataSource" >
	<qualifier value="mainDB" /> //qualifier 태그
</bean>

@Qualifier빈에 부가적인 속성을 지정해주는 효과가 있다.
- 메타 애노테이션으로 @Qualifier를 가지는 애노테이션도 같은 기능 사용 가능

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface DataBase {
	String value();
}

//위에서 만든 한정자 애노테이션 사용
@Autowired
@Database("main")
DataSource dataSource;

@Qualifier는 생성자, 일반메소드에 부여하더라도 의미가 없음!
- 사용하려면 파라미터에 직접 @Qualifier를 붙여야한다

@Autowired
public void config(@Qualifier("mainDB") DataSource dataSource, Printer printer) {

@Autowired는 해당 프로퍼티의 DI를 선택적으로 가능하게할 수 있음
- required 엘리먼트의 값을 false로 해주면 된다.

@Autowired(required=false) Printer printer;

@javax.inject.Inject

@Autowired와 유사하지만, required 엘리먼트에 해당하는 선택기능이 없다.

@javax.inject.Qualifier

스프링의 @Qualifier와 이름은 같지만, 패키지와 그 사용 방법이 다름
- 다른 한정자 애노테이션을 정의하는 용도로만 사용 가능

@Retention(RetentionPolicy.RUNTIME)
@javax.inject.Qualifier
public @interface Main {
}

◼ 자바 코드에 의한 의존관계 설정

@Configuration과 @Bean을 이용해서 자바 코드로 빈을 등록하는 경우에 빈의 의존관계를 설정하는 방법을 알아보자.

애노테이션에 의한 설정 @Autowired, @Resource

빈은 자바 코드에 의해 생성되지만 의존관계는 빈 클래스의 애노테이션을 이용하게 만들 수 있음
- @Autowired와 같은 애노테이션을 통한 의존관계 설정빈 오브젝트 등록을 마친 후에 후처리기에 의해 별도의 작업으로 진행되기 때문

public class Hello{
	@Autowired Printer printer;


//빈의 오브젝트만 생성해서 의존관계는 애노테이션 설정용 후처리기에 의해 별도로 설정됨
@Configuration
public class Config {
	@Bean public Hello hello() {
    	return new Hello(); 
    }
    
    @Bean public Printer printer() {
    	return new Printer();
    }
}

@Bean 메소드 호출

@Configuration과 @Bean을 사용하는 자바 코드 설정 방식의 기본은 메소드로 정의된 다른 빈을 메소드 호출을 통해 참조하는 것
- @Bean이 붙은 메소드 자체가 하나의 빈 이름처럼 사용
@Configuration이 붙지 않은 클래스의 @Bean 메소드에서는 이 방식을 사용하면 안된다. - @Configuration이 붙지 않으면 싱글톤이 아님!

@Configuratioin
public class Config {
	@Bean public Hello hello() {
    	Hello hello = new hello();
        hello.setPrinter(printer()); //printer()메소드를 실행
        return hello; //돌아온 오브젝트를 직접 수정자를 통해 DI
    }
    
    @Bean public Printer printer() {
    	return new Printer();
    }
    ...
}

@Bean과 메소드 자동와이어링

@Bean 메소드 호출의 단점을 극복하기 위해 사용.
@Bean 메소드를 호출하는 대신 그 빈의 레퍼런스를 파라미터로 주입받는 방식을 사용하는 것

  • 파라미터로 빈의 레퍼런스를 받기 때문에 자바 코드가 자연스러움.
  • @Qualifier를 추가해도 되며, 하나 이상의 파라미터를 사용할 수도 있음
    - 이렇게 제공되는 빈은 @Configuration이 붙은 클래스 안에서 정의되지 않아도 상관없다.
@Configuration
public class Config {
	
	@Bean public Hello hello(Printer printer) { //파라미터로 Printer타입 빈 정보 제공
    	Hello hello = new hello();
        hello.setPrinter(printer); 
        return hello; 
    }
    
    //printer() 메소드에 의해 Printer타입의 빈이 선언됨
    @Bean public Printer printer() {
    	return new Printer();
    }
    ...
}

◼ 빈 의존관계 설정 전략

의존관계 설정 방법을 빈 등록 방법과 조합하면 여러 조합이 가능하겠지만 여기서는 자주 쓰이는 방법들을 살펴볼 것이다.

XML 단독

빈 등록은 물론이고 의존관계 설정까지 모두 XML만으로 구성하는 방법

  • 네임스페이스와 전용 태그의 사용은 필수.
  • XML 자동와이어링 혹은 의존관계 정보를 직접 정의할 것인지 선택해야한다.
    • 가능한 한 XML 자동 와이어링을 사용하는 것이 좋다
      • XML 자동와이어링은 가능한 이름에 의한 방식을 사용하는 것이 좋다
        - 타입에 의한 방식은 불편하며 느리다.

XML과 애노테이션 설정의 혼합

빈은 XML로 등록하지만 의존관계 정보애노테이션으로 이용하는 방법

  • 빈 스캐너로 자동 등록할 경우 등록 대상을 정확히 파악하기 힘들 수 있음
    - <bean> 태그를 이용한 빈 등록을 선호할 수 있음
  • <property>를 일일이 선언해서 의존관계를 설정해주기는 번거롭고, 그렇다고 XML의 일괄적인 자동와이어링은 불편하다고 느낄 수 있음
    - 의존관계 설정은 애노테이션을 이용해 세밀하게 제어

애노테이션 단독

빈의 등록도 @Component로 빈 스캐너에게 맡기고, 의존관계 역시 @Autowired와 같은 애노테이션을 이용자동으로 등록하는 방법
- XML이 필요 없기 때문에 생산성이 높고 수정이 편리
- 일부 기술 서비스 빈이나 컨테이너 설정용 빈은 XML을 이용해도 됨

📝 프로퍼티 값 설정 방법

스프링에서 이란 스프링이 관리하는 빈을 제외한 모든 것
- 오브젝트도 빈에 주입된다면 값이라고 할 수 있음

📝 메타정보 종류에 따른 값 설정 방법

값을 넣는 방법에 대해 알아보자!

◼ XML:<property>와 전용 태그

<property>태그에서 ref대신 value 애트리뷰트를 사용하면 런타임 시 주입할 값으로 인식한다

<bean id="hello" ...>
  <property name="name" value="Everyone" /> //setName(String name)메소드를 호출하여 Everyone(값) 주입!
  ...
</bean>
  • 값이 String이 아닌 다른 타입이면 변환이 필요하다
    - 스프링은 String을 프로퍼티 타입으로 변환해주는 변환 서비스를 내장
    ⁕ ref 외에는 대부분 프로퍼티 값이라고 생각하면 된다.
    - pointcut도 값으로 보는 것

⁕ 코드와 외부 설정을 분리하는 경우?

  • 환경에 따라 매번 달라져서 수정이나 변경이 많은 값인 경우
  • 특별한 이벤트 시에 초기 값 대신 다른 값을 지정하고 싶은 경우


    이러한 분리를 통해 소스 코드의 수정 없이 값을 지정해줄 수 있게됨!

◼ 애노테이션: @Value

소스코드의 애노테이션을 이용해서도 프로퍼티 값을 지정할 수 있다

public class Hello {
	private String name;
    	@Value("Everyone") // <property name="name" value="Everyone" /> 과 같음
        public void setName(String name) {
        	this.name = name;
        }
}

이 방법은 잘 사용되지 않는다.
- @Value는 주로 자바 코드 외부의 리소스나 환경정보에 담긴 값을 사용하도록 지정하는데 사용한다

@Value("#{systemProperties['os.name']}") //시스템 프로퍼티의 os.name 값을 가져와 주입
String name;

◼ 자바코드:@Value

@Configuration과 @Bean을 사용하는 경우에도 프로퍼티 값을 외부로 독립시킬 수 있다. 이때는 클래스 자체가 메타정보이므로 수정이나 재컴파일이 문제가 되지는 않음.

하지만! 환경에 종속적인 정보환경정보나 프로퍼티 파일에서 가져오는게 바람직!

@Autowired를 활용했던 것처럼 @Value도 사용가능

@Configuration
public class Config {
	@Value("${database.username}")
    private String name;
    
    @Bean
    public Hello hello() {
    Hello hello = new Hello();
    	hello.setName(this.name);
        return hello;
    }
}

📝 PropertyEditor와 ConversionService

앞서 보았듯이 XML의 value 애트리뷰트에 String이 아닌 다른 타입의 값이라면 변환이 필요하고 스프링은 이를 위한 변환 서비스를 제공한다.
PropertyEditor는 디폴트로 사용되는 타입 변환기.
스프링 3.0 이후부터는 ConversionService로 대신해서 사용할 수 있다.

◼ 프로퍼티 파일을 이용한 값 설정

때로는 XML에서 다시 일부 설정정보를 별도의 파일로 분리해두면 유용할 때가 있다

  • 서버환경에 종속적인 정보가 있다면, 이를 애플리케이션 구성정보에서 분리하기 위함
  • @Value를 효과적으로 사용 가능
    - @Value에서 프로퍼티 파일의 내용을 참조하게 해주면 소스코드 수정 없이 @Value를 통해 프로퍼티에 주입되는 값을 변경할 수 있음

Vol.1에서 작성했던 dataSource 빈의 설정 내용을 분리해보자

<bean id="dataSource"
      class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver" />
  	<property name="url" value="jdbc:mysql://localhost/testdb" />
  	<property name="username" value="spring" />
  	<property naem="password" value="book" />
</bean>

value 애트리뷰트 안에 직접 선언한 값들을 별도의 프로퍼티 파일로 옮겨보자
클래스패스 루트에 database.properties라는 파일을 만들어 다음 내용을 넣는다

db.driverclass=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost/testdb
db.username=spring
db.password=book

이렇게 분리한 정보를 dataSource 빈의 프로퍼티 값으로 사용할 수 있게 해야한다
이때 사용할 수 있는 방법은 두 가지가 있다

수동 변환: PropertyPlaceHolderConfigurer

프로퍼티의 치환자(${ })를 이용하는 방법
치환자를 붙인 값과 동일한 value선언을 프로퍼티 파일에서 찾아 바꿔치기 하는 방식

//치환자를 이용하여 value애트리뷰트에 넣어주기
<bean id="dataSource"
      class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
	<property name="driverClass" value="${db.driverclass}" />
  	<property name="url" value="${db.url}" />
  	<property name="username" value="${db.username}" />
  	<property naem="password" value="${db.password}" />
</bean>
//context 네임스페이스의 property-placeholder 태그를 추가하고 프로퍼티 파일 위치 지정
<context:property-placeholder location="classpath:database.properties />
  • 장점(위의 예시 기준)
    • 환경에 따라 다른 DB 연결정보를 이용할 수 있게된다
    • DB 연결정보가 변경되더라도 XML을 수정하는 대신 파일만 수정해주면 된다.

능동 변환: SpEL

SpEL(Spring Expression Language)를 이용하는 방법
다른 빈 오브젝트에 직접 접근할 수 있는 표현식을 이용원하는 프로퍼티 값을 능동적으로 가져오는 방법

SpEL : 스프링 3.0에서 처음 소개된 스프링 전용 표현식 언어
- 기본적으로 #{ }안에 표현식을 넣도록 되어있음

<bean id="hello" ...>
	<property name="name" value="Spring" />
</bean>
<bean id="names">
   <property name="helloname" value="#{hello.name}"> //이름이 hello인 빈의 name 프로퍼티 
</bean>

◼ 컨테이너가 자동등록하는 빈

스프링 컨테이너는 초기화 과정에서 몇 가지 빈을 기본적으로 등록해줌

ApplicationContext, BeanFactory

스프링에서는 컨테이너 자신을 빈으로 등록해두고 필요하면 일반 빈에서 DI받아 사용 가능
- 애플리케이션 컨텍스트를 일반 빈에서 사용하고 싶다면 ApplicationContext타입의 빈을 DI받도록 선언해주면 된다. - 이름이 없으므로 @Autowired로 자동 와이어링

public class SystemBean {
	@Autowired ApplicationContext context; //@Autowired 대신 @Resource를 사용해도 됨
    
    public void specialJobWithContext() {
    	this.context.getBean(...); // 애플리케이션 컨텍스트를 직접 사용하는 코드 작성
    }
}

ApplicationContext의 구현 클래스는 내부에 빈 팩토리 오브젝트를 별도로 만들어 위임하는 방식을 사용
- 컨텍스트 내부의 빈 팩토리를 사용하려면 BeanFactory타입으로 DI 해주어야 한다
BeanFactory로 DI받는 오브젝트는 ApplicationContext로 가져오는 오브젝트와 다름

ResourceLoader, ApplicationEventPublisher

스프링 컨테이너는 ResourceLoader이기도 하다.
- 서버환경에서 다양한 Resource를 로딩할 수 있는 기능 제공


코드를 통해 서블릿 컨텍스트의 리소스를 읽어오려면 컨테이너를 ResourceLoader 타입으로 DI받아 사용
- @Autowired를 사용하거나 ResourceLoaderAware인터페이스를 구현하여 사용

@Autowired ResourceLoader resourceLoader;

public void loadDataFile() {
	Resource resource = this.resourceLoader.getResource("WEB-INF/info.dat");
    ...
}

ApplicationEventPublisher는 ApplicationListener를 구현한 빈에 이벤트를 발생시킬 수 있는 메소드(publishEvent())를 가진 인터페이스

❗ 거의 사용되지 않음. - 빈 사이에 독자적인 이벤트/리스너 구성을 하면 충분하기 때문

systemProperties, systemEnvironment

스프링 컨테이너가 직접 등록하는 빈 중에서 이름을 통해 접근할 수 있는 빈

  • systemProperties는 Properties타입의 오브젝트를 읽기전용으로 접근할 수 있게 만든 빈 오브젝트
    - JVM이 생성하는 시스템 프로퍼티의 값을 읽을 수 있게 해줌
  • systemEnvironment는 환경변수가 담긴 Map 오브젝트
    - 환경변수의 이름은 OS의 종류나 서버환경 설정에 따라 다르므로 주의

❗ 분량이 많아 나머지는 (하) 편으로 작성 예정

profile
모르는 것 정리하기

0개의 댓글