본문 바로가기

공부기록용

[ 스프링 프레임워크 ] 스프링 DI 지시서 작성_Constructor Injection, Setter Injection

Spring DI 지시서 작성하기(Spring Bean Configuration)

설정파일 보고 Spring이 Dependency를 Injection 해준다. 그 설정 파일을 Spring DI 지시서라고 하고,

Spring DI 지시서를 작성하는 법을 알아보겠다.

 

interface ExamConsole 를 구현한 구현체는 interface Exam 을 구현한 구현체를 부품으로 갖는다.결합하는 방식은 생성자 주입(Constructor Injection)과 Setter Injection(Setter 메서드를 통해서 Injection 하는 방식)이 있다. Setter Injection이 일반적인 방식이고, 생성자 주입으로 결합하는 것보다 느슨한 결합이다.

 

- Dependency를 직접 만들고 조립해보기(스프링 개입x)

 

데이터를 갖는 entity로서의 클래스를 만들고,

entity를 활용해서 출력을 담당하는 클래스를 만들 것

package spring.di;

public class Program{
	public static void main(String[] args) {
    
    	// 아래 객체가 생성되는 경우는
        // 1. NewlecExam클래스가 Exam클래스를 상속한 경우,
        // NewlecExam이 자식클래스, Exam클래스가 부모클래스
        // → public class NewlecExam extends Exam {...}
       
        
        // 2. NewlecExam클래스가 Exam 인터페이스를 구현한 경우
        // → public class NewlecExam implements Exam {...}
    	
        
        // 부모타입의 참조변수는 자식타입의 참조변수를 참조할 수 있다. 가리킬 수 있다.
        // 사용할 수 있는 멤버갯수가 달라짐
        Exam exam = new NewlecExam();
        
        // Exam(인터페이스를 구현한)객체를 출력할 건데,
        // Inline으로 출력하는 객체와 Grid로 출력하는 객체로 출력해보기
        // 하나의 참조변수로 메서드를 호출, 소스코드의 수정은 없이, 객체만 바뀔뿐
        // ExamConsole은 인터페이스로, 그리고 InlineExamConsole과 GridExamConsole이
        // ExamConsole을 구현한 구현체라면 소스코드의 수정없이 객체만 바뀐다면 실행이 가능하다.
        
        // Constructor Injection(생성자 주입)
        // ExamConsole console = new InlineExamConsole(exam);  
        
        
        // Setter Injection, setter를 통한 dependency 조립
        ExamConsole console = new GridExamConsole();

        console.setExam(exam);
        
        console.print();
        
        // 여기서 Spring이 어떤 역할을 하는지 살펴보기
        ExamConsole console = ?; // 1. console 참조변수가 참조할 객체를 생성해주고,
        
        console.setExam(exam); // 2. 생성한 객체에 필요한 부품객체를 조립해준다.
        
        생성할 객체를 생성해주고, 필요한 부품을 조립해주는 이 과정이 어떻게 일어나는지
        그 과정을 살펴보는 것이 중요!
    }
}

-------------------------------------------------

package spring.di.entity;

public interface Exam {
	int total();
    float average();
}

-------------------------------------------------

package spring.di.entity;

public class NewlecExam implements Exam {
    private int kor;
    private int eng;
    private int math;
    private int com;

    public NewlecExam() {}
    
    // 스프링 DI 지시서에 NewlecExam객체를 생성하고
    // 생성된 객체에 값을 설정하는 property를 작성하려면
    // setter 메서드가 작성되어 있어야 한다.
    public void setKor(int kor) {
        this.kor = kor;
    }
    
    // 생성자 주입을 스프링 DI 지시서에 작성하기 위한
    // 매개 변수가 있는 생성자를 작성
    public NewlecExam(int kor, int eng, int math, int com) {
        this.kor = kor;
        this.eng = eng;
        this.math = math;
        this.com = com;
    }

    public void setEng(int eng) {
        this.eng = eng;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public void setCom(int com) {
        this.com = com;
    }

	// 인터페이스 Exam을 implements 했으므로
    // 인터페이스 Exam이 갖는 추상 메서드를 오버라이드 하여 구현해야 함
    @Override
	public int total() {
    	return kor+eng+math+com;    
    }
    @Override
    float average() {
    	return total()/4.0f;
    }
}


-------------------------------------------------
package spring.di.ui;
import spring.di.entity.Exam;

public interface ExamConsole {
	void print();
    
    
    void setExam(Exam exam);
}

-------------------------------------------------

package spring.di.ui;

public class InlineExamConsole implements ExamConsole {

	private Exam exam;
    
    public InlineExamConsole() {}
    
    @Override
    public setExam(Exam exam) {
    	this.exam = exam;
    }
    
