스프링 3.1에 새롭게 도입된 IoC/DI 기술은 두가지다.

  • 강화된 자바 코드 빈 설정
    • 자바코드를 이용한 설정 방식을 빈 설정 메타정보 작성에 본격적으로 사용할 수 있도록 기능 확장
  • 런타임 환경 추상화
    • 개발, 테스트, 운영 단계에서 IoC/DI 구성이 달라질 때 효과적으로 관리할 수 있게 해주는 런타임 환경정보 관리 기능

빈의 역할과 구분

애플리케이션을 구성하는 빈의 종류와 특징은 아래와 같다 :

애플리케이션 로직 빈

  • 스프링에서 말하는 빈은 IoC/DI 컨테이너에 의해 생성되고 관리되는 오브젝트다.
  • 일반적으로 애플리케이션의 로직을 담고 있는 주요 클래스의 오브젝트가 빈으로 지정된다.

애플리케이션 인프라 빈

  • DataSource, DataSourceTrasactionManager와 같이 애플리케이션의 로직을 직접 담당하지 않고, 로직 빈을 지원하는 빈이다.
  • 로직에 직접 참여하지 않기 때문에 로직 빈과 구분해서 애플리케이션 기반 빈 혹은 애플리케이션 인프라스트럭처 빈이라고 부른다.

컨테이너 인프라 빈

  • AOP 공부할 때 DefaultAdvisorAutoProxyCreator를 통해 선정된 빈을 프록시로 바꿔줄 수 있다는 것을 배웠다. 이 빈은 스프링 컨테이너의 기능에 관여한다(=스프링 컨테이너가 빈을 생성할 때 프록시로 생성하도록 작업 지시) *스프링 IoC/DI 컨테이너의 기능을 확장하는 방법은 확장 기능을 가진 오브젝트를 스프링의 빈으로 등록하는 것이다.
  • 스프링 컨테이너의 기능을 확장해 빈의 등록과 생성, 관계설정, 초기화 등의 작업에 참여하는 빈을 인프라 빈이라고 부른다.

컨테이너 인프라 빈과 전용 태그

  • 태그를 이용해 직접 빈을 등록할 수 있지만, 전용 태그를 사용하는 방법이 더 많이 쓰인다.
  • 컨테이너 인프라 빈은 보통 이름이 길고, 한 번에 여러 개의 빈을 동시에 설정해야 하는 경우가 많아 전용 태그를 사용해 간접적으로 등록하는 방법을 권장한다.
@Configuration
public class SimpleConfig {
	@Autowired Hello hllo;
	
	@Bean Hello hello() {
		return new Hello();
	}
}

public class Hello {
	@PostConstruct
	public void init() {
		System.out.println("Init");
	}
	public void sayHello() {...}
}

위와 같이 자바코드로 빈을 설정하고, xml에서 bean id를 지정한 후 아래와 같이 빈이 등록되었는지를 살펴보는 코드를 작성해보자.

	ApplicationContext context = new GenerixXmlApplicationContext(BeanRoleTest.class, "beanrole.xml");
	SimpleConfig sc = context.getBean(SimpleConfig.class);
	sc.hello.sayHello();
  • 위 코드는 NPE가 발생한다.
  • 스프링 IoC/DI 컨테이너에는 @Configuration/ @Bean을 이용해 새로운 빈을 등록해주는 기능이 없기 때문이다.
  • 위 코드가 바르게 동작하려면 XML에 다음 한 줄을 추가해주면 된다.
<context:annotation-config    />
  • 태그는 context 네임스페이스의 태그를 처리하는 핸들러를 통해 특정 빈이 등록되게 해줄 뿐이다.
  • 이 때 등록되는 빈이 스프링 컨테이너를 확장해 빈의 등록과 관계 설정, 후처리 등에 새로운 기능을 부여하는 컨테이너 인프라 빈이다.
  • 위 태그 작성시 총 아래 6개의 빈이 추가된다 :
    • ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor
    • ConfigurationClassPostProcessor
    • AutowiredAnnotationBeanPostProcessor
    • RequiredAnnotationBeanPostProcessor
    • PersistenceAnnotationBeanPostProcessor
  • 대부분 빈의 메타정보나 빈 오브젝트를 가공하는 후처리기다.
  • 꼭 해당 태그로 작성하지 않아도 되고, 어떻게든 스프링의 빈으로 등록되게 하면 된다.

빈의 역할

  • int ROLE_APPLICATION = 0;
  • int ROLE_SUPPORT = 1;
  • int ROLE_INFRASTRUCTURE = 2;
  • 이전에는 자바 enum이라는 개념이 없어 int 상수 값으로 지정해주었다.(Java 5부터 enum이라는 개념 도입)
  • 스프링 3.1부터는 @Role 애노테이션을 통해 빈의 역할과 성격에 따라 구분할 수 있도록 하였다.

