Dev/Spring

Spring2.5 - 제어의역전,의존성주입 예제

창문닦이 2019. 3. 28. 11:35

의존성 주입 (Dependency Injection)

클래스 설계에서 주요 고려 사항중 하나는 객체 간 의존성을 제거해서 변경사항에 유연하게 대처하도록 구성하는 것이다.

의존성을 제거하는 클래스 구현방법으로 각 클래스가 인터페이스를 기반으로 호출하게 하고, 의존성 주입을 통해 객체 생성을 추상화하는 방법이 있다.

Dependency Injection : 스프링은 객체의 의존성을 의존성 주입을 통해 관리한다. 

의존성 주입 방법은 생성자 기반(Constructor Injection), 세터 기반(Setter Injection) 의존성 주입이 있다. 

DI는 Spring 프레임워크에서 새롭게 지원하는 IoC의 한 형태로 각 클래스사이의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동적으로 연결해주는 것을 말한다. 컨테이너가 의존관계를 자동적으로 연결시켜주기 때문에 개발자들이 컨테이너 API를 이용하여 의존관계에 관여할 필요가 없게 되므로 컨테이너 API에 종속되는 것을 줄일 수 있다. 개발자들은 단지 빈 설정 파일(저장소 관리 파일)에서 의존관계가 필요하다는 정보를 추가하기만 하면 된다.

IoC 컨테이너 

IoC(Inversion of Control) : 디자인 패턴의 일종으로, 협조해 동작하는 복수의 컴포넌트간의 의존성을 매우 느슨하게 해 두는 것으로, 각 컴포넌트의 재사용성을 높이고자 하는 것이다. 

Spring 프레임워크가 가지는 가장 핵심적인 기능인 IoC는 제어권을 프레임워크에 넘기는 포괄적인 개념이다. 

여기서 제어권은 새로운 객체의 생성, 트랜잭션, 보안에 대한 제어들을 의미 한다.

개발자들이 가지고 있던 제어권이 서블릿과 EJB를 관리하는 컨테이너에게로 넘어가게 되었다. 서블릿, EJB가 나타나기 전까지는 모든 클래스를 개발자들이 직접 생성할 수 있었지만 서블릿과 EJB의 경우는 다르다. 개발자들이 서블릿과 EJB를 직접 생성하고 싶어도 이 객체 들을 직접 생성하여 제어할 수 없게 되어버린 것이다. 객체 생성에 대한 제어권이 컨테이너에게 넘어가면서 객체의 생명주기(Life Cycle)를 관리하는 권한 또한 컨테이너들이 전담할 수밖에 없게 되었다. 서블릿과 EJB 기반 하에서 개발자들은 서블릿, EJB 스펙이 제공하는 데로 애플리케이션을 구현할 수밖에 없는 상황으로 바뀌게 되었다.


스프링 애플리케이션에서는 객체의 생성, 의존성 관리/사용/제거 등의 작업을 코드 대신 독립된 컨테이너가 담당한다. 

구성요소로는 ApplicatinContext, Java POJO class 집합, 설정 메타데이터가 있다. 

Spring BeanFactory Container - 빈팩토리는 순수 DI 작업에 집중하는 컨테이너 - org.springframework.beans.BeanFactory 인터페이스 구현
Spring ApplicationContext Container - 빈팩토리 기능에 다양한 엔터프라이즈 애플리케이션 개발 기능 추가 제공 - org.springframework.context.ApplicationContext 인터페이스 구현 - 스프링의 IoC 컨테이너는 일반적으로 애플리케이션 컨텍스트를 의미

라이브러리에 spring과 commons 로깅 라이브러리 추가

실습 진행을 위해 Dynamic web Project 생성 - st2spr. POJO 프로그램을 만들 것이므로 com.di.test1 패키지 생성.

1. 인터페이스 생성

package com.di.test1;

public interface Test {

public String result();

}

2. TestImpl1 - 인터페이스 구현 클래스1

package com.di.test1;

public class TestImpl1 implements Test{

 

private int su1;

private int su2;

 

//기본생성자

public TestImpl1() {

su1 = 10;

su2 = 20;

}

//오버라이딩된 생성자

public TestImpl1(int su1, int su2) {

this.su1 = su1;

this.su2 = su2;

}

 

@Override

public String result() {

String str = String.format("%d + %d = %d", su1, su2, su1+su2);

return str;

}

}

3. TestImpl2 - 인터페이스 구현 클래스2

package com.di.test1;

public class TestImpl2 implements Test{

 

private String name;

private int age;

 

public TestImpl2(){

name= "배수지";

age = 25;

}

 

public TestImpl2(String name, int age){

this.name= name;

this.age = age;

}

@Override

public String result() {

String str = "이름: " + name + ",나이: " + age;

return str;

}

}

