on
의존관계주입
의존관계주입
반응형
의존 관계 주입
내부 빈
빈 의존관계를 여러 빈이 공유하지 않는다면 의존관계를 내부 빈으로 만든것을 고려할 수 있다.
내부 빈은 해당 내부 빈 정의를 둘러싸고 있는 빈 정의 안에서만 접근할 수 있다.
스프링 컨테이너에 등록된 다른 빈들은 내부 빈을 사용할 수 없다.
내부 빈 정의에 해당하는 엘리먼트는 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 빈에 암시적으로 의존한다는 뜻이다.
... ...
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 메서드를 스프링이 오버라이드 하기 때문에 메서드 안에서 어떤 동작을 수행하든 아무 동작도 하지 않는 빈 메서드로 만들든 관계없다.
...
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에 전달할 이름과 같은 이름의 빈 인스턴스를 반환받는다.
...
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 하위 엘리먼트를 사용해서 메서드 인수타입을 지정할 수 있다.
java.lang.String java.lang.String
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를 사용하면 애플리케이션의 전체 구조를 명시적으로 문서화하는 효과가 있다.
대규모 애플리케이션에서는 자동연결을 권장하지 않는다.
배워서 바로 쓰는 스프링프레임워크
애시시 사린, 제이 샤르마 지음
오현석 옮김
반응형
from http://frontierdev.tistory.com/263 by ccl(A) rewrite - 2021-12-26 23:27:47