framework/spring IOC(제어의 역행)

[springIOC] 5. DI | 수동주입 | Constructor Injection & Setter Injection

jeri 2024. 7. 28. 09:16
반응형

01. DTO클래스

Student클래스
package xyz.itwill05.di;

//학생정보를 저장하기 위한 클래스 - VO 클래스(DTO 클래스)
public class Student {
	private int num;
	public String name;
	public String email;

	public Student() {
		System.out.println("### Student 클래스의 기본 생성자 호출 ###");
	}

	public Student(int num) {
		super();
		this.num = num;
		System.out.println("### Student 클래스의 매개변수(학번)가 선언된 생성자 호출 ###");
	}

	/*
	//Spring Bean Configuration File에서 매개변수가 있는 생성자로 객체를 만들기 위해 constructor-arg 엘리먼트 사용 시,
	//스프링에서는 전달값을 기본적으로 문자열(String 객체)로 전달하기 때문에
	//아래의 생성자를 만들 경우 num값을 설정하는 생성자는 dead code가 되어버리니 주의!!
	//즉, 오버로드로 메소드를 선언해도 문자열(String 객체)로 선언된 생성자만 계속 이용하게 됨!!
	public Student(String name) {
		super();
		this.name = name;
		System.out.println("### Student 클래스의 매개변수(이름)가 선언된 생성자 호출 ###");
	}
	*/

	public Student(int num, String name) {
		super();
		this.num = num;
		this.name = name;
		System.out.println("### Student 클래스의 매개변수(학번,이름)가 선언된 생성자 호출 ###");
	}

	public Student(int num, String name, String email) {
		super();
		this.num = num;
		this.name = name;
		this.email = email;
		System.out.println("### Student 클래스의 매개변수(학번,이름,이메일)가 선언된 생성자 호출 ###");
	}
    
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		System.out.println("*** Student 클래스의 setNum(int num) 메소드 호출 ***");
		this.num = num;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		System.out.println("*** Student 클래스의 setName(String name) 메소드 호출 ***");
		this.name = name;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		System.out.println("*** Student 클래스의 setEmail(String email) 메소드 호출 ***");
		this.email = email;
	}
	
	//객체에 저장된 필드값을 확인하기 위해 오버라이드 선언
	@Override
	public String toString() {
		return "학번= "+num+", 이름 = "+name+", 이메일 = "+email;
	}
}

02. DAO클래스 - 포함(의존) : 부

(부모) StudentDAO인터페이스
package xyz.itwill05.di;

import java.util.List;

//학생정보를 처리하는 DAO 클래스가 반드시 상속받아야 하는 인터페이스
// => 객체 간의 결합도를 낮추어 유지보수의 효율성 증가
public interface StudentDAO {
	int insertStudent(Student student);
	int updateStudent(Student student);
	int deleteStudent(int num);
	Student selectStudent(int num);
	List<Student> selectStudentList();
}
(자식) StudentJdbcDAO클래스
package xyz.itwill05.di;

import java.util.List;

//DAO 클래스 : 저장매체(File,DBMS, 등)에 대한 행 삽입, 변경, 삭제, 검색 기능을 제공하는 클래스
//=> 저장매체의 종류 또는 방법에 따라 DAO 클래스 변경 가능
//=> DAO 클래스가 변경되어도 DAO 클래스와 관계가 있는 클래스(Service 클래스)에 영향을 최소화
//하기 위해 인터페이스를 상속받아 작성 - 결합도를 낮춰 유지보수의 효율성 증가
public class StudentJdbcDAO implements StudentDAO{
	 public StudentJdbcDAO() {
		 System.out.println("### StudentJdbcDAO 클래스의 기본 생성자 호출 ###");
	}

	@Override
	public int insertStudent(Student student) {
		System.out.println("*** StudentJdbcDAO 클래스의 insertStudent(Student student) 메소드 호출 ***");
		return 0;
	}