4. TestService 클래스 생성

package com.di.test1;

public class TestService {

 

private Test test; //인터페이스

 

//기본생성자가 없고, 오버라이딩된 생성자

public TestService(Test test){

this.test = test; //TestImpl1 와 TestImpl2 모두 가능

}

 

public String getValue(){

return test.result();

//인터페이스로 오버라이딩한 메소드. 자신의 메소드를 호출

//여기서 test클래스가 TestImpl1 인지 TestImpl2 인지에 따라 result 메소드가 달라진다.

}

}

기본생성자를 이용한 객체 생성 & 의존성 주입

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation=

"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd

http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">

 

<!-- 객체 생성 후 끝 -->

  <bean id="testImpl1" class="com.di.test1.TestImpl1"/>

  <bean id="testImpl2" class="com.di.test1.TestImpl2"/>

 

  <!-- 객체 생성 후 데이터 전달 -->

  <bean id="testService" class="com.di.test1.TestService">

   <!-- 생성자의 매개변수 -->

   <constructor-arg>

   <ref bean="testImpl2"/>

   </constructor-arg>

  </bean>

</beans>

★ 참고
 
xmlns:p="http://www.springframework.org/schema/p"
네임 스페이스가 "http://www.springframework.org/schema/p 접두어를 사용하여 프로퍼티 값을 간편하게 설정 가능.
위 예에서는 접두어로 p를 설정 했는데 기본 데이터 타입을 <p:프로퍼티이름="값"> 형식으로 설정
빈 객체인 경우에는 <p:프로퍼티이름-ref="값">형식으로 설정
 

5. POJO 프로그램은 반드시 메인절이 필요 - TestMain 클래스 생성 

bean이 나오면 객체 생성이라고 이해하자.
BeanFactory 인터페이스
org.springframework.beans.factory.BeanFactory는 bean을 포함하고 관리하는 책임을 지는 Spring IoC 컨테이너의 실제 표현이다.
구현 클래스로는 XmlBeanFactory 클래스가 있다. 이 클래스는 XML개념으로 애플리케이션과 객체간의 풍부한 상호의존을 조합하는 객체를 표시하는 것을 허용한다. XmlBeanFactory 는 이 XML 설정 메타데이터를 가지고 완전히 설정된 시스템이나 애플리케이션을 생성한다.

Spring IoC 컨테이너를 인스턴스화 하는 방법(1)

String path = "com/di/test1/applicationContext.xml";

Resource resource = new FileSystemResource(path); BeanFactory factory = new XmlBeanFactory(resource);

 

Spring IoC 컨테이너를 인스턴스화 하는 방법(2)

String path = "com/di/test1/applicationContext.xml";

Resource src = new ClassPathResource(path); //경로설정

 

BeanFactory factory = new XmlBeanFactory(src); //스프링 컨테이너 생성

 

Resource 구현 클래스

① org.springframework.core.io.FileSystemResource 파일 시스템의 특정 파일로부터 정보를 읽어온다. ② org.springframework.core.io.InputStreamResource InputStream 으로 부터 정보를 읽어 온다. ③ org.springframework.core.io.ClassPathResource 클래스 패스에 있는 자원으로 부터 정보를 읽어 온다. ④ org.springframework.core.io.UrlResource 특정 URL로 부터 정보를 읽어 온다. ⑤ org.springframework.web.context.support.ServletContextResource 웹 어플리케이션의 루트 디렉토리를 기준으로 지정한 경로에 위치한 자원으로 부터 정보를 읽어온다. ⑥ org.springframework.web.portlet.context.PortletContextResource 포틀릿 어플리케이션 루트 디렉토리를 기준으로 지정한 경로에 위치한 자원으로 부터 정보를 읽어 온다.

 
클래스 패스에 위치한 config/applicationContext.xml 파일로부터 설정 정보를 읽어 ApplicationContext 인스턴스를 생성

package com.di.test1;

import org.springframework.beans.factory.BeanFactory;

import org.springframework.beans.factory.xml.XmlBeanFactory;

import org.springframework.core.io.ClassPathResource;

import org.springframework.core.io.Resource;