    public InlineExamConsole(Exam exam) {
    	this.exam = exam;
    }
    
    @Override
    public void print() {
    	System.out.printf("total: %d, average: %f\n", exam.total(), exam.average());
    }
}

-------------------------------------------------

package spring.di.ui;

public class GridExamConsole implements ExamConsole {

	private Exam exam;
    
    public InlineExamConsole() {}
    
    @Override
    public setExam(Exam exam) {
    	this.exam = exam;
    }
    
    public GridExamConsole(Exam exam) {
    	this.exam = exam;
    }
    
    @Override
    public void print() {
    	System.out.printf("total: %d\n", exam.total());
        System.out.printf("average: %f\n", exam.average());
    }
}

-------------------------------------------------

 

main() 메서드에서 출력을 호출하는 소스코드의 변화는 없으나

객체의 변화는 있어야 한다. 객체의 변화, 상황과 시간에 따라 필요한 객체를 생성해서 갈아끼워주는 역할을 스프링이 해주는 것이다.

스프링은 어떻게 객체를 갈아끼울까? 그건 외부설정에 기반한다. 

 

ExamConsole console = new GridExamConsole(exam); → new GridExampleConsole(exam); 또는 new InlineExamConsole(exam);  외부설정에 기인하여 객체를 생성해서 필요할 때마다 갈아끼워주는 것을 도와주는 게 스프링이고,

이 개념을 DI라고 한다.

 

console.print(); // 호출하는 소스코드의 변화는 없다.

 

-----------------------------------------------------------------------------------

 

스프링 지시서(Spring DI) 작성하기

스프링에게 지시할 내용

 

- 객체 생성 및 객체간 결합을 지시: 생성해야할 객체를 만들고, 만든 객체에 필요한 부품을 주입해라

예를 들어 위 코드를 보면

 

// 1. 출력할 객체를 만들고,

ExamConsole console = new GridExamConsole();

 

// 2. 출력하는 기능만 있는 객체에 어떤 걸 출력해야 하는지

// 출력내용을 가진 객체를 생성하고, 생성된 객체를 부품으로 주입

Exam exam = new NewlecExam();

console.setExam(exam);

 

spring di 지시서로 setting.xml 파일을 만듦

- 빈이라는 태그를 이용해서 스프링에게 생성할 객체를 알려준다.

 

```

<!-- Exam exam = new NewlecExam(); -->

<!--

소스코드를 작성하는 파일에서 exam 이라는 이름으로 꺼내서 사용할 수 있다.

exam은 spring.di.entity폴더에 존재하는 NewlecExam 클래스에 기반해 생성한 인스턴스다.

-->

<bean id="exam" class="spring.di.entity.NewlecExam" />>

 

<!-- ExamConsole console = new GridExamConsole(); -->

<bean id="console" class="spring.di.ui.GridExamConsole" /.>

 

<!-- ExamConsole console = new GridExamConsole(); -->

<bean id="console" class="spring.di.ui.GridExamConsole".>

    <!-- console.setExam(exam)

 

 

    -->

    name의 값에는 setter메서드명을, setExam 에서 set을 지우고, E를 소문자로 바꾸어서 exam이 된다.

    setExam은 exam으로, setter에 설정할 수 있는 객체의 이름(bean id)을 value에 작성한다.

    또는 

    <!-- value에 대입이 허용되는 값은 타입이 value이면 value에 타입이 참조형이면? ref에 대입한다? -->

    <!-- property 태그를 사용하려면 property 태그가 속한 객체가 setter함수를 갖고 있어야 한다.

      value나 ref에 대입되는 값은 setter함수의 매개변수에 사용되는 값이나 참조형 값이 해당한다.

      -->

    <property name="exam" ref="exam" />

</bean>

 

```

위의 지시서를 넘길 주체도 만들어야 한다(지시사항을 전달할 객체를 만들어야 함)

 

스프링에서 DI를 읽고 Bean(객체)들을 생성하고 조립하는 주체(객체)를 만드는 문법:

ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");

 

스프링에서 DI를 읽고 Bean들을 만들고, 조립하는 주체는 ApplicationContext다.

그리고 ApplicationContext는 인터페이스이기 때문에 이를 구현한 구현체를 통해 ApplicationContext의 기능을 사용할 수 있다.

ApplicationContext를 구현한 구현체의 종류가 몇 개 있다.

ApplicationContext를 구현한 구현체가 생성될 때 DI지시서를 생성자의 매개변수에 전달하는데,

DI 지시서의 위치를 어떻게 알려주느냐에 따라 사용할 구현체가 달라진다.

어플리케이션의 루트로부터 경로를 지정하고 싶을 때는 ClassPathXmlApplicationContext 구현체를 사용한다.

 

ClassPathXmlApplicationContext 생성자의 매개 값으로 전달하는 DI의 지시서는 어플리케이션의 루트(src가 루트)에  있다. 실행할 때 실행되는 지점에 두었다. 그 경로에 config.xml이 있을 것이다. 라고 알려주는 것.

웹 어플리케이션을 개발할 때 어플리케이션의 루트는 src고,

스프링 DI 지시서를 읽고, 객체를 생성하고, 객체간의 조립을 해주는 ApplicationContext를 생성할 때

ApplicationContext를 구현한 ClassPathXmlApplicationContext를 생성하면서 스프링 DI 지시서를 매개 값으로 넘겨준다고 할 때

스프링 DI 지시서의 위치는 src(어플리케이션 루트)를 기준으로 풀 경로를 적어준다.

 

예를 들어

 

```

src(어플리케이션 루트)

┗spring(패키지)

         ┗di(패키지)

             ┗Program.java(프로그램의 시작점이 존재하는 자바파일 → main())

             ┗setting.xml(스프링 DI 지시서)

 

```

 

이런 구조일 경우

 

 

```

 

프로그램의 시작점이 존재하는 Program.java 의 public static void main(String[] args) {

    // 이곳에 스프링 DI 지시서를 읽고, 객체를 생성하고, 객체간 결합을 해주는 ApplicationContext를 이 곳에서 만들기

   // 어플리케이션 루트로부터 스프링 DI 지시서의 위치를 매개 값으로 전달할 것이니 ClassPathXmlApplication 구현체를 생성

   

   // ApplicationContext를 사용하려면 springframework의 context 라이브러리를 다운받아야 함

   ApplicationContext context = new ClassPathXmlApplicationContext("spring/di/setting.xml");

   

  // 개발자가 작성한 스프링 DI 지시서를 읽고, 지시서대로 객체를 생성하고, 생성된 객체를 담고있는 그릇이 IoC 컨테이너다. 

 

  // 1. bean id를 사용해서 컨테이너에게 원하는 객체를 얻으려면 다음과 같이 작성하면 된다.

  // ExamConsole console = (ExamConsole) context.getBean("console");

 

  // 2. 자료형이름으로 객체 얻기

  // ExamConsole이 인터페이스라면 인터페이스 ExamConsole에 참조될 수 있는 객체를 찾아서 해당 객체를 얻을 수 있음

  // ExamConsole console = context.getBean(ExamConsole.class);

}

 

```

 

스프링 DI 지시서에 객체를 생성하고, 생성한 객체에 타입이(ref="") 아닌 값(value="")을 생성하는 예시

<beans>
	<bean id="exam" class="spring.di.entity.NewlecExam">
    	<!--1. 한 줄에 메서드명과 메서드 호출로 설정할 값을 작성한다.-->
    	<!--setter 메서드를 property로 설정할 수 있음-->
        <property name="kor" value="20" />
        
        
        <!--2. name과 value를 분리해서 작성
        