컨테이너 인프라 빈을 위한 자바 코드 메타정보

  • 역할을 세 가지로 구분하는 이유 : 이 세가지 종류의 빈은 빈 설정 메타정보를 작성하는 방법과 전략을 각각 다르게 가져가기 때문이다.

자바 코드를 이용한 컨테이너 인프라 빈 등록

  • 스프링 3.1에서는 @Bean 메소드가 없어도 @Configuration 클래스를 사용할 수 있다.
  • @ComponentScan : 스테레오 타입 애노테이션이 붙은 빈을 자동으로 스캔해서 등록해준다.
@Configuration
@ComponentScan("springbook.learningtest.spring31.ioc.scanner")
public class AppConfig {
}
  • @Import : 다른 @Configuration 클래스를 빈 메타정보에 추가할 때 사용한다.
    • @Configuration 클래스는 각각 하나의 XML 파일과 같다고 볼 수 있다.
  • @ImportResource :
    • XML에서 사용되던 주요한 전용 태그를 자바 클래스에서 애노테이션과 코드로 대체할 수 있게 해준다.
    • XML이 꼭 필요한 빈 설정만 별도의 파일로 작성한 뒤, @Configuraion 클래스에서 @ImportResource를 이용해 XML 기반의 빈 설정을 가져올 수 있다.
  • @EnableTransactionManagement :
    • @Configuration 클래스에 사용할 수 있는 애노테이션이다.
    • 태그와 동일한 기능을 수행한다.


웹 애플리케이션의 새로운 IoC 컨테이너 구성

  • 웹 환경에서는 보통 루트 애플리케이션 컨텍스트와 서블릿 애플리케이션 컨텍스트의 두 단계로 분리해 사용하는 경우가 일반적이다.
    • 루트 컨텍스트 : 웹 애플리케이션의 실제 비즈니스 혹은 목적을 위한 service 레이어 및 해당 서비스 레이어에서 조회 및 처리에 필요한 database와 연결되어 있는 repository layer를 구성하는 빈들에 대한 설정
    • 서블릿 컨텍스트 : 웹 애플리케이션에서 클라이언트의 요청을 받기 위한 entry point, 보통 controller, view resolver, 웹과 관련된 빈을 설정한다.
  • 루트 컨텍스트와 서블릿 컨텍스트는 각각 web.xml의 에 컨텍스트의 설정 관련 정보를 넣어 웹 애플리케이션이 시작될 때 자동으로 생성되게 만든다.
  • 두 가지 모두 애플리케이션 컨텍스트가 사용하는 기본 정보가 XML이기 때문에, 스프링 3.1에서는 XML을 배제하고 자바 코드 설정 메타정보만을 사용하거나, 자바 코드 설저을 주로 하고 XML을 보조적으로 사용할 수 있다.

런타임 환경 추상화와 프로파일

  • 스프링의 빈 설정 메타정보는 빈의 클래스와 값 속성, 다른 빈과의 관계로 이루어져 있다.
  • 애플리케이션 기능이 바뀌지 않는다면 메타정보도 대부분 바뀌지 않지만, 환경에 따라 바뀌어야 하는 것을이 있다.

환경에 따른 빈 설정정보 변경 전략과 한계

  • 스프링이 만든 애플리케이션은 성격이 다른 여러 환경(개발, QA, 운영) 에서 동작한다.
  • 각각의 다른 환경에서 설정정보를 어떻게 다르게 설정할 수 있을까?

빈 설정파일 변경

  • 가장 손쉽게 생각할 수 있는 방법으로, 메타정보를 담은 XML이나 클래스를 다로 준비하는 것이다.
  • 개발이나 유지보수가 계속 진행되며 설정정보가 지속적으로 달라지는 경우라면 이런 방법은 번거롭고 위험하다.
  • 100개의 빈이 등록된 XML 파일 중, 2~3개의 빈만 설정이 달라질 경우 빈이 추가될 때 마다 3개의 XML 파일에 일일이 추가하고 수정해야 한다.

프로퍼티 파일 활용

  • 빈 설정정보를 담은 파일을 통째로 여러 벌 가져가는 대신 환경에 따라 달라지는 정보를 담은 프로퍼티 파일을 활용하는 방법도 있다.
<bean id="dataSource" clas=="...SimpleDriverDataSource" >
	<property name="driverClass" value="${db.driverClass}"   />
	<property name="url" value="${db.url}"   />
	<property name="username" value="${db.username}"   />
	<property name="password" value="${db.password}"   />