public class TestMain {

public static void main(String[] args) {

 

String path = "com/di/test1/applicationContext.xml";

//경로설정

Resource src = new ClassPathResource(path);

 

//스프링 컨테이너 생성(Spring IoC 컨테이너를 인스턴스화 하는 방법)

BeanFactory factory = new XmlBeanFactory(src);

//XmlBeanFactory클래스가 <bean>태그에 있는 객체를 생성하고 factory에 할당

 

//스프링 컨테이너에서 bean객체를 가져옴

TestService ob = (TestService)factory.getBean("testService");//object로 넘어오므로 downcast

System.out.println(ob.getValue());

 

//POJO 스프링으로 콘솔프로그램 생성 가능

Test ob;//여기에서 Test클래스의 객체명 ob가 변동되게 되면 밑에 4줄은 모두 오류발생

ob = new TestImpl1();

System.out.println(ob.result()); //TestImpl1의 result 메소드 실행

 

ob = new TestImpl2();

System.out.println(ob.result()); //TestImpl2의 result 메소드 실행

 

클래스에 작성된 내용을 그대로 가져다가 사용함. 그런데 다른 사람이 해당 클래스를 수정하게 된다면 다른 사람은 오류가 발생할 수밖에 없음. 이런 개념이 의존적. 스프링은 의존성을 낮추기 위해서 노력함.

 

TestImpl1 ob1 = new TestImpl1();

System.out.println(ob1.result()); //TestImpl1의 result 메소드 실행

 

TestImpl2 ob2 = new TestImpl2();

System.out.println(ob2.result()); //TestImpl2의 result 메소드 실행

}

}

제어의 역전 (DI)

내가 필요한 것은 testService클래스. 그런데 이 걸 사용하려면 필요조건이 존재. testImpl2가 있어야 한다.

이런 구조를 제어의 역전이라고 한다.

<bean> 태그의 class 속성은 생성할 빈 객체의 완전한 클래스 이름이며, id 속성은 스프링 컨테이너에서 생성된 객체를 구분하는 데 사용되는 식별 값이다. id 속성 대신 name 속성을 사용해도 된다. <bean> 태그를 이용하여 생성할 빈 객체에 대한 정보를 설정한 후에는 ApplicationContext나 BeanFactory를 이용하여 스프링 컨테이너를 생성한 뒤, 컨테이너로부터 빈 객체를 가져와 사용할 수 있다.

 

applicationContext.xml를 통한 객체 생성

<ref> 태그는 레퍼런스(reference)를 의미하며 <ref> 태그를 사용하는 대신 <constructor-arg> 태그의 ref 속성을 사용해도 된다.

 오버로딩된 생성자를 이용한 객체 생성 & 의존성 주입

<bean> 태그에 생성자와 관련된 정보(<constructor-arg> 태그)를 명시하지 않은 경우 기본 생성자를 이용하여 객체를 생성한다. 오버로딩된 생성자를 통해 객체를 생성하려 한다면 <constructor-arg> 태그를 작성하면 된다.

 

생성자가 전달받는 값이 int나 double과 같이 기본 데이터 타입이나 java.lang.String 타입이라면

<ref> 태그 대신에 <value> 태그를 사용하여 값을 지정할 수 있다.

 

applicationContext.xml


TestMain 실행 시 testImpl1 의 오버로딩된 생성자를 찾아가서 잘 실행되는 것을 볼 수 있다.

 

applicationContext.xml

TestMain 실행 시 testImpl2 의 오버로딩된 생성자를 찾아가서 잘 실행되는 것을 볼 수 있다.

 메소드를 사용한 의존성 주입

생성자 방식이 생성자를 사용해서 필요한 객체나 값을 전달 받는다면, 
프로퍼티 방식은 set() 형태의 메서드를 통해 필요한 객체의 값을 전달 받는다.

package com.di.test1;

public class TestService {

 

private Test test; //인터페이스

 

//기본생성자

public TestService(){

 

}

 

//property는 반환값. property가 기재된 내용은 메소드에 해당

//Setter를 통해 의존성을 주입할 경우에는 기본생성자로 객체를 생성한 뒤 실행되는 것이므로

//반드시 기본생성자를 생성해두어야 한다.

public void setTest(Test test){

this.test = test;

}

 

//DI(의존성 주입)

//기본생성자가 없고, 오버라이딩된 생성자. 초기화 작업

public TestService(Test test){

this.test = test; //TestImpl1 / TestImpl2

}

 

public String getValue(){

return test.result();

//인터페이스로 오버라이딩한 메소드. 자신의 메소드를 호출

//여기서 test클래스가 TestImpl1 인지 TestImpl2 인지에 따라 result 메소드가 달라진다

}

}

# Constructor는 생성자, Property는 메소드

Setter메소드를 활용하여 testImpl1 객체 생성

<property> 태그에서 <ref> 태그를 이용하여 다른 빈 객체를 프로퍼티 값으로 전달할 수 있으며, 기본 데이터 타입의 경우 <ref> 태그 대신에 <value> 태그를 사용 한다.

Setter메소드를 활용하여 testImpl2 객체 생성

Setter메소드를 활용하여  오버로딩된 생성자도 잘 진행됨

사용빈도 

property - method를 이용하여 대부분 의존성 주입 진행 > 기본생성자 > 오버로딩된 생성자