[Spring] 자바 코드 기반 설정


자바 코드 기반 설정


Spring JavaConfig 프로젝트는 XML이 아닌 자바 코드를 이용해서 컨테이너를 설정할 수 있는 기능을 제공하는 프로젝트로서, 이를 사용하면 XML이 아닌 자바 코드를 이용해서 생성할 빈 객체와

각 빈 간의 연관 등을 처리하게 된다.


1. @Configuration 어노테이션과 @Bean 어노테이션을 이용한 코드 기반 설정

org.springframework.context.annoatation 패키지의 @Configuration 어노테이션과 @Bean 어노테이션을 이용해서 스프링 컨테이너에 새로운 빈 객체를 제공할 수 있다. 


다음은 자바 코드를 이용해서 새로운 빈 객체를 스프링 컨테이너에 제공하는 클래스의 예를 보여주고 있다.

import org.springframework.context.annoatation.Bean;

import org.springframework.context.annoatation.Configuration;


@Configuration

public class SpringConfig{

}

@Bean

public AlarmDevice alarmDevice(){// alarmDevice()를 빈의 식별값으로 사용

return new SmsAlarmDevice();

}

}


@Bean 어노테이션은 새로운 빈 객체를 제공할 때 사용되며, @Bean이 적용된 메서드의 이름을 빈의 식별값으로 사용한다. 따라서 위 코드는 다음의 스프링 XML 설정과 동일한 빈을 정의한다.

<bean id="alarmDevice" class="mad.spring.ch4.homecontrol.SmsAlarmDevice"/>


메서드의 이름이 아닌 다른 이름을 빈 객체의 이름으로 사용하고 싶다면 @Bean 어노테이션의 name 속성을 사용하면 된다.

@Bean(name="smsAlarmDevice")//alarmDevice이 아닌 smsAlarmDevice을 사용하고 싶은 경우

public AlarmDevice alarmDevice(){

return new SmsAlarmDevice();

}


(1) @Bean 객체 간의 의존 설정

의존 설정은 매우 간단하다. 다음과 같이 의존할 빈 객체에 대한 메서드를 호출하는 것으로 의존관계를 설정할 수 있다.


@Configuration

public class SpringConfig{


@Bean

public Camera camera1(){

return new camera();

}


@Bean

public infraredRaySensor windowSensor(){

return new infraredRaySensor("창 센서");

}


@Bean

public Viewer viewer(){

MonitorViewer viewer = new MonitorViewer();

viewer.setDisplayStrategy(displayStrategy());

return viewer;

}

@Bean

public DisplayStrategy displayStrategy(){

return new DefaultDisplayStrategy();

}


@Bean

public HomeController homeController(){

HomeContoller homeController = new HomeController();

List<infraredRaySensor> sensors = new ArrayList<infraredRaySensor>();

sensors.add(windowSensor());

sensors.add(doorSensor());

homeController.setSensors(sensors);

...

homeController.setCamera1(camera1());

homeController.setDisplayStrategy(displayStrategy());

return homeController;

}

}

위 코드에서 각 빈 객체들은 의존할 빈에 해당하는 메서드를 호출함으로써 의존 관계를 설정하고 있다.

여기서 눈여겨 볼 부분은 displayStrategy() 메서드가 viewer() 메서드와 homeController() 메서드에서 각각 한 번씩 호출된다는 점이다.

즉, displayStrategy() 메서드가 두 번 호출된다. displayStrategy() 메서드는 호출 횟수에 상관없이 매번 동일한 객체를 리턴한다.


스프링은 CGLIB를 이용해서 @Configuration이 적용된 클래스의 프록시 객체를 생성한다. 이 프록시 객체는 @Bean 어노테이션이 적용된 메서드가 호출될 때 생성할 빈 객체의 범위에 

따라서 알맞은 객체를 제공한다. 예를 들어, 설정한 빈의 범위가 singleton이면 메서드가 리턴하는 객체는 매번 동일한 객체가 되고, 빈의 범위가 prototype이면 메서드가 메번 새로운 빈 객체를 

리턴하게 된다.


(2) @Bean 어노테이션의 autowire 속성을 이용한 연관 자동 설정

<bean id="alarmDevice" class="mad.spring.ch4.homecontrol.SmsAlarmDevice" autowire="byName"/>


@Bean(autowire=autowire.BY_NAME)

Autowire.BY_NAME: 이름을 이용해서 자동 연관을 처리한다.

Autowire.BY_TYPE: 타입을 이용해서 자동 연관을 처리한다.

Autowire.NO: 자동 연관 처리를 하지 않는다.


2. @Configuration 어노테이션 기반 설정 정보 사용

클래스에 @Configuration 어노테이션을 적용한다고 해서 스프링 컨테이너가 해당 클래스로부터 빈 정보를 구할 수 있는 것은 아니다.

@Configuration 어노테이션이 적용된 클래스를 이용해서 스프링 빈을 생성하는 방법 2가지를 살펴보자


(1) AnnotationConfigApplicationContext를 이용한 @Configuration 클래스 사용

@Configuration 어노테이션이 적용된 클래스를 설정 정보로 이용하는 첫 번째 방법은 AnnotationConfigApplicationContext를 이용하는 것이다.


다음과 같이 @Configuration 어노테이션 클래스를 전달해 주기만 하면 된다.

import org.springframework.context.ApplicationContext;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.springframework.context.support.AbstractApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;