</bean>
  • 애플리케이션을 구성하는 빈의 설정정보를 환경에 독립적으로 작성해서 환경이 바뀌어도 XML 파일은 수정할 필요가 없다. 각 환경에 따라 달라지는 정보만 프로퍼티 파일 등으로 준비하면 된다.
  • 개발에는 로컬 DB를 사용하도록 각 프로퍼티 항목을 지정하고, QA 환경에는 테스트용 DB를 사용하도록 준비하면 된다.


  • 앞서 소개한 방법은 스프링 3.0에서 주로 사용되던 방법이다. 하지만 이 방법으로는 감당할 수 없는 상황도 있다.
  • 환경에 따라 아예 빈 클래스가 바뀌거나 빈 구성이 달라지는 경우다.
  • 같은 개발환경이여도 두 개의 properties가 필요할 수 있다(개발에서 테스트시, 본인 로컬 DB를 사용)
  • 그 외에도 테스트중 내장형 DB를 사용해야 하는 경우도 있다. 이럴 경우 datasource의 db 타입과 초기 스크립트 또한 셋팅해주어야 하는 경우도 발생한다.

런타임 환경 프로파일

  • 스프링 3.1에서는 런타임 환경 추상화를 이용해 환경에 따라 빈 설정정보가 달라지는 문제를 해결할 수 있다.
  • 런타임 환경은 애플리케이션 컨텍스트에 새롭게 도입된 개념이다. 컨텍스트 내부에 Environment 인터페이스를 구현한 런타임 환경 오브젝트가 만들어져서 빈을 생성하거나 의존관계를 주입할 때 사용된다.

  • 런타임 환경은 프로파일과 프로퍼티 소스로 구성된다. 환경에 따라 프로파일과 프로퍼티 소스가 다르게 설정된 Environment 오브젝트가 실행되는 것이다.
  • 프로파일의 개념은 간단한데, 환경에 따라 다르게 구성되는 빈들을 다른 이름을 가진 프로파일 안에 정의하면 된다.
.....
<beans>
		<bean id="userDao" class="springbook.user.dao.UserDaoJdbc">
			<property name="dataSource" ref="dataSource"    />
		</bean>
		
		<beans profile="spring-test">
			<jdbc:embedded-databaase id="dataSource" type="HSQL">
				<jdbc:script location="schema.sql"    />
			</jdbc:embedded-database>
		</beans>
		
		<beans profile="dev">
			<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
				<property name="driverClass" value="${db.driverClass}"   />
				<property name="url" value="${db.url}"   />
				<property name="username" value="${db.username}"   />
				<property name="password" value="${db.password}"   />
			</bean>
		</beans>
</beans>
  • 위와 같이 작성하면 각각 테스트환경, 개발환경, 운영환경에서 사용되는 빈이라는 의미로 알 수 있다.
  • 중첩되어 나오는 가 루트 와 다른 점은, profile이라는 속성을 갖고 있다는 것이다.
  • 이 XML파일을 그대로 개발, 테스트, 운영환경에 넣고 어떤 프로파일을 사용할지 지정하면 지정된 프로파일과 같은 이름의 profile 애트리뷰트를 가진 에 지정된 빈이 사용된다.
  • 이렇게 프로파일을 이용하면 하나의 빈 설정파일로 여러 개의 다른 환경에서 각각 다른 빈 설정이 적용되도록 만들 수 있다.

사용할 활성 프로파일 지정 방법

  • 특정 프로파일에 정의된 빈을 사용하고 싶으면 해당 빈을 활성(active) 프로파일로 만들어주면 된다.
  • 프로파일은 XML이 로딩되거나 @Configuration이 적용되는 refresh() 메소드가 컨텍스트에서 실행되기 전에 지정해주어야 한다.
  • 활성 프로파일을 지정하지 않고 실행할 경우, profile이 붙은 내용은 다 무시된다.
  • 활성 프로파일을 두 개 이상 동시에 적용하게 되면, 우선순위가 가장 높은 방법으로 지정한 것이 적용된다.
    • 서블릿 초기화 파라미터 > 서블릿 컨텍스트 파라미터 > JNDI 프로퍼티 값 > 시스템 프로퍼티 > 환경변수 순으로 적용된다.

프로파일 활용 전략

  • 프로파일은 한 번에 두가지 이상 활성화 할 수 있다.
  • 예시 : DB 연결과 관련된 빈은 dsDev, dsTest, dsProduction 세가지로 구분하고 메일 서버 접속과 관련된 빈은 실제 동작하는 메일 서버와 연결하는 빈을 가진 mailServer와 테스트용으로 사용할 목 오브젝트를 가진 mockMailServer 프로파일로 구분 할 수 있다. 이 경우 개발시에는 아래와 같이 적용할 프로파일을 여러개 넣어줄 수 있다.
