어노테이션을 사용하지 않고, xml에서 autowire 지시자를 통한 의존성 주입을 위해서는
해당 클래스에 주입을 원하는 변수의 setter 메소드가 반드시 존재해야 한다.
(어노테이션을 사용할 경우 setter 메소드가 별도로 필요하지 않다.)


대부분의 서적이나 인터넷 자료들을 보면 이러한 setter메소드의 필요성은 누락하고
autowire의 옵션별 설명만을 서술한다. 이러한 서술자료를 보면 아래와 같다.


autowire 옵션
 - byName : 프로퍼티의 이름과 같은 이름을 갖는 빈 객체를 설정
 - byType : 프로퍼티의 타입과 같은 타입을 갖는 빈객체를 설정
 - constructor : 생성자 파라미터 타입과 같은 타입을 갖는 빈객체를 생성자에 전달
 - autodetect : constructor 방식을 먼저 적용하고, byType 방식을 적용하여 의존 객체를 설정


설명만 보면 간단하게 이해할 수 있다.

하지만 실제로 의존성 주입을 통한 어플리케이션을 구현하려고 하면
setter 메소드가 없다면 절대 의존성주입은 이루어지지 않는다.
더군다나 setter 메소드에 대한 설명없이는 어노테이션을 사용하지 않는 의존성주입은 설명되기 어렵다.

예를 들어 일반적으로 의존성주입을 위한 클래스를 만들라고 하면 아래와 같을 것이다.

------------------------------------------------
...java...

public class Service{

...

  protected DAO dao;
  public setDAO(DAO dao){
    this.dao = dao;
  }

...


}

...xml...
<bean id="service" class="com.Service" autowire="byName"/>
<bean id="dao" class="com.DAO"/>
--------------------------------------------------------

위와 같이 설정되었다고 한다면 Service 클래스의 맴버변수인 dao에는 com.DAO 의 인스턴스가 주입될 것이다.
autowire 설명만 본다면 당연한 결과이다.
byName : 프로퍼티의 이름과 같은 이름을 갖는 빈 객체를 설정
자 그렇다면 Service 클래스에서 맴버변수인 dao의 이름을 dao2로 바꾼다면 어떻게 될까?
dao2라는 빈이 존재하지 않기 때문에 주입이 되지 않을까?
결과는 아무런 이상이 없이 정상적으로 의존성주입이 수행된다.
단순히 autowire 옵션에 대한 설명만 본다면
이러한 현상은 어떻게 설명될 수 있을까?


어노테이션을 사용하지 않는 의존성 주입 방식의 경우
기본적으로 해당 클래스에 정의한 setter 메소드에 의해 의존성 주입이 수행된다.
결국 의존성 주입시 바라보는 것은 해당 프로퍼티의 이름이 아닌
해당 프로퍼티의 setter 메소드라는 말이다.
autowire="byName" 으로 설정할 경우 set을 접두어로 사용하는 메소드 중에서
접미어로 지정된 명칭을 기준으로 빈 객체를 찾게된다.


결국 byName 에 대한 설명은 아래와 같이 바뀌어야 한다.
byName : 프로퍼티의 setter 메소드의 set을 제외한 이름과 같은 이름을 갖는 빈 객체를 설정

byType의 경우도 마찬가지이다.
프로퍼티의 타입이 아니라 정의된 setter 메소드의 파라미터 타입에 의해
의존성 주입이 결정된다. 이러한 이유로  byType의 경우 setter메소드의 명칭은
set만 접두어로 사용되면 되고 이후 접미어는 어떤 것이 와도 상관없다.
어차피 의존성주입시에는 파라미터의 타입만 확인하기 때문이다.


이렇게 어노테이션을 사용하지 않는 의존성주입의 경우
단순히 해당 맴버변수의 명칭이나 타입이 아닌 해당 setter 메소드의
관점에서 바라봐야만 명확한 정의를 내릴 수 있다.

 

Posted by mypiece
,
Spring 에서 객체변수의 인스턴스를 얻어오는 방식은 3가지 방법이 있다.