	@Override
	public int updateStudent(Student student) {
		System.out.println("*** StudentJdbcDAO 클래스의 updateStudent(Student student) 메소드 호출 ***");
		return 0;
	}

	@Override
	public int deleteStudent(int num) {
		System.out.println("*** StudentJdbcDAO 클래스의 deleteStudent(int num) 메소드 호출 ***");
		return 0;
	}

	@Override
	public Student selectStudent(int num) {
		System.out.println("*** StudentJdbcDAO 클래스의 selectStudent(int num) 메소드 호출 ***");
		return null;
	}

	@Override
	public List<Student> selectStudentList() {
		System.out.println("*** StudentJdbcDAO 클래스의 selectStudentList() 메소드 호출 ***");
		return null;
	}
}
(자식) StudentMybtisDAO클래스
package xyz.itwill05.di;

import java.util.List;

public class StudentMybtisDAO implements StudentDAO{
	 public StudentMybtisDAO() {
		 System.out.println("### StudentMybtisDAO 클래스의 기본 생성자 호출 ###");
	}

	@Override
	public int insertStudent(Student student) {
		System.out.println("*** StudentMybtisDAO 클래스의 insertStudent(Student student) 메소드 호출 ***");
		return 0;
	}

	@Override
	public int updateStudent(Student student) {
		System.out.println("*** StudentMybtisDAO 클래스의 updateStudent(Student student) 메소드 호출 ***");
		return 0;
	}

	@Override
	public int deleteStudent(int num) {
		System.out.println("*** StudentMybtisDAO 클래스의 deleteStudent(int num) 메소드 호출 ***");
		return 0;
	}

	@Override
	public Student selectStudent(int num) {
		System.out.println("*** StudentMybtisDAO 클래스의 selectStudent(int num) 메소드 호출 ***");
		return null;
	}

	@Override
	public List<Student> selectStudentList() {
		System.out.println("*** StudentMybtisDAO 클래스의 selectStudentList() 메소드 호출 ***");
		return null;
	}
}

03. Service클래스 - 포함(의존) : 주

(부모) StudentService인터페이스

package xyz.itwill05.di;

import java.util.List;

//학생정보를 처리하는 Service 클래스가 반드시 상속받아야 되는 인터페이스
public interface StudentService {
	void addStudent(Student student);
	void modifyStudent(Student student);
	void removeStudent(int num);
	Student getStudent(int num);
	List<Student> getStudentList();
}
(자식) StudentServiceImpl클래스
package xyz.itwill05.di;

import java.util.List;

//Service 클래스 ( = 컴퍼넌트 )
// => 프로그램 실행에 필요한 데이터 처리 기능을 "모듈화"하여 제공하는 클래스 - 컴퍼넌트

// => Service 클래스의 메소드는 다수의 DAO 클래스의 메소드를 호출하여 데이터 처리 기능 제공 - 모듈화
// => DAO 클래스(보조-도구)는 Service 클래스(주-main)와 포함관계(의존관계)로 설정되도록 작성
// => Service 클래스가 변경되어도 Service 클래스와 관계가 있는 클래스(모델 클래스)에 영향을 최소화
//하기 위해 반드시 인터페이스 상속받아 작성 - 결합도를 낮춰 유지보수의 효율성 증가
public class StudentServiceImpl implements StudentService{

	//StudentJdbcDAO 클래스로 필드 선언
  	//StudentJdbcDAO 객체만을 저장하기 위한 필드
	// => 필드에 StudentJdbcDAO 객체를 저장해야만
  	//"포함"관계(Association)가 성립 - 의존관계는 아님
    
	// => StudentServiceImpl 클래스의 메소드에서 StudentJdbcDAO 객체의 메소드 호출 가능
	//문제점) DAO 클래스가 변경될 경우 Service 클래스의 필드 및 메소드도 변경해야함
	//=> 결합도가 높아 유지보수의 효율성 낮음(Service 클래스의 메소드도 계속 수정해야함!!!)
	//해결법) DAO 클래스가 반드시 상속받아야 되는 인터페이스로 필드 선언
	// => 필드에는 인터페이스를 상속받은 모든 DAO 클래스의 객체 저장 가능
	//private StudentJdbcDAO studentJdbcDAO;

