본문 바로가기

JAVA/Spring

의존관계주입

반응형

의존 관계 주입

내부 빈

  • 빈 의존관계를 여러 빈이 공유하지 않는다면 의존관계를 내부 빈으로 만든것을 고려할 수 있다.
  • 내부 빈은 해당 내부 빈 정의를 둘러싸고 있는 빈 정의 안에서만 접근할 수 있다.
  • 스프링 컨테이너에 등록된 다른 빈들은 내부 빈을 사용할 수 없다.
<bean id="service" class="sample.spring.service.FixedDepositServiceImpl">    
    <property name="fixedDepositDao">
        <bean class="sample.spring.dao.FixedDepositDaoImpl" />
    </property>
</bean>
  • 내부 빈 정의에 해당하는 엘리먼트는 id 속성을 지정하지 않는다.
  • 내부 빈은 항상 프로토타입 스코프 빈이기 때문에 내부 빈에 해당하는 엘리멘트에 scope 속성이 들어있어도 이를 무시한다.

depends-on 속성을 통해 빈 초기화 순서 제어하기

  • A빈이 B빈을 생성자 인수로 받는다면, 스프링 컨테이너는 A를 생성하기 전에 B를 생성한다. 이때 XML 파일에 두 빈이 정의된 순서는 무시된다.
  • 스프링 컨테이너가 이런식으로 작동하기 때문에 어떤 빈이 의존하는 모든 의존 관계가 주입 전에 미리 설정되도록 보장할 수 있다.
  • 빈 의존관계가 암시적인 경우 depends-on 속성을 활용해 스프링 컨테이너가 빈을 생성하는 순서를 제어할 수 있다.
  • depends-on이 지정한 의존 관계에 들어 있는 빈이 depends-on 속성이 들어 있는 빈보다 먼저 초기화되도록 보장한다.
public class EventSenderSelectorServiceImpl {
    public EventSenderSelectorServiceImpl(String configFile) throws Exception {
        ClassPathResource resource = new ClassPathResource(configFile);
        OutputStream os = new FileOutputStream(resource.getFile());
        Properties properties = new Properties();
        properties.setProperty(Constants.EVENT_SENDER_CLASS_PROPERTY,
            "sample.spring.event.DatabaseEventSender");
        properties.store(os, null);
        //...
    }
}
  • EventSenderSelectorServiceImpl 클래스 생성자에게 appConfig.properties 파일 경로를 전달한다.
  • EventSenderSelectorServiceImpl 클래스 생성자는 이름이 eventSenderClass인 프로퍼티를 Constants 클래스에 정의된 EVENT_SENDER_CLASS_PROPERTY 상수값으로 appConfig.properties 파일에 쓴다.
  • eventSenderClass 프로퍼티는 데이터베이스에 이벤트를 저장하기 위해 FixedDepositServiceImpl 인스턴스가 사용할 EventSender 구현의 전체 이름을 지정한다.
  • 단순화를 위해 EventSenderSelectorServiceImpl 클래스 생성자는 eventSenderClass 프로퍼티 값을 DatabaseEventSender 클래스의 전체 이름으로 지정한다.

appConfig.properties

eventSenderClass=sample.spring.event.DatabaseEventSender
public class FixedDepositServiceImpl implements FixedDepositService {

    private FixedDepositDao fixedDepositDao;
    private EventSender eventSender;

    public FixedDepositServiceImpl(String configFile) throws Exception {
        ClassPathResource configProperties = new ClassPathResource(configFile);

        if (configProperties.exists()) {
            InputStream inStream = configProperties.getInputStream();
            Properties properties = new Properties();
            properties.load(inStream);

            String eventSenderClassString = properties.getProperty(Constants.EVENT_SENDER_CLASS_PROPERTY);
            if (eventSenderClassString != null) {
                Class<?> eventSenderClass = Class.forName(eventSenderClassString);
                eventSender =  (EventSender) eventSenderClass.getDeclaredConstructor().newInstance();
                logger.info("Created EventSender class");
            } else {
                logger.info("appConfig.properties file doesn't contain the information about EventSender class");
            }
        }
    }