1. 자동묶기 방식
 - 해당 bean을 컨테이너에 등록시 autowire 지시자를 이용하여 자동묶기를 한다.
   일반적으로 xml파일과 java파일에 아래와 같이 코딩한다.
   해당 java소스에는 반드시 set메소드가 존재해야 한다.
   단, constructor 방식을 사용할 경우에는 생성자가 주입을 담당하기 때문에
   굳이 set메소드가 존재하지 않아도 된다.
  
  1.1. application-context.xml
    ...생략...
    <bean id="sqlMapClientUtil" class="com.local.util.SqlMapClientUtil" autowire="byName">
    </bean>
    ...생략...

  1.2. SqlMapClientUtil.java
    ...생략...
    private SqlMapClient sqlMapClient;
   
    public void setSqlMapClient(SqlMapClient sqlMapClient) {
    this.sqlMapClient = sqlMapClient;
    }
    ...생략...

*   기타 autowire 옵션 

   1) byName : 프로퍼티의 이름과 같은 이름을 갖는 빈 객체를 설정
   2) byType : 프로퍼티의 타입과 같은 타입을 갖는 빈객체를 설정
   3) constructor : 생성자 파라미터 타입과 같은 타입을 갖는 빈객체를 생성자에 전달

   4) autodetect : constructor 방식을 먼저 적용하고, byType 방식을 적용하여 의존 객체를 설정
    

byName를 제외한 방식의 경우 2개 이상의 bean이 검색될 경우 아래와 같은 Exception을 발생시키게 된다.
org.springframework.beans.factory.UnsatisfiedDependencyException                    

이러한 경우 autowire 방식이 아닌 직접지정 방식을 사용해야 한다.
      
타입에 의한 자동와이어링은 타입이 같은 빈이 두 개 이상 존재하는 경우에는 적용되지 못한다.

이름에 의한 자동와이어링은 프로퍼티 이름과 빈의 아이디만 비교하면 되기 때문에 상대적으로 빠르다.

하지만 타입을 비교하는 것은 String으로 된 이름을 비교할 때보다 느리다. 

게다가 자동와이어링은 빈의 모든 프로퍼티에 일괄 적용되어 대상이 아니어도 비교하는 작업이 일어나게 된다.​

 



2. 직접 지정방식

 - 해당 bean을 컨테이너에 등록시 주입할 property를 아래와 같이 지정한

.

  2.1. application-context.xml
    ...생략...
    <bean id="sqlMapClientUtil" class="com.local.util.SqlMapClientUtil">
      <property name="sqlMapClient" ref="sqlMapClient"/>
    </bean>
    ...생략...

  2.2. SqlMapClientUtil.java
    ...생략...
    private SqlMapClient sqlMapClient;
   
    public void setSqlMapClient(SqlMapClient sqlMapClient) {
    this.sqlMapClient = sqlMapClient;
    }
    ...생략...
    
 * 생성자 방식을 사용할 경우 아래와 같이 지정한다.
    <constructor-arg type="java.lang.String" index="0">
        <value>config/service/serviceContext-config.xml</value>
    </constructor-arg>
    인자의 순서와 타입에 주의하도록 한다.
    java 다형성에 의해 생성자가 여러개 정의되어 있는 경우
    인자의 순서와 타입에 의해 생성자가 결정된다.

 


 

 