	public StudentServiceImpl() {
		System.out.println("### StudentServiceImpl 클래스의 기본 생성자 호출 ###");
	}

	public StudentServiceImpl(StudentDAO studentDAO) {
		super();
		System.out.println("### StudentServiceImpl 클래스의 매개변수가 선언된 생성자 호출 ###");
	}

 	public StudentDAO getStudentDAO() {
		return studentDAO;
	}

	public void setStudentDAO(StudentDAO studentDAO) {
		System.out.println("*** StudentServiceImpl 클래스의 setStudentDAO(StudentDAO studentDAO) 메소드 호출 ***");
		this.studentDAO = studentDAO;
	}

	@Override
	public void addStudent(Student student) {
		System.out.println("*** StudentServiceImpl 클래스의 addStudent(Student student) 메소드 호출 ***");
		studentDAO.insertStudent(student);
	}

	@Override
	public void modifyStudent(Student student) {
		System.out.println("*** StudentServiceImpl 클래스의 modifyStudent(Student student) 메소드 호출 ***");
		studentDAO.updateStudent(student);
	}

	@Override
	public void removeStudent(int num) {
		System.out.println("*** StudentServiceImpl 클래스의 removeStudent(int num) 메소드 호출 ***");
		studentDAO.deleteStudent(num);
	}

	@Override
	public Student getStudent(int num) {
		System.out.println("*** StudentServiceImpl 클래스의 getStudent(int num) 메소드 호출 ***");
		studentDAO.selectStudent(num);
		return null;
	}

	@Override
	public List<Student> getStudentList() {
		System.out.println("*** StudentServiceImpl 클래스의 getStudentList() 메소드 호출 ***");
		studentDAO.selectStudentList();
		return null;
	}

}

04. 💖Spring Bean Configuration File

[05-1_di.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>"
	xsi:schemaLocation="<http://www.springframework.org/schema/beans> <http://www.springframework.org/schema/beans/spring-beans.xsd>">

 

[student1] 기본생성자로 기본값으로 [초기값 저장]

	<!-- Spring Bean으로 등록된 클래스의 기본 생성자를 이용하여 객체 생성 -->
	<!-- => 객체의 필드에는 기본값(숫자형:0, 논리형:false, 참조형:null) 자동 저장 -->
	<bean class="xyz.itwill05.di.Student" id="student1"/>

	<!-- Spring Bean Injection :
		스프링 컨테이너에 의해 Spring Bean Configuration File에
		등록된 클래스로 객체(Spring Bean) 생성 시
		필드에 원하는 값(Value Injection) 또는 객체(Dependecy Injection)를
		저장되도록 설정  -->
	<!-- => 생성자(Constructor Injection) 또는 Setter 메소드(Setter Injection)를 이용하여 값 또는 객체를 필드에 저장 -->

1) 값 주입