    public void createFixedDeposit(FixedDepositDetails fixedDepositDetails) throws Exception {
        //...
        eventSender.sendEvent(event);
    }
}
  • appConfig.properties 파일에서 eventSenderClass 프로퍼티를 찾을 수 없으면, eventSenderClassString 변수가 설정되지 않는다.
  • 스프링 컨테이너가 FixedDepositServiceImpl 인스턴스를 EventSenderSelectorServiceImpl 인스턴스보다 먼저 생성하게 되면(먼저 빈 정의 순서대로) FixedDepositServiceImpl 인스턴스는 appConfig.properties 파일에서 eventSenderClass 프로퍼티를 찾을 수 없게 된다.
  • FixedDepositServiceImpl 빈이 EventSenderSelectorServiceImpl 빈에 암시적으로 의존한다는 뜻이다.
<bean id="service" class="sample.spring.service.FixedDepositServiceImpl" depends-on="eventSenderSelectorService">
    ...
</bean>

<bean id="eventSenderSelectorService" class="sample.spring.service.EventSenderSelectorServiceImpl">
    ...
</bean>
  • service 빈은 depends-on 속성을 사용해 자신이 eventSenderSelectorService 빈에 의존한다는 사실을 명시한다.
  • service 빈이 eventSenderSelectorService에 대한 의존관계를 지정했기 때문에 스프링 컨테이너는 service 빈 인스턴스를 생성하기 전에 eventSenderSelectorService를 생성한다.
  • 빈에 암시적 의존관계가 여러 존재하면 depends-on 속성 값으로 모든 의존관계의 이름이나 id를 지정할 수 있다.
  • 자식 빈 정의는 depends-on 속성을 상속하지 않는다.

싱글턴 빈 내부에서 새로운 프로토타입 빈 인스턴스 얻기

  • 싱글턴 빈이 의존하는 프로토타입 스코프 의존관계는 싱글턴 빈이 생성되는 시점에 주입된다.
  • 스프링 컨테이너가 싱글턴 빈의 인스턴스를 단 한번만 생성하기 때문에 싱글턴 빈은 자신의 생애주기 동안 똑같은 프로토타입 빈 인스턴스에 대한 참조를 유지한다.

싱글턴 빈의 메서드를 사용해서 프로토타입 스코프 의존관계의 새 인스턴스를 얻는 법

  • 싱글턴 빈 클래스가 ApplicationContextAware 인터페이스를 구현
  • 스프링 beans 스키마의 사용
  • 스프링 beans 스키마의 사용

ApplicationContextAware 인터페이스

  • 내부 메서드가 실행하는 동안 ApplicationContext 인스턴스에 접근할 필요가 있는 빈은 스프링 ApplicationContextAware 인터페이스를 구현해야 한다.
  • ApplicationContextAware 인터페이스는 setApplicationContext 메서드를 구현하는 빈에 ApplicationContext 객체를 제공한다.
  • 스프링 컨테이너는 빈이 생성하면서 setApplicationContext 메서드를 호출한다.
  • ApplicationContextAware 인터페이스는 생애주기 인터페이스이다.
  • 생애주기 인터페이스는 빈의 생애주기에 적절한 시점에 스프링 컨테이너가 호출할 수 있는 콜백 메서드를 하나이상 정의하는 인터페이스다.
  • 스프링 컨테이너는 빈 인스턴스를 생성한 다음 빈 인스턴스가 완전히 초기화되기 전에 ApplicationContextAware의 setApplicationContext 메서드를 호출한다.
public class CustomerRequestServiceImpl implements CustomerRequestService, ApplicationContextAware {

    private CustomerRequestDao customerRequestDao;
    private ApplicationContext applicationContext;

