ApplicationContext도 인터페이스이기 때문에 어떤 ApplicationContext 구현체에게 getResource()를 호출하느냐에 따라 리턴되는 Resource의 구현체가 달라진다. 예를 들어, ClassPathXmlApplicationContext에서 getResource()를 호출한다면 ClassPathResource가 리턴된다. 이러한 식으로 ApplicationContext와 리턴되는 Resource의 타입은 맵핑된다.
ResourceLoaderAware는 콜백 인터페이스로 컨텍스트에 생성된 ResourceLoader 빈을 가져올 수 있다.
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
물론 ApplicationContext가 ResourceLoader를 구현하고 있기 때문에 ApplicationContextAware로 컨텍스트를 이용하여 리소스에 접근할 수 있지만, 추천되지는 않는다. 해당 빈을 가져오고자 하는 이유가 리소스의 접근이라면 그 용도로 설계된 ResourceLoader 타입의 빈을 가져오는 것이 맞다.
만일 특정 빈이 고정된(static) 리소스를 필요로 한다면 프로퍼티로써 ResourceLoader를 사용하지 않고 Resource 빈을 주입할 수 있다. 예시처럼 value속성을 문자열로 주지만 Resource로 빈 주입이 가능한 것은 ApplicationContext가 등록하고 사용하는 특별한 JavaBean인 PropertyEditor가 있기 때문이다.
XML을 베이스로 한 ApplicationContext를 생성시 Resource를 활용하는 방법에 대해 알아본다.
ApplicationContext 생성시 Resource 인자
ApplicationContext을 소스코드로 생성자를 이용하여 생성할 때 인자로 문자열(String)을 지정한다. 문자열이 해석되어 특정 타입의 Resource객체로 XML파일이 로드된다. 문자열에 prefix를 지정하지 않으면 ApplicationContext의 구현체 타입에 따라 Resource의 타입이 결정되어 로드한다.
// ClassPathResource로 빈 설정파일이 로드된다.
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
// FileSystemResource로 빈 설정파일이 로드된다.
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");
// classpath에서 해당 이름의 모든 xml이 로드된다.
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
Ant-Style과 classpath*: prefix를 혼용하여 사용할 수도 있다.
FileSystemResource를 사용할 때 주의점 FileSystemApplicationContext는 모든 FileSystemResource 인스턴스들에 대해서 슬래쉬(/)의 유무에 상관없이 상대경로를 사용하도록 강제한다. 즉 아래 예시는 동일하게 상대경로를 지정한다.
// 1번
ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/context.xml");
// 2번
ApplicationContext ctx = new FileSystemXmlApplicationContext("/conf/context.xml");
그러므로 만일 절대경로를 사용하고 싶으면 file:/// prefix를 활용하여 UrlResource를 사용하도록 한다.
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
빈 설정 메타데이터를 XML이나 애노테이션을 이용하여 작성할 수 있지만, 어떤 것이 절대적으로 좋다고 말할 수는 없다.(it depends) 그리고 XML방식과 애노테이션 방식을 혼용하여 사용할 수도 있으나 한쪽 설정이 오버라이드 될 수 있는 점을 고려해야 한다.
애노테이션에 기반한 설정 방식은 앞서 봤던 BeanPostProcessor를 이용하여 ApplicationContext가 빈 조립을 가능하게 해 준다. 빈으로 등록되는 클래스에 애노테이션을 마킹하고 BeanPostProcessor에서 애노테이션을 검사하여 특정 로직을 실행하는 방식이다.
@Autowired 클래스의 생성자, 세터 메서드, 필드 등에 사용하여 컨테이너에서 의존성을 주입받는다. 타입 기준으로 빈이 주입되며, 해당 타입의 빈이 여러 개인 경우 빈 이름에 따라 주입된다. 같은 타입의 빈이 여러개 일 때 특정 빈을 주입하고자 한다면 아래 3가지를 고려한다.
@Primary 빈 주입을 위한 후보자 빈이 여러 개일 때 특정 빈을 @Primary로 마킹하면 해당 빈이 주입된다.
@Qualifier @Primary 와 같이 빈 주입 시 후보군 중에서 특정 빈을 선택하고자 할 때 사용한다. @Qualifier("name") 형태로 특정 빈에 마킹을 하고, 주입받을 빈에서도 @Autowired와 함께 @Qualifier("name") 를 마킹한다. 기존의 애노테이션을 확장하여 커스터마이징한 @Qualifier를 만들 수 있으며, 내용은 링크를 참고한다.
@Resource JSR-250에서 정의된 @Resource 애노테이션을 의존성 주입에 사용할 수 있다. @Autowired와 다른 점은 name 속성으로 빈을 주입한다는 점이며 일치하는 빈 이름이 없는 경우에는 타입을 이용하여 빈을 주입한다. name속성을 제거하여 사용하는 경우 필드명이나 메서드의 파라미터명과 일치하는 빈 이름을 찾는다. @Resource(name="myMovieFinder")
@PostConstruct, @PreDestrory 메서드에 마킹하면 빈 라이프사이클에 따라 호출된다.
XML을 이용한 빈 설정 메타데이터를 사용하지 않고 classpath를 스캐닝하여 빈을 등록하는 방법에 대해 알아본다. @Component 와 같은 애노테이션이나 AspectJ 타입 표현 등을 사용할 수 있다.
@Component 및 이를 확장한 스테레오타입 애노테이션 스프링에서는 스캐닝을 통한 빈 등록을 위해 @Component를 포함한 여러 애노테이션을 제공한다. 클래스에 애노테이션이 마킹되어 있으면 빈으로 등록하는 식이다. 빈의 기능이 명확하게 정의되어 있는 경우라면 @Component를 확장한 스테레오타입 애노테이션을 사용하는 것이 추천된다. 빈의 기능(또는 계층)이 명확하게 정의되었다는 것은 일반적으로 말하는 DAO(Data Access Object) 객체와 같은 것을 의미한다. DAO의 기능을 하는 객체는 @Repository라는 @Component를 확장한 애노테이션으로 마킹하여 빈으로 등록한다. 이 외에도 @Controller, @Service 등의 스테레오타입 애노테이션 마킹을 활용하면 로깅, 예외처리, AOP 적용에 용이하다.
Meta-Annotation, Composed-Annotation 다른 애노테이션에 마킹되어 적용되는 애노테이션을 Meta-Annoation이라 하며, 메타 애노테이션의 조합으로 만들어진 애노테이션을 Composed-Annotation이라 한다. Meta-Annotation 예시 : @Service 애노테이션에 마킹된 @Component Composed-Annotation 예시 : @ResponseBody와 @Controller의 조합으로 이루어진 @RestController 더 자세한 설명은 링크를 참조한다.
Bean Scanning 클래스에 애노테이션을 마킹했다면 빈으로 ApplicationContext에 등록하기 위해서 특정 패키지를 지정하여 빈 스캐닝을 진행한다. 빈 스캐닝 과정에서 클래스로 작성한 빈 설정 메타데이터나 @Service, @Controller 등으로 마킹된 클래스들이 빈으로 등록된다. 자바 빈 설정 파일에서는 @ComponentScan(basePackages="org.expample") 으로 지정하고, XML설정에서는 <context:component-scan base-package="org.example"/> 으로 지정한다. <context:component-scan />이 위에 언급한 <context:annotation-config /> 를 포함하므로 중복으로 설정할 필요가 없다.
ComponentScan - Filter 컴포넌트 스캔 시 base-package를 기준으로 @Component와 이를 확장한 애노테이션들이 모두 빈으로 등록된다. 이러한 디폴트 스캔 방식을 커스터마이징 하고 싶다면 Filter를 이용한다. Filter는 특정 클래스나 애노테이션 등을 제외하거나 포함시킬 수 있다.
자바 코드를 통한 컨테이너 구성에 대해 알아본다. 자바 코드로 빈 설정 메타데이터를 만든다는 의미이다.
@Bean / @Configuration 기본
스프링의 자바 코드 설정에서 가장 중심이 되는 애노테이션이다. 보통 @Configuration이 마킹된 클래스 파일이 빈 설정 파일이 되고, @Configuration 클래스 안에 @Bean이 마킹된 메서드의 반환 객체를 컨테이너에서 관리하는 빈으로 등록한다.
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl(); // 반환되는 MyService 객체가 빈으로 등록된다.
}
}
AnnotationConfigApplicationContext
자바 코드를 이용한 빈 설정에서 사용되는 ApplicationContext를 확장한 클래스이다. 코드로 생성 시 빈설정 클래스를 생성자에 인자로 하여 생성할 수도 있고, 아래 예시처럼 생성할 수도 있다.
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh(); // 설정 클래스의 빈들이 초기화되고 등록된다.
// ApplicationContext 에서 등록된 빈을 가져올 수 있다.
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
일반적으로 웹 어플리케이션에서는 AnnotationConfigWebApplicationContext이 사용되며, 이것도 ApplicationContext로부터 확장된 클래스이다.
@Bean 사용
자바 설정 파일의 메서드에 @Bean을 마킹하여 리턴값을 빈으로 등록한다.
빈으로 등록되는 객체에 대해 라이프사이클 콜백 메서드를 지정할 수 있다. JSR-250에 정의된 @PostConstruct, @PreDestory를 메서드에 마킹하거나, 스프링에서 제공하는 InitializingBean, DisposableBean, Lifecyle 등의 인터페이스의 메서드를 구현하거나, *Aware 인터페이스의 메서드를 구현하면 BeanPostProcessor에서 해당 메서드를 실행해준다.
*Aware 인터페이스 예시 : BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware
@Bean 애노테이션을 마킹할 때 인자로 추가적인 설정을 할 수 있다. @Bean(initMethod = "init"), @Bean(destroyMethod = "cleanup") : 라이프사이클 메서드 지정 @Scope("prototype") : 빈 스코프 지정 @Bean(name = "myThing") : 빈 이름 지정 @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"}) : 이름은 복수개를 동시에 줄 수 있다. @Description("Provides a basic example of a bean") : 빈 설명 지정
@Configuration 사용
자바 클래스파일에 빈설정 파일임을 나타내는 @Configuration 마킹을 할 수 있으며, 아래 애노테이션과 사용된다. @Import : 다른 빈 설정 클래스 파일을 포함한다.
@ImportResource : 다른 XML 빈 설정 파일을 포함한다.
XML 중심의 빈 설정에서는 <bean /> 태그를 이용하여 클래스 설정 파일을 서브로 포함시킬 수 있고, 클래스 중심의 빈 설정에서도 @ImportResource를 이용하여 XML 설정 파일을 서브로 포함시킬 수 있다.
Environment는 스프링에서 제공하는 인터페이스로 어플리케이션에서 profiles와 properties에 대한 관리를 위해 사용한다.
@Profile
Profile 은 환경에 따른 빈들의 그룹이다. 어플리케이션의 사용자(개발자 또는 QA)나 운영환경(개발 or 운영 or 테스트)에 따라 사용하고자 하는 빈이 다를 때 빈을 그룹핑하여 환경에 맞게 컨테이너에 등록하게 해 준다.
@Configuration 클래스나 @Bean 메서드에 @Profile("name")을 마킹하여 Profile을 설정한다. 어플리케이션 구동 시 아무런 설정을 해주지 않으면 디폴트로 @Profile이 설정되지 않은 빈들이 등록되고, @Profile("name")이 설정된 빈은 등록되지 않는다. @Profile("name")이 설정된 빈들만 등록하고 싶다면 아래와 같이 Profile을 지정해준다.
Environment에 Profile을 설정
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("development"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh();
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
2. JVM 옵션에 Profile 을 설정 : -Dspring.profiles.active="profile1,profile2"
Profile은 표현식으로 지정할 수도 있다.
XML 설정에서도 <bean /> 태그 안에서 프로퍼티로 Profile 설정이 가능하다.
PropertySource
Environment는 등록된 Property에 대한 접근을 가능하게 해 준다. Property가 등록된 저장소를 PropertySource라 한다.
스프링의 StandardEnvironment에서는 두 개의 PropertySource객체가 있다.
JVM system properties : System.getProperties()
system environment variables : System.getenv()
일반적인 웹 어플리케이션의 StandardServletEnvironment에는 5개의 PropertySource 가 있고 이들 사이에는 다음과 같은 우선순위를 가진다. 같은 프로퍼티명을 가지는 프로퍼티를 여러곳에 선언했다면 아래 우선순위에 따라 오버라이드 된다.
ServletConfig parameters (if applicable — for example, in case of a DispatcherServlet context)
JVM system environment (operating system environment variables)
기본으로 추가되는 PropertySource 외에도 개발자가 생성한 PropertySource를 추가할 수 있다. 아래 예시는 classpath에 app.properties 파일을 생성하고 testbean.name 프로퍼티의 값을 소스에서 주입받는 예시이다. @PropertySource 애노테이션으로 PropertySource를 생성한다.
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Value("${testbean.name}") // @Value 애노테이션으로 프로퍼티 주입
String testName;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
// Environment 객체를 이용하여 프로퍼티 조회
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
${...} placeholder 표현식 안에서 ${...} 표현식이 사용될 수 있다. @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
스프링은 클래스가 JVM에 로드될 때 동적으로 변경하기 위해 LoadTimeWeaver를 사용한다. @Configuration 클래스에 @EnableLoadTimeWeaving 마킹을 추가하여 설정하면, ApplicationContext의 빈들이 LoadTimeWeaverAware을 구현하여 load-time weaver instance에 대한 참조가 가능하다.
org.springframework.beans.factory 패키지는 프로그램적으로(programmatic) 빈을 관리하고 조작하기 위한 기본 기능들을 제공한다. ApplicationContext를 포함한 org.springframework.context 패키지의 클래스는 factory 패키지를 확장하여 좀 더 프레임워크적(framework-oriented style)으로 기능들을 제공한다. 일반적인 어플리케이션에서 ApplicationContext를 소스코드로 생성하지 않고 지원되는 ContextLoader를 이용하여 자동으로 객체화하는 것이 그 예이다.
프로그램적(programmatic) : 라이브러리는 제공해주지만 개발자가 소스코드로 생성하고 조기화하여 사용해야 한다.
프래임워크적(framework-oriented style) : 설정을 통해 자동으로 생성되고 초기화되고 실행된다.
BeanFactory에서 기능적으로 확장된 ApplicationContext의 추가 기능에 대해 알아본다.
MessageSource : 국제화(i18n) 기능을 제공
ApplicationContext는 MessageSource 인터페이스를 구현하여 i18n 이라 부르는 국제화를 지원한다. MessageSource 인터페이스를 확장한 HierarchicalMessageSource 인터페이스를 제공하여 메세지가 계층형으로 해석될 수 있도록 한다. (메세지 키(코드)가 중복되는 경우 오버라이드 된다.)
ApplicationContext에 messageSource 이름을 갖는 빈이 선언되어 있어야 한다. ApplicationContext는 해당 이름의 빈이 없으면 자신의 상위(parent)에서 빈을 찾는다. 만일 연결된 상위 context에서 빈을 찾을 수 없으면 비어있는 DelegatingMessageSource가 요청을 받기위해 초기화된다.
스프링에서는 MessageSource에 대한 구현체로 ResourceBundleMessageSource, StaticMessageSource 두 가지를 제공하지만 주로 ResourceBundleMessageSource이 사용된다. (스프링부트에서도 resources 이하 디렉터리에 message.properties 파일로 ResourceBundleMessageSource이 생성된다.)
프로퍼티 파일과 ApplicationContext에서 MessageSource를 사용하는 예시
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", null);
System.out.println(message);
}
메세지를 로딩하는 과정에서 국제화(i18n)을 적용하고자 한다면 properties 파일의 파일명을 규칙에 맞게 작성한다.
만일 영국(en-GB) 지역의 국제화를 제공하고 싶다면 파일명을 XXX_en_GB.properties로 메세지 프로퍼티 파일을 생성하고, getMessage() 호출시 Locale 인자를 Locale.UK 로 한다.
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
ResourceBundleMessageSource 가 아닌 리로딩 기능이 있는 MessageSource를 사용할 수도 있다.
@Bean
public MessageSource messageSource() {
var messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(3);
return messageSource;
}
Standard Event / Custom Event
ApplicationContext는 이벤트를 핸들링 하는 기능을 다음 두 인터페이스를 통해 제공하고 있다.
ApplicationEvent, ApplicationListener
ApplicationContext는 뒤에 나올 ApplicationEventPublisher를 구현하고 있다. Observer design pattern을 사용하여 ApplicationEvent가 ApplicationContext로 발행(publish)되면 ApplicationListener빈으로 이벤트가 전달된다.
스프링에서는 다음의 Built-in Event를 제공하고 있다. 자세한 설명은 문서를 참고하자.
ApplicationEventPublisherAware 를 구현하여 publisher에 작성한 이벤트를 발행 ApplicationEventPublisher의 publishEvent() 메서드를 호출한다.
ApplicationListener에서 발행된 이벤트를 수신하여 처리 리스너는 ApplicationListener를 구현하거나 메서드에 @EventListener를 마킹한다. @EventListener는 스프링 4.2부터 지원하는 기능이다.
이벤트를 발행하고 리스너에서 처리하는 과정은 동기화(synchronously) 된다. 즉, 하나의 트랜잭션으로 묶어 처리가 가능하다. 이벤트 처리의 순서를 지정하고 싶다면 @Order 애노테이션, 비동기로 처리되기를 원한다면 @Async 애노테이션과 함께 사용한다.
Low-level Resources에 대한 편리한 접근
ApplicationContext은 ResourceLoader로써 Resources에 대한 편리한 접근이 가능하다. 구체적인 것은 2장 Resources에서 알아본다.
웹어플리케이션에서의 편리한 ApplicationContext 초기화
ApplicationContext를 소스코드로 생성/초기화 하여 사용해도 되지만 웹어플리케이션에서는 ContextLoaderListener를 사용해 편리하게 생성하고 초기화 할 수 있다.
web.xml 에 다음과 같이 작성하면 ContextLoaderListener가 어플리케이션이 구동될 때 contextConfigLocation 인자를 검사하여 ApplicationContext를 초기화한다. 만일 해당 인자가 없다면 디폴트 파일(/WEB-INF/applicationContext.xml)이 빈 설정을 위한 메타데이터로 이용된다.
BeanFactory(DefaultListableBeanFactory)는 기본적인 Spring의 IoC 기능을 API 제공하고 있다. 그럼에도 ApplicationContext 가 주로 사용되는 이유는 BeanFactory의 기능을 포함한 강력한 추가기능에 있다. 예를 들면, BeanPostProcessor를 이용한 기능확장 등이다.
ApplicationContext에서는 미리 정해진 기본 규약에 의해 여러 종류의 빈들이 등록(detected)된다. (빈 이름 또는 타입에 의해 - 특히 post-processor)
반면에 DefaultListableBeanFactory는 이런 특별한 빈들을 알지 못하므로 등록을 위해서는 다음과 같이 소스코드로 등록을 해주어야 한다.
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
스프링에서 IoC컨테이너는 BeanFactory 인터페이스를 말하며, 실제 어플리케이션에서는 이를 확장한 ApplicationContext를 의미하는 것이 일반적이다.
비즈니스가 포함된 자바 소스(POJOs)와 빈 설정관련 정보가 있으면 스프링컨테이너에 이를 읽어들여 실행가능한 어플리케이션을 구성해준다.
ApplicationContext는 인터페이스이므로 ApplicationContext를 생성(초기화)하고자 한다면 설정파일(빈 설정에 사용되는 메타데이터)의 타입에 따라 클래스를 선택한다. 스프링에는 이미 구현된 다수의 클래스들을 제공하고 있다.
제공되는 구현 클래스 예시 : ClassPathXmlApplicationContext(XML), AnnotationConfigApplicationContext(JAVA)
ApplicationContext가 제공하는 T getBean(String name, Class<T> requiredType) 메서드를 통해 IoC컨테이너에서 관리되는 빈을 가져올 수 있지만 어플리케이션 소스에서 직접 빈을 가져오는 Spring API를 사용하는 것은 안된다.
@Autowired 애노테이션처럼 스프링에서 DI를 위해 제공하는 방법에 따라 빈을 DI받아야 한다.
어플리케이션은 한 개의 객체로만 구성되어 있지 않고 여러 객체가 서로 상호작용하며 동작한다. 한 객체가 다른 객체를 사용할 때 의존성(Dependency)을 가진다고 한다.
스프링에서는 객체 간의 의존성을 스프링 컨테이너가 관리하며, 객체가 생성되고 초기화 될 때 객체간의 의존성이 구성된다. 즉, 소스코드 자체에서 의존성을 관리하는 것이 아니라 컨테이너가 메타데이터를 이용해 객체간의 의존성을 구성해주므로 의존성 주입(Dependency Injection)이라 한다.
컨테이너에 의한 의존성 주입은 객체의 생성자나 세터(setter) 메서드를 이용하여 실행된다.
Lifecycle Callbacks 라이프사이클에 따라서 호출될 콜백메서드를 지정한다. InitializingBean 인터페이스의 afterPropertiesSet()과 DisposableBean 인터페이스의 destroy() 를 구현함으로써 빈 생성 이후 동작을 지정할 수 있지만 스프링의 라이브러리를 코드에서 직접적으로 사용한다는 점에서 추천되지 않는다. 대신 JSR-250의 @PostConstruct 와 @PreDestroy 를 활용하면, 스프링 소스에 의존하지 않고 커스터마이징을 할 수 있다.(XML에서는 init-method 와 destroy-method 속성을 활용한다.)
@Component
public class TestPerson implements InitializingBean, DisposableBean {
private String name;
private int age;
@PostConstruct
public void init(){
// 빈 생성 후의 동작을 지정한다.
}
@PreDestroy
public void end(){
// 빈 종료 후의 동작을 지정한다.
}
@Override
public void afterPropertiesSet() throws Exception {
// 빈 생성 후의 동작을 지정한다.
}
@Override
public void destroy() throws Exception {
// 빈 종료 후의 동작을 지정한다.
}
}
ApplicationContextAware / BeanNameAware 위 인터페이스의 메서드를 구현하여 ApplicationContext와 BeanName을 확인할 수 있다. ApplicationContext를 이용하여 소스코드에서 빈을 커스터마이징할 수 있다. 하지만 이 방법도 스프링의 라이브러리를 코드에서 직접 사용하는 것이므로 추천되지 않는다.
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
그 외 Aware 인터페이스 ApplicationContextAware 와 BeanNameAware 을 포함하여 스프링에서 제공하는 Aware 인터페이스들이 있다. 자세한 내용은 링크 참조. 하지만 이런 Aware들을 사용하는 것도 스프링 라이브러리에 의존하며, IoC 스타일에 맞지 않으므로 지양하자.
개발자가 ApplicationContext 의 구현체를 직접 사용하지 않고 제공되는 통합 인터페이스(integration interface)를 이용하여 컨테이너를 확장 할 수 있다.
BeanPostProcessor 인터페이스로 빈을 커스터마이징 BeanPostProcessor는 ApplicationContext가 빈을 초기화하는 과정에 동작한다. 그러므로 해당 인터페이스를 구현한 BeanPostProcessor 객체(빈)를 컨테이너에 등록하면 빈 초기화 과정에서 추가적인 동작(초기화 로직, 의존성 변경 등)을 지정할 수 있다. 다수의 BeanPostProcessor 가 동작하는 경우 Ordered 인터페이스를 구현하여 동작 순서를 지정한다. 빈 개별마다 초기화 동작을 지정하고 싶다면 @PostConstruct 를 사용하고 공통적으로 지정할 로직은 BeanPostProcessor 의 사용을 고려한다.
BeanFactoryPostProcessor 인터페이스로 메타데이터를 커스터마이징 BeanPostProcessor 와 갖는 의미(Semantic)은 유사하지만 큰 차이점은 빈 설정 메타데이터에 대한 조작이 가능하다는 점이다. 스프링 컨테이너는 빈 초기화 전에 등록된 BeanFactoryPostProcessor 구현체에게 메타데이터에 대한 변경을 가능하게 해준다. 여러개의 BeanFactoryPostProcessor 가 존재할 경우 Ordered 인터페이스를 구현하여 동작 순서를 지정한다. 예시로는 PropertyPlaceholderConfigurer 가 있는데, 빈 설정 메타데이터에서 ${property.name} 와 같이 placeholder 안에 있는 값을 지정된 외부위치로부터 가져와 치환해준다.
PropertyPlaceholderConfigurer(BeanFactoryPostProcessor의 구현체)를 빈으로 등록하면, 빈 초기화시 동작하여 placeholder로 표현한 프로퍼티 값이 주입된다.
FactoryBean 인터페이스로 빈 초기화를 커스터마이징 빈 설정 메타데이터를 XML방식으로 사용하면서, 복잡한 빈 초기화 로직을 작성하고자 한다면 자바소스로 작성하는 것이 더 낫다. 이럴 때 FactoryBean 을 작성하여 <bean />의 class 프로퍼티에 지정해주면 FactoryBean.getObject() 가 리턴하는 객체가 빈으로 등록된다. ApplicationContext 객체로부터 FactoryBean 의 실제 구현체를 가져오고자 하는경우 getBean("&name") 으로 '&' 을 prefix로 하여 호출하면 된다.(getBean("name")은 FactoryBean의 구현체가 리턴된다.)