🍒[student2] Contructor Injection : 매개변수가 있는 생성자로 [초기값 저장]

	<!-- Contructor Injection -->
	<!-- Spring Bean으로 등록된 클래스의 매개변수가 선언된 생성자를 이용하여 객체 생성 -->
	<!-- => bean 엘리먼트의 하위 엘리먼트를 사용하여 생성자 매개변수에 값을 전달하여 필드값으로 저장 -->
	<!-- => Contructor Injection : 생성자를 이용하여 필드 초기화 작업 실행 -->
	<bean class="xyz.itwill05.di.Student" id="student2">
		<!-- constructor-arg : Spring Bean으로 등록된 클래스의 생성자 매개변수에 값(객체)을 전달하기 위한 엘리먼트 -->
		<!-- => 엘리먼트의 갯수만큼 매개변수가 선언된 생성자를 반드시 작성 -->
		<!-- value 속성 : 매개변수에 전달하기 위한 값을 속성값으로 설정 -->
		<!-- => spring Bean에 등록된 클래스가 객체로 생성될 때 객체 필드에 전달값 저장 -->
		<!-- => Value Injection(값 주입) : 객체의 필드에 값이 저장되도록 초기화 작업 실행 -->
		<!-- => 전달값은 기본적으로 문자열(String 객체)로 전달 - 매개변수의 자료형에 의해 자동 형변환 -->
		<!-- => 매개변수의 자료형에 의해 자동 형변환될 경우  NumberFormatException 발생 가능 -->
		<constructor-arg value="1000"/><!-- 전제조건 : 매개변수가 1개짜리 생성자 존재 / 문자열 1000이 전달되면 자동형변환되어 int로 저장될 것임 -->
		<!--<constructor-arg value="홍길동"/>--> <!-- NumberFormatException 발생 -->
	</bean>

🍒[student3] Contructor Injection : 매개변수가 있는 생성자로 순서 지정해 [초기값 저장]

	<!-- constructor-arg의 작성순서에 의해 매개변수에 값(객체)이 전달되어 객체 초기화 -->
	<!--
	<bean class="xyz.itwill05.di.Student" id="student3">
		<constructor-arg value="2000"/>
		<constructor-arg value="홍길동"/>
		<constructor-arg value="abc@itwill.xyz"/>
	</bean>
	 -->

	<bean class="xyz.itwill05.di.Student" id="student3">
		<!-- index 속성 : 생성자 매개변수에 값(객체)을 전달하기 위한 순서를 속성값으로 설정 -->
		<!-- => Index 속성값은 0부터 1씩 증가되는 정수값 사용 -->
		<constructor-arg value="홍길동" index="1"/>
		<constructor-arg value="abc@itwill.xyz" index="2"/>
		<constructor-arg value="2000" index="0"/>
	</bean>

🍒[student4] Setter Injection : Setter 메소드로 [초기값 저장]

	<!-- Setter Injection -->
	<!-- 클래스의 기본 생성자를 이용하여 객체 생성 - 객체 필드에는 기본값 저장 -->
	<!-- => 하위 엘리먼트를 사용하여 Setter 메소드를 호출해 필드값 변경 -  Setter Injection -->
	<bean class="xyz.itwill05.di.Student" id="student4">
		<!-- property : 객체의 Setter 메소드를 호출하여 필드값을 변경하는 엘리먼트 -->
		<!-- name 속성 : 필드값을 변경하기 위한 필드명을 속성값으로 설정 - 자동 완성 기능 사용 -->
		<!-- => name 속성값으로 설정된 필드에 대한 Setter 메소드를 호출하여 필드값 변경 -->
		<!-- -> 필드에 대한 Setter 메소드가 없거나 잘못 선언된 경우 예외 발생 -->
		<!-- value 속성 : 필드에 저장될 값을 속성값으로 설정 - 값 주입(Value Injection) -->
		<property name="num" value="3000"/>
		<property name="name" value="임꺽정"/>
		<property name="email" value="xyz@itwill.xyz"/>
	</bean>

🍒[student5] Contructor Injection & Setter Injection

	<!-- Constructor Injection & Setter Injection -->
	<!-- 생성자(Constructor Injection)와 Setter 메소드(Setter Injection)를 같이 사용하여 객체 초기화 작업 가능 - 값 주입(Value Injection) -->
	<bean class="xyz.itwill05.di.Student" id="student5">
		 <constructor-arg value="4000"/>
		 <constructor-arg value="전우치"/>
		 <property name="email" value="opq@itwill.xyz"/>
	</bean>