<context-param>
	<param-name>spring.profiles.active</param-name>
	<param-value>dsDesv, mockMailServer</param-value>
</context-param>
  • 이 때에는 애플리케이션이 동작하는데 필요한 빈이 모두 포함되어있는지 유의해야 한다.
  • 프로파일 적용 후, getActiveProfiles()를 통해 활성 프로파일 목록을 확인해보자.

  • @Configuration이 붙은 클래스도 동일하게 프로파일을 지정할 수 있다.
@Configuration
@Profile("dev")
public class DevConfig {
......
}

  • 위의 방법 말고도, 프로파일이 붙은 3개의 클래스를 AppConfig의 스태틱 중첩 클래스로 정의 할 수 있다.
@Configuration
public class AppConfig {
	@Bean UserDao userDao() {
		...
	}
	
	@Configuration
	@Profile("spring-test")
	public static class SpringTestConfig {
		....
	}
	
	@Configuration
	@Profile("dev")
	public static class DevConfig {
		...
	}
	
	@Configuration
	@Profile("production")
	public static class ProductionConfig {
		...
	}
}

프로퍼티 소스

  • XML이나 @Configuration 클래스로 작성하는 빈의 멤타정보는 애플리케이션의 구성 정보를 담고 있다.
  • 애플리케이션 기능과 구현방법이 변경되지 않으면 구성정보를 변경할 필요가 없지만, DB 연결정보처럼 환경에 따라서 달라지는 것은 빈 메타정보 보다는 프로퍼티 파일 같은 별도의 리소스를 사용해 분리하는 편이 바람직하다.

프로퍼티란?

  • 자바에서 말하는 프로퍼티는 보통 키와 그에 대응되는 값의 쌍을 말한다.
db.username=spring
db.password=book
Properties p = new Properties();
p.load(new FileInputStream("database.properties"));
  • 자바를 통해 가져오는방법 외에도 전용 태그를 이용해서 가져올 수 있다.
<util:properties id = "dbProperties" location="database.properties"  />
<context:property-placeholder location="database.properties"/> //키에 대은되는 치환자를 찾아 빈의 프로퍼티 값을 업데이트 해주는 기능
  • Properties는 기본적으로 ISO-8859-1 인코딩만 지원하기에 영문만 사용가능하다.
  • 해당 인코딩으로 표현 불가능한 문자는 u로 시갖하는 유니코드 값을 대신 사용해야 한다.
  • 보통 이런 경우 XML 포맷의 프로퍼티 파일을 사용하면 좋다.

스프링에서 지원하는 프로퍼티 종류

  • 스프링은 프로퍼티 파일 외에도 값을 지정하고 가져오는 다양한 방법을 지원한다.

환경변수

  • 스프링 애플리케이션이 구동되는 OS의 환경변수이다.
  • System.getEnv()로 systemEnvironment 이름의 빈을 가진 프로퍼티 맵을 가져올 수 있다.

시스템 프로퍼티

  • JVM 레벨에서 정의된 프로퍼티를 말한다.
  • JVM이 시작될 때 시스템 관련 정보부터 자바 관련 정보, JVM 관련 정보등이 시스템 프로퍼티로 등록된다.

JNDI

  • WAS에 여러 개의 웹 애플리케이션이 올라가고 그 중 하나의 애플리케이션에만 프로퍼티를 지정하고 싶다면 JNDI 프로퍼티 또는 JNDI 환경 값 사용을 고려해 볼 수 있다.
  • JNDI 값을 코드로 작성하려면 복잡하지만, 스프링에서은 아래의 전용 태그 한 줄이면 충분하다.
<jee:jndi-lookup id="db.username" jndi-name="db.username"    />

서블릿 컨텍스트 파라미터

  • 웹 애플리케이션 레벨의 프로퍼티 지정을 원하지만, JNDI 값을 설정하기 번거롭다면 web.xml에서 서블릿 컨텍스트 초기 파라미터를 프로퍼티로 사용할 수 있다.
<context-param>
	<param-name>db.username</param-name>
	<param-value>spring</param-value>
</context-param>

서블릿 컨픽 파라미터

  • 개별 서블릿을 위한 설정이다. 서블릿 컨텍스트보다 범위가 좁다.

참고자료 :
토비의 스프링 3.1 Vol.2 1.5 - 스프링 3.1의 IoC 컨테이너와 DI


oksusutea's blog

꾸준히 기록하려고 만든 블로그