    @ConstructorProperties({"customerRequestDao"})
    public CustomerRequestServiceImpl(CustomerRequestDao customerRequestDao) {
        this.customerRequestDao = customerRequestDao;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void submitRequest(String requestType, String requestDescription) {
        CustomerRequestDetails customerRequestDetails = applicationContext.getBean(CustomerRequestDetails.class);
        customerRequestDetails.setType(requestType);
        customerRequestDetails.setDescrption(requestDescription);
        customerRequestDao.submitRequest(customerRequestDetails);
    }

}
  • setApplicationContext 메서드는 CustomerRequestServiceImpl에 ApplicationContext 객체 인스턴스를 제공한다.
  • 나중에 submitRequest 메서드에서 ApplicationContext 인스턴스를 사용해 스프링 컨테이너로부터 CustomerRequestDetails 객체 인스턴스를 얻는다.

ApplicationContextAware 인터페이스를 구현하는 방식의 단점

  • 빈과 스프링 프레임워크에 결합시킨다는데 있다.
  • 빈 클래스와 스프링 프레임워크를 결합시키는 것을 피하고 싶지만, 스프링 컨테이너를 통해 다른 빈에 접근해야 한다면 beans 스키마가 제공하는 lookup-method, replace-method 를 통한 메서드 주입 기법을 사용한다.

엘리먼트

  • 어떤 빈 클래스가 빈을 표현하는 타입을 반환하는 빈 검색 메서드를 정의한다면 을 사용해서 스프링에게 해당 메서드 구현을 제공하게 만들 수 있다.
  • 스프링이 제공하는 메서드 구현은 스프링 컨테이너에서 빈 인스턴스를 가져와 반환한다.
  • 의 bean 속성은 검색할 빈의 이름을 지정하고, name 속성은 스프링이 구현을 제공할 메서드의 이름을 지정한다.
  • 빈 클래스는 빈 검색 메서드를 추상 메서드나 구체적인 메서드로 정의해야 한다는 점이 중요하다.
public abstract class CustomerRequestServiceImpl implements CustomerRequestService {

    private CustomerRequestDao customerRequestDao;

    @ConstructorProperties({"customerRequestDao"})
    public CustomerRequestServiceImpl(CustomerRequestDao customerRequestDao) {
        this.customerRequestDao = customerRequestDao;
    }

    public abstract CustomerRequestDetails getCustomerRequestDetails();

    @Override
    public void submitRequest(String requestType, String requestDescription) {
        // CustomerRequestDetails 객체에 내용을 넣고 저장한다.
        CustomerRequestDetails customerRequestDetails = getCustomerRequestDetails();
    }

}
  • getCustomerRequestDetails를 정의하는 CustomerRequestServiceImpl 클래스 변종이다.
  • getCustomerRequestDetails의 반환 타입은 CustomerRequestDetails이다.
  • submitRequest 메서드는 getCustomerRequestDetails 메서드를 호출해서 새로운 CustomerRequestDetails 인스턴스를 얻는다.
  • getCustomerRequestDetails 메서드를 구체적인 메서드로 정의할 수도 있다.
  • getCustomerRequestDetails 메서드를 스프링이 오버라이드 하기 때문에 메서드 안에서 어떤 동작을 수행하든 아무 동작도 하지 않는 빈 메서드로 만들든 관계없다.
<bean id="customerRequestSerivce" class="sample.spring.service.CustomerRequestServiceImpl">
    <construrctor-args name="customerRequestDao" ref="customerRequestDao" />
    <lookup-method bean="customerRequestDetails" name="getCustomerRequestDetails" />
</bean>

<bean id="customerRequestDetails" class="sample.spring.service.CustomerRequestDetails" scope="prototype" />
    ...
</bean>
  • CustomerRequestServiceImpl 클래스에 대한 빈 정의에 엘리먼트 name 속성값은 getCustomerRequestDetails이다.
  • 스프링이 getCustomerRequestDetails에 대한 검색 메서드를 구현을 제공하도록 지시한다.
  • bean 속성값은 customerRequestDetails이다.
  • 이는 getCustomerRequestDetails 메서드 구현이 스프링 컨테이너에서 customerRequestDetails인 빈을 id 또는 name로 받아서 반환한다는 뜻이다.
  • customerRequestDetails 빈이 CustomerRequestDetails 객체를 표현하므로, getCustomerRequestDetails 메서드 구현은 CustomerRequestDetails 객체를 반환한다.
  • 스프링 컨테이너가 빈 검색 메서드 구현을 제공하기 때문에 빈 검색 메서드의 시그니처에 몇가지 제약이 존재한다.
  • 빈 검색 메서드는 반드시 public이나 protected로 정의하고, 어떤 인수도 받으면 안된다.
  • 실행 시점에 스프링이 빈 검색 메서드 구현을 제공하려면 빈 검색 메서드가 정의된 클래스의 하위 클래스를 만들어야 한다.
  • 따라서 빈 클래스와 빈 검색 메서드는 final이 아니어야 한다.

실행 시점에 스프링이 빈 검색 메서드 구현을 제공하려면 빈 검색 메서드를 포함하는 빈 클래스의 하위 클래스를 만들어야 한다.
이를 위해 스프링은 CGLIB 라이브러리르 사용해 빈 클래스의 하위 클래스를 만든다.
스프링 3.2부터는 spring-core jar 파일 내부에 CGLIB이 함께 패키징된다.
따라서 명시적으로 CGLIB jar 파일에 대한 의존관계를 프로젝트에 기술할 필요는 없다.

엘리먼트