        <property name="kor">
        	<value>20</value>
        </property>
        
        -->
    </bean>
</beans>

 

생성자를 통해서 값을 설정하는 DI 지시서 작성하기

<bean id="exam" class="spring.di.entity.NewlecExam">
    
    
    <!--
	이렇게 작성하면 매개변수가 있는 생성자의 매개 값 순서대로 아래 값들이 설정된다.
	그런데 어떤 게 kor점수인지 math점수인지 인지하기? 어려워 순간 버그가 생길 수도 있어
    인덱스로 순서를 지정해줄 수 있다
    -->
    
    
    <!-- 1. index 키워드를 사용해서 매개변수의 순서를 지정하는 방법이 있고,
	
    <constructor-arg index="0" value="10">
    <constructor-arg index="1" value="20">
    <constructor-arg index="2" value="30">
    <constructor-arg index="3" value="40">
    
    -->
    
    <!-- 2. name 키워드를 사용하여 이 값이 어떤 필드인지를 표시할 수 있다. -->
    <constructor-arg name="kor" value="10" />
    <constructor-arg name="eng" value="20" />
    <constructor-arg name="math" value="30" />
    <constructor-arg name="com" value="40" />
</bean>

 

생성자 주입을 통해 필드의 값을 설정하는 지시서 작성시 index 속성을? 사용하면 index를 통해 순서변경도 가능하다.

index 키워드를 사용하면 int값이 쓰여져 있으므로 가독성이 좋지 않아 name 속성을 사용하여

이 값이 어떤 값인지를 명확하게 나타내어 작성할 수도 있다.

매개변수의 개수가 동일하고, 매개변수의 이름도 동일한데 자료형이 다를 경우 위처럼 작성하면 애매하므로

타입도 지정할 수 있는 옵션을 사용한다.

 

참고: [뉴렉처] 스프링 프레임워크 강의