<div class="information"> 원문은 .. http://www.infoq.com/articles/spring-2.5-part-1 </div>

Spring 2.5에서 추가된 기능에 대한 간단한 정리#

  • 애노테이션을 이용한 의존성 삽입
  • XML보다는 애노테이션을 사용하여 클래스패스에 있는 Spring컴포넌트를 자동 감지하기
  • 생명주기를 다루는 메소드를 위한 애노테이션 지원
  • 요청을 애노테이션 처리된 메소드에 매핑하기 위한 새로운 웹 컨트롤러 모델
  • Junit4 지원
  • Spring XML 명명공간의 추가

JSR-250 애노테이션을 위한 Spring지원#

다음의 3가지 JSR-250 애노테이션을 지원한다.
  • @Resource
  • @PostConstruct
  • @PreDestroy

이 기능을 지원하기 위해서는 다음의 Spring 처리자를 등록해줘야 한다.

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

@Resource#

@Resource 애노테이션은 Java EE 애플리케이션에서 JNDI 컨텍스트에 바운드된 객체로 해석되는 "명명된 자원(named resource)" 을 의존성 삽입하도록 해준다. 아래와 같이 setter메소드를 사용하는 경우 DataSource는 name속성의 값인 dataSource라는 이름의 bean이 삽입된다.

@Resource(name="dataSource")
public void setDataSource(DataSource dataSource) {
  this.dataSource = dataSource;
}

위 경우처럼 setter메소드를 사용하는 것과 달리 직접 삽입하는 것도 가능하다. 아래는 변수명이 dataSource이기 때문에 자동으로 해당 변수명의 bean이 삽입된다.

@Resource
private DataSource dataSource; 

이전에도 계속 사용되던 방식이지만 다음과 같은 setter방식도 가능하다.