🍒[student6] properties파일 이용해 [초기값 저장]

	<!-- PropertyPlaceholderConfigurer 클래스 : Properties 파일을 제공받아 파일에 설정된 값을
	Spring Bean Configuration File에서 사용할 수 있도록 제공하는 클래스 : deprecated 되어있음-->
	<!-- => locations 필드에 Propertis 파일의 경로를 전달하여 저장 -->
	<!-- => Propertis 파일에 의해 제공되는 값은 Spring Bean Configuration File에서 ${이름}으로 사용 가능 -->
	<!--
	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations" value="xyz/itwill05/di/student.properties"/>
	</bean>
	 -->
	 <!-- Spring 5.2 이상에서는 PropertySourcesPlaceholderConfigurer 클래스를 사용하여
	 Properties 파일이 제공받아 Spring Bean Configuration File에서 사용할 수 있도록 변경 -->
	 <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
		<property name="locations" value="xyz/itwill05/di/student.properties"/>
	 </bean>

	<!-- Propertis 파일에 의해 제공되는 값을 사용하여 객체 필드 초기화 작업 -->
	<bean class="xyz.itwill05.di.Student" id="student6">
		<property name="num" value="${num}"/>
		<property name="name" value="${name}"/>
		<property name="email" value="${email}"/>
	</bean>

	<!-- => Propertis 파일을 사용하는 이유 - 실제로 Spring 컨테이너가 읽는 파일도 아니지만, 이렇게 쓰는 이유는???? -->
	<!-- => XML은 직관적이라 좋긴하지만 가독성이 떨어짐 (즉, 써야하는 엘리먼트와 속성들이 너무 많음)
			반면, Propertis 파일은 가독성이 좋고, ${이름}으로 XML 파일에서 사용하므로 환경 설정에 필요한 값들이 중복되지 않음
                 그리고 값 변경이 필요할 때 Propertis 파일만 변경하면 되므로 유지보수의 효율성 증가 -->

2) 객체 주입(의존성 주입)

🍑 객체 등록

	<!-- DI : 스프링은 XML 파일에서 직접 의존 관계를 설정해주며, 변경, 삭제 등 관리 가능함 -->

	<!-- StudentDAO 인터페이스를 상속받은 자식 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill05.di.StudentJdbcDAO" id="studentJdbcDAO"/>
	<bean class="xyz.itwill05.di.StudentMybtisDAO" id="studentMybtisDAO"/>

🍑 Constructor Injection : 의존성 주입 방법1

	<!-- StudentService 인터페이스를 상속받은 자식클래스를 Spring Bean으로 등록 -->
	<!-- => 클래스의 기본 생성자를 이용하여 객체 생성 - 객체 필드에는 기본값 저장 -->
	<!-- 문제점) StudentServiceImpl 클래스로 생성된 객체의 studnetDAO 필드에는 [null]이 저장되어
	StudentServiceImpl 클래스의 메소드에서 studnetDAO 필드로 StudentDAO 클래스의 메소드를
	호출하면 NullPointerExcetion 발생 - 🍑의존관계 미성립 -->
	<!-- 해결법) StudentServiceImpl 클래스의 객체 필드에 StudentDAO 인터페이스를 상속받은
	자식클래스의 객체가 저장되도록 설정 - 🍑의존관계 성립 -->
	<!-- <bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl"/> -->

	<!-- StudentServiceImpl 클래스의 매개변수가 선언된 생성자를 이용하여 객체 생성 -->
	<!-- => 생성자 매개변수에 StudentDAO 인터페이스를 상속받은 자식클래스의 객체를 전달하여 StudentDAO 필드에
	필드에 저장 => 🍑Constructor Injection -->
	<!-- constructor-arg 엘리먼트를 사용하여 StudentServiceImpl 클래스의 객체 필드에
	StudentDAO 인터페이스를 상속받은 자식클래스의 객체 저장 - 🍑의존관계 성립 -->
	<!-- ref 속성 : 스프링 컨테이너로 관리되는 Spring Bean의 식별자를 속성값으로 설정 - 자동완성 기능 이용 -->
	<!-- => 스프링 컨테이너로 관리되는 Spring Bean을 객체 필드에 저장 - 의존성 주입(DI : Dependency Injection) -->
	<!-- => 직접 값을 주입하는 것이 아닌 의존성 주입! -->
	<!--
	<bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl">
		<constructor-arg ref="studentJdbcDAO"/>
	</bean>
	 -->