  • replace-method를 사용하면 빈 클래스에 있는 아무 메서드나 다른 구현으로 대체할 수 있다.
public class CustomerRequestServiceImpl implements CustomerRequestService {

    private CustomerRequestDao customerRequestDao;

    public Object getMyBean(String beanName) {
        return null;
    }

    @ConstructorProperties({"customerRequestDao"})
    public CustomerRequestServiceImpl(CustomerRequestDao customerRequestDao) {
        this.customerRequestDao = customerRequestDao;
    }


    @Override
    public void submitRequest(String requestType, String requestDescription) {
        CustomerRequestDetails customerRequestDetails = (CustomerRequestDetails) getMyBean("customerRequestDetails");
        customerRequestDetails.setType(requestType);
        customerRequestDetails.setDescrption(requestDescription);
        customerRequestDao.submitRequest(customerRequestDetails);
    }

}
  • getMyBean 메서드를 정의하는 CustomerRequestServiceImpl 클래스를 보여준다.
  • getMyBean 메서드는 빈 이름을 인수로 받고 이름에 대응하는 빈 인스턴스를 반환하는 대신 null을 반환한다.
  • submitRequest 메서드는 getMyBean 메서드에 빈 이름(customerRequestDetails)을 인수로 넘기고, getMyBean 메서드가 customerRequestDetails 빈 인스턴스를 반환한다고 가정한다.
  • replace-method 사용하면 getMyBean을 오버라이드해서 인수로 받은 빈 이름에 해당하는 빈 인스턴스를 반환할 수 있다.
  • 오버라이드할 메서드는 스프링의 methodReplacer 인터페이스를 구현하는 클래스가 제공한다.
public class MyMethodReplacer implements MethodReplacer, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        return applicationContext.getBean((String) args[0]);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws Throwable {
        this.applicationContext = applicationContext;
    }

}
  • 스프링 MethodReplacer 인터페이스는 reimplement 메서드를 정의하고, 메서드에 대한 구현은 MyMethodReplacer 클래스가 제공한다.
  • reimplement 메서드는 오버라이드할 메서드를 표현한다. MyMethodReplacer 클래스의 reimplement 메서드가 스프링 컨테이너로부터 인스턴스를 얻기 위해 스프링 ApplicationContext 객체를 사용하므로 MyMethodReplacer는 스프링의 ApplicationContextAware도 구현한다.

Object obj: 메서드를 오버라이드할 대상 객체. CustomerRequestServiceImpl 객체다.
Method method: reimplement 메서드가 오버라이드할 빈 메서드. CustomerRequestServiceImpl의 getMyBean 메서드다.
Object[] args: 오버라이드할 대상 메서드에 전달된 인수. CustomerRequestDetails의 getMyBean 메서드에 전달할 인수를 표현한다.
reimplement 안에서 args[0]은 CustomerRequestServiceImpl의 getMyBean 메서드에 전달된 빈 이름 인수를 가리킨다.

  • MyMethodReplacer의 reimplement 메서드가 args 인수를 사용해ㅓ CustomerRequestServiceImpl의 getMyBean에 전달된 빈 이름을 얻는다.
  • 그 후 reimplement는 ApplicationContext의 getBean 메서드를 호출해 이름이 일치하는 빈 인스턴스를 얻는다.
  • MyMethodReplacer의 reimplement가 CustomerRequestServiceImpl의 getMyBean을 오버라이드하므로, 실행시점에 getMyBean을 호출하면 getMyBean에 전달할 이름과 같은 이름의 빈 인스턴스를 반환받는다.