private DataSource dataSource;
@Resource
public void setDataSource(DataSource dataSource) {
  this.dataSource = dataSource;

name속성을 사용하지 않고 @Resource애노테이션을 사용할때 Spring에서 해당 bean을 찾지 못한다면 예외를 던지게 된다. 일치하는 bean이 정확히 하나만 있을 경우 정상적으로 삽입이 된다. 이렇게 하고 싶지 않을 경우(실제로 사용해봐야 알겠지만 이렇게 하고 싶지 않을 경우라는 게 애매하다. bean을 찾지 못하더라도 예외를 던지지 않게 하는건가..??) 다음처럼 설정해주면 된다. fallbackToDefaultTypeMatch의 디폴트값은 true이다.

<bean id="" class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
    <property name="fallbackToDefaultTypeMatch" value="false"/>
</bean>

@Resource애노테이션은 JNDI룩업외 다양한 자원에 대해 의존성을 삽입할수 있다. 하지만 아무래도 대부분의 사용이 JNDI룩업에 사용될것으로 보이며 JNDI룩업만을 하도록 다음처럼 제한을 걸수도 있다.

<bean id="" class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
    <property name="alwaysUseJndiLookup" value="true"/>
</bean>

context 명명공간#

기존에 프로퍼티파일을 지정하는 경우 다음처럼 사용했다.

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location" value="classpath:jdbc.properties"/>
</bean>

하지만 2.5에서는 다음처럼 가능하다.

<context:property-placeholder location="classpath:jdbc.properties"/>

생명주기를 다루는 애노테이션 : @PostConstruct 와 @PreDestroy#

일단 이 두 기능은 InitializingBean(의 afterPropertiesSet())과 DisposableBean(의 destroy())인터페이스의 콜백 메소드를 구현하는 이전 버전의 방법을 대체하는 방법이 아닌 확장하는 방법으로 이해하는 것이 좋다. 인터페이스를 구현하는 방식이라면 정해진 메소드를 구현해야 하겠지만 추가된 애노테이션을 사용하게 되면 자기만의 메소드를 가질수 있게 된다. Spring에서 제공하는 생명주기를 제어하는 옵션을 정리해보면 다음과 같다.
  1. XML설정에서 bean의 init-method, detroy-method 속성 설정
  2. InitializingBean, DisposableBean 인터페이스를 구현하는 방법
  3. 2.5에서 추가된 애노테이션을 사용하는 방법

일단 애노테이션을 사용하는 방법은 Spring유저라면 짐작할수 있겠지만 다음과 같다. 초기화할때 콜백으로 호출되어야 할 메소드에 다음처럼 애노테이션을 넣는다.

public class FilePoller 
   @PostConstruct
   public void startPolling() {
    ...
   }
   ...

역으로 객체가 생명을 다할때는 다음과 같다.

public class FilePoller {
 
   @PreDestroy
   public void stopPolling() {
    ...
   }
   ...

앞서 말했지만 추가된 애노테이션을 작동시키기 위해서는 역시 앞에서 언급한 CommonAnnotationBeanPostProcessor 을 bean으로 등록해줘야만 한다. 그리고 차후 나오겠지만 CommonAnnotationBeanPostProcessor를 등록해주면 @WebServiceRef, @EJB 애노테이션도 사용이 가능하게 된다.

<context:annotation-config/>

애노테이션을 사용한 잘 구성된 autowiring#

2.5 이전에는 객체가 가진 setter메소드의 하위세트(subset)나 타입에 의한 몇가지 프로퍼티와 이름에 의한 다른 것들을 autowire하는 것이 사실상 불가능했다. 그러한 원인으로 인해 대개의 Spring사용자는 autowire를 프로토타입을 만들거나 테스트을 위한 목적으로만 autowire을 활용하는 경향이 심해졌다. 결국에 운영환경에서는 autowire를 사용하기 보다는 명시적으로 선언하는 방식으로 구현을 해서 제공하는 기능에 비해 넓게 사용되지 못한것으로 보인다. 2.5에서는 이미 앞에서도 언급한 @Resource애노테이션을 사용하여 메소드별 또는 필드별로 명명된 자원의 autowire가 가능하게 되었다. 하지만 @Resource애노테이션 자체가 autowire하도록 사용하기 위해서는 제한점을 가지기 때문에 @Autowired 애노테이션을 추가적으로 제공한다. 이 애노테이션을 사용하기 위해서는 다음과 같은 bean을 등록해주어야 한다.

<bean id="" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

@Autowired 애노테이션을 사용하여 타입에 일치하는 의존성을 삽입하는 것이 가능하다. 이 작업은 필드, 생성자, 메소드에서 모두 가능하다. 사실 autowire는 settter메소드를 가질 필요가 없고 다음처럼 여러개의 인자를 가질수도 있다.

<context:annotation-config/> 

@Autowired
public void setup(DataSource dataSource, AnotherObject o) { ... 

다음의 예제는 Spring컨테이너가 SomeStrategy 타입의 bean을 찾지 못한다면 DefaultStrategy 객체로 대체해서 사용하는 것을 뜻한다. 즉 디폴트에 생성되는 타입을 지정해주되 컨테이너가 자동으로 삽입되는 의존성 객체가 있다면 자동으로 해당 타입의 객체로 생성시켜준다.

@Autowired(required=false)
private SomeStrategy strategy = new DefaultStrategy()

타입에 의한 autowire를 사용할때 해당 타입의 bean이 여러개가 있다면 Spring컨테이너는 에러를 띄우게 된다. 이럴때를 위해 bean에 primay라는 속성이 추가되었다. 해당 타입의 bean이 여러개라고 하더라도 가장 먼저 primary라고 잡힌 bean의 참조가 넘어가게 된다.

<bean id="dataSource" primary="true" ... /> 

primary를 사용하는 대신에 다음처럼 @Qualifier 애노테이션에 인자로 실제 참조하고자 하는 bean의 id를 넘길수도 있다. 일종에 제한자 역할을 한다고 보면 이해에 도움이 된다.

@Autowired
@Qualifier("primaryDataSource")
private DataSource dataSource; 

@Autowired
public void setup(@Qualifier("primaryDataSource"DataSource dataSource, AnotherObject o) { ... 

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface VetSpecialty ... 

@Autowired
@VetSpecialty("dentistry")
private Clinic dentistryClinic; 

<bean id="dentistryClinic" class="samples.DentistryClinic">
  <property type="example.VetSpecialty" value="dentistry"/>
</bean>

@Qualifier 애노테이션에 대해 의존성을 제거하기 위해서는 Spring 컨텍스트에 CustomAutowireConfigurer 에 대한 bean정의를 추가하고 사용자가 정의한 애노테이션 타입을 직접 기술해줘야한다.

<bean id="" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
  <property name="customQualifierTypes">
     <set>
      <value>example.VetSpecialty</value>
     </set>  
  </property>
</bean>

다음은 사용자가 정의한 제한자를 명시적으로 선언하는 것이다. 여기서는 @Qualifier 애노테이션이 더 이상 필요하지 않다.

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface VetSpecialty ... 

<bean id="" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor">
  <property name="autowiredAnnotationType" value="example.Injected"/>
</bean>

autowire처리를 제어하기 충분한 이름과 구문적인 값들에 일치하는 옵션으로 조합된 "표시자(marker)" 애노테이션을 정의하는 것이 가능하다.

@SpecializedClinic(species="dog", breed="poodle")
private Clinic poodleClinic; 

위에서 정의된 애노테이션은 아래와 같이 구현될수 있다.

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface SpecializedClinic {
  String species();
  String breed();

@Qualifier 애노테이션의 속성은 다음처럼 key/value의 쌍으로 선언할수도 있다.

<bean id="poodleClinic" class="example.PoodleClinic">
   <qualifier type="example.SpecializedClinic">
    <attribute key="species" value="dog"/>
    <attribute key="breed" value="poodle"/>
   </qualifier>
</bean> 

별 차이점은 없겠지만 List와 같은 타입의 autowire도 다음처럼 애노테이션을 사용하면 된다.

@Autowired
private List allClinics;

One final feature that is worth pointing out in this section is the use of autowiring in place of Spring's "Aware" interfaces. Prior to Spring 2.5, if an object requires a reference to the Spring context's ResourceLoader, it can implement ResourceLoaderAware thereby allowing Spring to provide this dependency via the setResourceLoader(ResourceLoader resourceLoader) method. This same technique applies for obtaining a reference to the Spring-managed MessageSource and even the ApplicationContext itself. For Spring 2.5 users, this behavior is now fully supported through autowiring (note that the inclusion of these Spring-specific dependencies should always be carefully considered and typically only used within "infrastructure" code that is clearly separated from business logic).

@Autowired
private MessageSource messageSource;

@Autowired
private ResourceLoader resourceLoader;

@Autowired
private ApplicationContext applicationContext;

Spring컴포넌트를 자동 감지하기#

2.0에서 Spring은 데이터 접근 코드를 위한 표시자처럼 제공하는 @Repository 애노테이션과 함께 "stereotype" 타입 개념의 애노테이션을 도입했다. 2.5에서는 3-티어(데이터 접근 객체, 서비스, 웹 컨트롤러) 개념을 완성하기 위해 추가적으로 @Service 와 @Controller 애노테이션을 만들었습니다. 그리고 다른 stereotype을 논리적인 확장하도록 @Component 애노테이션도 추가했다. 이러한 애노테이션은 2.5에서 추가된 자동감기 기능과 함께 사용될수 있다. XML이 Spring 메타데이터를 위한 가장 선호되는 형태이기는 하지만 사실 이 XML도 옵션에 지나지 않는다. Spring 컨테이너 내부의 메타데이터 표현은 자바로 되어있다. Spring 컨테이너가 관리하는 객체를 XML에 정의할때, 이러한 정의는 초기화전에 파싱되고 자바 객체로 변환된다. 2.5에서 추가된 기능은 소스 레벨의 애노테이션에서 메타데이터를 읽도록 한다. autowire 기법은 의존성을 삽입하기 위해 애노테이션 메타데이터를 사용하지만 그래도 최소한의 bean정의가 필요하다. 추가된 컴포넌트 감지 기능은 XML에 정의하는 최소한의 bean설정또한 제거할수 있다. 앞서 본것처럼 Spring의 애노테이션을 사용한 autowire는 XML설정을 눈에 띄게 줄여준다. 컴포넌트 감지 기능은 이것을 더 줄여준다. 2.5 버전의 PetClinic 샘플 애플리케이션에서 외부 프로퍼티와 함께 XML정의된 인프라 성격의 컴포넌트(예를 들면, 데이터소스, 트랜잭션 관리자 등)가 먼저 정의된다. 데이터 접근 티어의 객체는 부분적으로는 XML에 정의되지만, 설정은 의존성 삽입을 단순히 하기 위해 @Autowired 애노테이션의 장점을 사용한다. 마지막으로 웹 티어의 "컨트롤러"는 XML전부 정의되지 않느다. 대신 다음 설정으로 모든 웹 컨트롤러의 자동감지를 할 뿐이다.

<context:component-scan base-package="org.springframework.samples.petclinic.web"/> 

컴포넌트 감지 기능은 base-package 속성에 설정된 값의 패키지내에 포함된 클래스의 stereotype 의 애노테이션을 재귀적으로 감지한다. 여러개의 패키지를 정의하기 위해서는 , 로 구분하면 된다. 다양한 컨트롤러 구현체는 @Controller 애노테이션을 사용한다.

@Controller
 public class ClinicController {
 
   private final Clinic clinic;
 
   @Autowired
   public ClinicController(Clinic clinic) {
    this.clinic = clinic;
   }
 ... 

자동감지된 컴포넌트는 XML설정된것처럼 Spring 컨테이너에 등록된다. 즉 애노테이션을 사용한 autowiring에 사용할수 있게 된다. 컴포넌트 감지자는 이름 패턴을 위해 타입, 애노테이션, AspectJ 표현식 또는 정규표현식에 기초하여 컴포너트를 포함시키거나 제외시키는 필터를 정의할수 있다. 다음의 예제는 디폴트 stereotype 은 무시하고 대신 Stub로 시작하는 이름의 클래스나 @Mock 애노테이션을 가지는 클래스를 자동감지한다.

<context:component-scan base-package="example" use-default-filters="false">
   <context:include-filter type="aspectj" expression="example..Stub*"/>
   <context:include-filter type="annotation" expression="example.Mock"/>
</context:component-scan> 

<context:component-scan base-package="example">
   <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan> 

Clearly it is possible to extend the component scanning in a number of ways to register your own custom types. The stereotype annotations are the simplest option, so the notion of stereotype is therefore extensible itself. As mentioned earlier, @Component is the generic stereotype indicator that the @Repository, @Service, and @Controller annotations "logically" extend. It just so happens that @Component can be provided as a meta-annotation (i.e. an annotation declared on another annotation), and any custom annotation that has the @Component meta-annotation will be automatically detected by the default matching rules of the scanner. An example will hopefully reveal that this is much simpler than it sounds.

Recall the hypothetical background task that was described in the section above covering the @PostConstruct and @PreDestroy lifecycle annotations. Perhaps an application has a number of such background tasks, and those task instances would typically require XML bean definitions in order to be registered with the Spring context and have their lifecycle methods invoked at the right time. With component scanning, there is no longer a need for those explicit XML bean definitions. If the background tasks all implement the same interface or follow a naming convention, then include-filters could be used. However, an even simpler approach is to create an annotation for these task objects and provide the @Component meta-annotation.

@Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Component
 public @interface BackgroundTask {
   String value() default "";

Then provide the custom stereotype annotation in any background task's class definitions.

@BackgroundTask
 public class FilePoller {
 
   @PostConstruct
   public void startPolling() {
    ...
   }
 
   @PreDestroy
   public void stopPolling() {
    ...
   }
   ...

The generic @Component annotation could just as easily have been provided instead, but the custom annotation technique provides an opportunity for using meaningful, domain-specific names. These domain-specific annotations then provide further opportunities such as using an AspectJ pointcut expression to identify all background tasks for the purpose of adding advice that monitors the activity of those tasks.

By default, when a component is detected, Spring will automatically generate a "bean name" using the non-qualified class name. In the previous example, the generated bean name would be "filePoller". However, for any class that is annotated with one of Spring's stereotype annotations (@Component, @Repository, @Service, or @Controller) or any other annotation that is annotated with @Component as a meta-annotation (such as @BackgroundTask in the above example), the 'value' attribute can be explicitly specified for the stereotype annotation, and the instance will then be registered within the context with that value as its "bean name". In the following example the name would be "petClinic" instead of the default generated name of "simpleJdbcClinic".

@Service("petClinic")
 public class SimpleJdbcClinic {
   ...
 

Likewise, the bean name generated for the following revised version of the FilePoller would be "poller" instead of "filePoller".

@BackgroundTask("poller")
 public class FilePoller {
   ...

While all Spring-managed objects are treated as singleton instances by default, it is sometimes necessary to specify an alternate "scope" for an object. For example, in the web-tier a Spring-managed object may be bound to 'request' or 'session' scope. As of version 2.0, Spring's scope mechanism is even extensible so that custom scopes can be registered with the application context. Within an XML configuration, it's simply a matter of including the 'scope' attribute and the name of the scope.

<bean id="shoppingCart" class="example.ShoppingCart" scope="session">
   ...
</bean> 

With Spring 2.5, the same can be accomplished for a scanned component by providing the @Scope annotation.

@Component
 @Scope("session")
 public class ShoppingCart {
   ...

One final topic to address here is the simplification of qualifier annotations when using component-scanning. In the previous section, the following object was used as an example of autowiring with a custom qualifier annotation:

@VetSpecialty("dentistry")
 private Clinic dentistryClinic; 

That same example then featured use of a 'qualifier' element within the XML on the intended target bean definition for that dependency. When using component-scanning, the XML metadata is not necessary. Instead, the custom qualifier may be included as a type-level annotation in the target class definition. An alternative example with a scanned @Repository instance as the dependency would thus appear as follows:

@Repository
 @VetSpecialty("dentistry")
 public class DentistryClinic implements Clinic {
   ...

Finally, for the previous example that featured custom qualifier annotations with attributes, the non-XML equivalent for that dependency's target would be:

@Repository
 @SpecializedClinic(species="dog", breed="poodle")
 public class PoodleClinic implements Clinic {
   ...

정리#

2.5에서 추가된 기능중에 설정의 단순화를 도모할수 있는 애노테이션 위주로 살펴보았다.

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-7) was last changed on 20-Nov-2007 19:25 by DongGukLee