🍑 Setter Injection : 의존성 주입 방법2

	<!-- StudentServiceImpl 클래스의 기본 생성자를 이용하여 객체를 생성-->
	<!--  => Setter 메소드를 호출하여 StudentDAO 인터페이스를 상속받은 자식클래스의 객체를
	필드에 저장 => 🍑Setter Injection -->
	<!-- property 엘리먼트를 사용하여 StudentDAO 클래스의 객체 필드에 StudentDAO
	 인터페이스를 상속받은 자식클래스의 객체 저장 - 🍑의존관계 성립 -->
	<!--
	<bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl">
		<property name="studentDAO" ref="studentJdbcDAO"/>
	</bean>
	 -->

🍑 의존관계 변경

	<!-- 기존에 사용하던 StudentJdbcDAO 클래스 대신 새롭게 작성한 StudentMybtisDAO 클래스로
	의존관계를 변경하고자 할 경우 ref 속성값만 변경 -->
	<!-- => 기존 클래스 대신 새로운 클래스로 변경해도 관계가 설정된 클래스를 변경하지 않아도
	Spring Bean Configuration File만 수정해도 관계 변경 - 유지보수의 효율성 증가 -->
	<bean class="xyz.itwill05.di.StudentServiceImpl" id="studentServiceImpl">
		<property name="studentDAO" ref="studentMybtisDAO"/>
	</bean>
</beans>

05. properties 파일

student.properties
#Student Properties
num=5000
name=\\\\uC77C\\\\uC9C0\\\\uB9E4
email=il@itwill.xyz

06. 요청처리

StudentApp.java
package xyz.itwill05.di;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class StudentApp {
	public static void main(String[] args) {
		System.out.println("============= Spring Container 초기화 전 =============");
		ApplicationContext context = new ClassPathXmlApplicationContext("05-1_di.xml");
		System.out.println("============= Spring Container 초기화 후 =============");

 

🍒값을 주입한 객체 호출

		Student student1 = context.getBean("student1",Student.class);
		 //참조변수 출력 시 Student 클래스의 toString() 메소드 자동 호출 - 객체의 필드값 확인
		System.out.println(student1);
		System.out.println("======================================================");
		Student student2 = context.getBean("student2",Student.class);
		System.out.println(student2);
		System.out.println("======================================================");
		Student student3 = context.getBean("student3",Student.class);
		System.out.println(student3);
		System.out.println("======================================================");
		Student student4 = context.getBean("student4",Student.class);
		System.out.println(student4);
		System.out.println("======================================================");
		Student student5 = context.getBean("student5",Student.class);
		System.out.println(student5);
		System.out.println("======================================================");
		Student student6 = context.getBean("student6",Student.class);
		System.out.println(student6);
		System.out.println("======================================================");

🍑의존성(객체)을 주입한 객체 호출

		//프로그램 실행에 필요한 데이타 처리 기능은 Service 클래스의 메소드를 호출하여 사용
		// => 스프링 컨테이너에게 Service 클래스의 객체를 제공받아 메소드 호출
		//StudentServiceImpl service = context.getBean("studentServiceImpl",StudentServiceImpl.class);

		//클래스로 참조변수를 생성하여 객체를 반환받아 저장하는 것보다는 인터페이스로 참조변수를
		//생성하여 객체를 저장하는 것이 유지보수의 효율성 증가
		// => 인터페이스로 반환받기 위한 객체의 형변환 가능
		StudentService service = context.getBean("studentServiceImpl",StudentService.class);

		service.addStudent(student1);
		service.modifyStudent(student1);
		service.removeStudent(1000);
		service.getStudent(1000);
		service.getStudentList();
		System.out.println("======================================================");

		((ClassPathXmlApplicationContext)context).close();
	}
}
반응형