public class MainConfig {


public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);


HomeController homeControl = context.getBean(:homeController", HomeController.class);

....

}

}


cf.) 한 개 이상의 @Configuration 어노테이션 적용 클래스로부터 ApplicationContext를 생성하고 싶다면 다음과 같이 @Configuration 어노테이션 클래스의 목록을 지정하면 된다.

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class, ArticleRepositoryConfig.class);


cf.) AnnotationConfigApplicationContext클래스는 <context:annotation-config> 태그를 사용했을 때와 마찬가지로 각 빈 클래스에서 사용된 @Autowired 어노테이션과 @Resource 어노테이션 등 

앞서 '어노테이션 기반 설정' 절에서 살펴봤던 어노테이션들이 기본을 적용된다.


(2) XML 설정 파일에서 @Configuration 어노테이션 클래스 사용하기

XML 설정 파일에서 @Configuration 클래스를 사용하려면 ConfigurationClassPostProcessor 클래스와 @Configuration 어노테이션이 적용된 클래스를 스프링 설정 파일에 빈 객체로 등록해 주면 된다.

<bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor"/>

<bean class="mad.spring.ch4.config.SpringConfig"/>

ConfigurationClassPostProcessor 클래스는 @Configuration 어노테이션이 적용된 빈 객체에서 @Bean 어노테이션이 적용된 메서드로부터 빈 객체를 가져와 스프링 컨테이너에 등록한다.


@Configuration 어노테이션이 적용된 클래스는 @Component 어노테이션이 적용된 클래스와 마찬가지로 컴포넌트 스캔 대상이다. 따라서, 아래와 같이 컴포넌트 스캔 기능을 이용해서 

@Configuration 어노테이션이 적용된 클래스를 자동으로 빈으로 등록할 수 있다.

<?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-3.0.xsd">

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<context:component-scan base-package="mad.spring.ch4.config"/>

...

</beans>



(3) @Importresource를 통해 @Configuration 설정 클래스에서 XML 사용하기

@Configuration 클래스에서 XML 설정 정보를 사용할 수도 있다.

@Configuration 클래스에서 XML 설정 정보를 함께 사용하고 싶다면 @ImportResource 어노테이션을 사용하면 된다.

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.ImportResource;


@Configuration

@ImportResource({"classpath:/article-repository.xml"})

public class ArticleConfigWithImportResource {


@Autowired

private ArticleRepository articleRepository;

@Bean

public ArticleService articleService() {

return new ArticleServiceImpl(articleRepository);

}

}


3. 서로 다른 @Configuration 어노테이션 클래스 간의 의존 설정
@Configuration 어노테이션 클래스를 작성하다보면, 다른 @Configuration 어노테이션 클래스에서 설정한 빈 객체를 의존하는 경우가 있다. 이런 경우는 주로 레이어 별로 설정 정보를 구분할 때 발생한다.예를 들어, 아래 코드와 같이 서비스 레이어와 리포지토리 레이어에 대한 설정을 별도 클래스로 제공한다고 해보자.
위 코드에서 ArticleServiceConfig에 설정된 articleService 빈은 ArticleRepositoryConfig에 설정된 articleRepository 빈에 의존하고 있다.
이렇게 서로 다른 설정 클래스에 존재하는 빈 객체 간의 의존을 처리할 때에는 @Autowired 어노테이션이나 @Resource 어노테이션을 이용해서 의존에 필요한 빈 객체를 전달 받을 수 있다.

위 코드에서 ArticleServiceConfig 클래스의 articleRepository 필드에 @Autowired 어노테이션이 적용되었기 때문에 articleRepository 필드에는 ArticleServiceConfig 클래스에 정의된
articleRepository() 메서드가 생성한 빈 객체가 할당된다. 따라서, ArticleServiceConfig.srticleService() 메서드에서 ArticleServiceImpl 객체를 생성할 때 사용되는 articleRepository 객체는
ArticleRepositoryConfig 클래스에서 생성한 ArticleRepositoryImpl 객체가 된다.

@Configuration
public class ArticleServiceConfig{
  @Autowired
private ArticleRepositoryConfig repositoryConfig;

@Bean
public ArticleService articleService(){
return new ArticleServiceImpl(repositoryConfig.articleRepository());
}
}
의존 객체를 참조하는 또다른 방법은 @Configuration 클래스를 @Autowired로 전달받는 것이다. 위 코드는 @Configuration 클래스를 전달받아 의존을 처리하는 경우의 예를 보여주고 있다.

cp.) @Import를 이용한 @Configuration 어노테이션 적용 클래스의 조합

@Import 어노테이션을 이용하면 하나의 @Configuration 클래스에서 다수의 @Configuration 클래스를 묶을 수 있다. 아래와 같이 @import 어노테이션에 설정 정보를 가져올 @Configuration 클래스 목록을 지정해주면 된다.

import org.springframework.context.annotaion.Cofiguration;

import org.springframework.context.annotation.Import;


@Configuration

@Import({ArticleServiceConfig.class, ArticleRepositoryConfig.class})

public class ArticleConfigWithImport{

...

}

@Import 어노테이션을 이용할 경우의 장점은 개발자가 모든 @Configuration 클래스 목록을 기억할 필요 없이 @import 어노테이션이 적용된 클래스만 기억하면 손쉽게 설정 정보 추적이 가능하다는 점이다.