3. bean으로 등록되지 않은 클래스에서 동적 얻어오는 방식
 - 컨테이너에 등록된 bean에 객체를 주입할때 위와 같은 방식으로 할 수도 있지만
   bean으로 등록하지 않은 유틸클래스에서 해당 객체를 주입할 필요가 있을때가 있다.
   유틸클래스로 사용될 클래스를 모두 bean 컨테이너에 명시하고, autowire를 하던지
   직접 지정하는 것도 한 방법이다.
   하지만 지금부터 소개하는 아래의 방식이 더 깔끔한 방식 임을 알고 있으면 된다.
   아래의 방식은 application-context 자체를 객체로 얻어서 해당 객체에서 관리되는

   bean객체를 얻어올 수 있다는 점을 이용한다.
  
   그렇기 때문에 프록시 역할을 하게될 application-context 객체를 얻어오는 클래스를

   구현하고 최소한 해당 클래스는 bean으로 등록되어 있어야 한다.
  
  3.1. application-context 객체를 얻어올 클래스 구현.
    - ApplicationContextAware 인터페이스를 구현한 클래스여야 한다.
      해당 인터페이스에는 아래와 같은 1개의 메소드가 존재한다.
     
      public abstract interface ApplicationContextAware
      {
        public abstract void setApplicationContext(ApplicationContext paramApplicationContext)
          throws BeansException;
      }
     
      해당 메소드를 구현해서 application-context 객체를 구하면 된다.
      아래는 해당 인터페이스를 구현한 실제 클래스 예이다.
    
      ...생략...
      public class ApplicationContextUtil implements ApplicationContextAware{
    
        private static ApplicationContext context;
       
        public void setApplicationContext(ApplicationContext ac)throws BeansException {
        ApplicationContextUtil.context = ac;
       }
       
       public static ApplicationContext getContext(){
        return ApplicationContextUtil.context;
       }
      ...생략...

  3.2. 컨테이너 등록
    - 위에서 구현한 ApplicationContextUtil 을 bean으로 등록한다.
    
     <bean id="applicationContextUtil" class="com.local.util.ApplicationContextUtil" autowire="byName">
     </bean>
     
  3.3. 유틸 클래스에서 ApplicationContextUtil 을 통해 객체를 주입한다.
    - 아래와 같이 application-context 객체에서 getBean()메소드를 통해 컨테이너에 존재하는 bean 객체를 얻어올 수 있다.
  
      localeResolver = (LocaleResolver)ApplicationContextUtil.getContext().getBean( "localeResolver"); 

 

Posted by mypiece
,

스프링에서는 모든 서블릿에서 사용될 공유자원을 DI개념으로 사용하기 위해
일반적으로 ContextLoader를 구현한 위 두개의 클래스를 제공한다.
이는 일반적으로 web.xml에 ContextLoaderListener 또는 ContextLoaderServlet 중 하나를
선택적으로 정의해서 사용한다.


목적과 기능은 동일하나 Servlet 2.2 나 2.3 에서는 ContextLoderServlet을,

Servlet 2.4 이상부터는 ContextLoaderListener를 사용하는 것이 권장된다.

백워드 호환을 위해 ContextLoaderServlet를 사용하는게 좋다고 하지만
상황에 따라 명시적으로 선택해서 사용하는 것이 좋을 듯 하다.

WAS에서 지원하는 Servlet버전을 확인하여 그에 맞게 사용하도록 하자.

참고로 Tomcat5.5에서 제공하는 Servlet버전은 2.4 이므로

ContextLoaderListener를 사용하면 되겠다.


각각 선언방법은 아래와 같다.

ContextLoaderListener

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


ContextLoderServlet

 <servlet>
  <servlet-name>ContextLoaderServlet</servlet-name>
  <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>


주의할 점은 ContextLoaderServlet를 선언하여 사용할 경우에는
반드시 <load-on-startup>을 기타 명시적인 서블릿보다 적게 지정하여
제일먼저 로드되도록 해야 한다. ContextLoaderListener는 listener로 지정되기 때문에

굳이 지정하지 않아도 서버가 구동될때 제일먼저 로드된다.


ContextLoader에서 사용될 자원의 경우 일반적인 Servlet과 마찬가지로
web.xml에서 param 지정을 통해 명시할 수 있다.
일반적인 param이 아닌 Context param으로 아래와 같이 명시한다.


<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>


위에서 <param-value>/WEB-INF/applicationContext.xml</param-value> 절은

생략할수도 있는데 이럴 경우 /WEB-INF/applicationContext.xml파일을

기본 설정파일로 사용하게 된다.

<param-value>을 지정할 경우 xml파일의 경로는 물론이고, 파일명도 원하는대로

선언할 수 있다. 프로젝트 성격에 따라 경로는 달라질 수도 있고, 추후 유지보수를 위해서도

위와 같이 명시적으로 선언하는 것을 추천한다.


이렇게 구현된 ContextLoader는 기타 DispatcherServlet의 부모 역할을 하게 되며
ContextLoader의 모든 자원은 DispatcherServlet에서 활용이 가능하게 된다.
단, ContextLoader에서 자식인 DispatcherServlet의 자원을 이용하는 것은 불가능하다.

Servlet에서의 initParam 자원 취득방식은 다음과 같다.
기본적으로 해당 DispatcherServlet에서 자원을 검색하게 되고
없다면 부모인 ContextLoader에서 자원을 검색하게 된다.
만약 ContextLoader에도 해당 자원이 없다면 Exception을 발생시키게 된다.

Posted by mypiece
,