<bean id="customerRequestSerivce" class="sample.spring.service.CustomerRequestServiceImpl">
    <construrctor-args name="customerRequestDao" ref="customerRequestDao" />
    <replace-method bean="getMyBean" replacer="methodReplacer" />
</bean>

<bean id="methodReplacer" class="sample.spring.service.MyMethodReplacer" />
    ...
</bean>
  • MyMethodReplacer와 CustomerRequestServiceImpl 클래스의 빈 정의를 보여준다.
  • replace-method name 속성은 오버라이드하려면 메서드의 이름을 지정하고, replacer 속성은 MethodReplacer 인터페이스를 정의하는 빈에 대한 참조를 지정한다.
  • name 속성이 지정하는 메서드는 replacer 속성이 참조하는 빈의 reimplement 메서드에 의해 오버라이드된다.
  • replace-method를 사용해 빈 클래스의 추상 메서드나 구체적인 메서드를 다른 메서드 구현으로 대체할 수 있다.
  • getMyBean 메서드를 abstract로 선언하고 replace-method를 사용하는 방식과 똑같이 써도 제대로 오버라이딩된다.

빈 메서드의 유일성

  • replace-method 대치하고 싶은 빈 메서드를 이름만 가지고 유일하게 식별할 수 없는 상황도 있다.
  • 오버로드한 perform 메서드가 존재하는 빈 클래스
  • public class MyBean { public void perform(String task1, String task2) { ... } public void perform(String task) { ... } public void perform(MyTask task) { ... } }
  • MyBean 클래스에는 perform 메서드가 여러 존재한다. 오버라이딩할 대상 메서드를 유일하게 식별하기 위해 replace-method안에 arg-type 하위 엘리먼트를 사용해서 메서드 인수타입을 지정할 수 있다.
<bean id="mybean" class="MyBean">
    <replace-method name="perform" replacer="..." >
        <arg-type>java.lang.String</arg-type>
        <arg-type>java.lang.String</arg-type>
    </replace-method>
</bean>
  • arg-type 값으로 인수 타입의 전체 이름을 지정하는 대신 전체 이름의 부분 문자열을 지정해도 된다.

의존관계 자동 연결

  • byType
  • constructor
  • byName
  • default/no (자동연결 비활성화)
  • autowire-cadidate="false": 빈을 자동연결에 사용하지 못하게 막기
  • default-autowire-cadidates="*Dao" : 패턴과 일치하는 빈을 자동 연결에서 사용할 수 있도록

자동 연결의 한계

  • 생성자 인수나 프로퍼티의 타입이 단순한 자바타입(int, long, boolean, String, Date 등)인 경우 자동 연결을 사용할 수 없다.
  • autowire 속성값을 byType, constructor로 지정하면 배열이나 타입이 있는 컬렉션, 맵 등을 자동 연결할 수 있다.
  • 의존관계를 스프링이 자동으로 해결하기 때문에 애플리케이션 전체 구조가 감춰진다. 빈 사이의 의존관계를 지정하기 위해 property, construrctor-args를 사용하면 애플리케이션의 전체 구조를 명시적으로 문서화하는 효과가 있다.
  • 대규모 애플리케이션에서는 자동연결을 권장하지 않는다.

 

배워서 바로 쓰는 스프링프레임워크
애시시 사린, 제이 샤르마 지음
오현석 옮김

반응형

'JAVA > Spring' 카테고리의 다른 글

데이터베이스 연결  (0) 2022.01.05
빈과 빈 정의 커스텀  (0) 2021.12.26
빈 설정  (0) 2021.12.22
IoC 컨테이너 계층 구조 - 토비의 스프링  (0) 2020.09.25
IoC 컨테이너와 DI - 토비의 스프링  (0) 2020.09.24