Programming/Spring framework

스프링의 근원적 역할= 객체조립기, [최범균 스프링5 책] 2021-05-28

최동훈1 2021. 5. 27. 16:40

이전 포스트 들에서 DI와 의존주입하는 방법을 객체조립기에 대한 내용으로 설명했다. 

그런데 난 이런 의문이 들었다. 이 책은 스프링에 대한 본격적인 기능을 바로 설명해주지 않고, DI, 객체조립기 같은 것을 알려주지? 

이번 장을 공부하면서 바로 이유를 알게 되었다. 바로 스프링의 툴의 기능이 DI를 지원하는 객체 조립기 이기 때문이다.

즉, 앞선 포스트에서 자바 코드를 통해 구현한, Assembler클래스를 스프링의 힘을 빌리면 훨씬 더 표준적으로 구성할 수있어서, 유지보수성이 뛰어나다.

차이를 비교해 보기 바란다.

 

1. 일반 자바코드로 작성한 객체조립기. 

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;

public class Assembler {
	private MemberDao memberDao;
	private MemberRegisterService regSvc;
	private ChangePasswordService pwdSvc;

	public Assembler() {
		memberDao = new MemberDao();
		regSvc = new MemberRegisterService(memberDao);
		pwdSvc = new ChangePasswordService();
		pwdSvc.setMemberDao(memberDao);
	}

	public MemberDao getMemberDao() {
		return memberDao;
	}

	public MemberRegisterService getMemberRegisterService() {
		return regSvc;
	}

	public ChangePasswordService getChangePasswordService() {
		return pwdSvc;
	}

}

2.스프링을 빈 객체를 이용한. 객체조립기

@Configuration 클래스(빈객체들의 설정정보.) 실제로 조립하는 곳은 스프링 컨테이너임.

(AnnotationConfigApplicationContext 클래스 가 실제 스프링 컨테이너이자 조립기.)

 

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberRegisterService;

@Configuration
public class AppCtx {
	@Bean
	public MemberDao memberDao() {
		return new MemberDao();
	}
	
	@Bean
	public MemberRegisterService memberRegSvc() {
		return new MemberRegisterService(memberDao());
	}
	
	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		pwdSvc.setMemberDao(memberDao());
		return pwdSvc;
	}
	
}

 

 

또한, 각 @Bean 메서드 속 반환하는 클래스들의 의존관계는 같은 클래스 속 함수(memberDao())를 이용하여 의존주입을 한다는 것을 알 수 있다.

 

또한 위에 두 코드들은 각각 설정 클래스들일 뿐이고, 실제 객체를 생성하고 의존객체를 주입하는 것은 스프링 컨테이너이므로, 스프링 컨테이너를 만들어야 한다. 앞서 포스트에 설명했듯이 스프링의 핵심기능은 객체를 생성하고, 초기화 하는것(객체조립기) 역할을 하는데, 그럴러면 내가 생성할 객체들의 정보(@Configuration과 @Bean 객체를 통해 만들어 놓은 설정클래스 AppCtx)를 이용하여 스프링에 등록 하는 과정이 필요하다. 바로 스프링컨테이너에 등록하는 것이다. 지금 단계에서는 애너테이션을 이용한 등록만 배웠다. 앞 포스트에서 내가 설명한 AnnotationConfigurationContext 클래스.

 

이젠 직접 스프링컨테이너를 이용한 DI 방식과 일반 자바클래스로 만든 객체조립기의 차이를 한번 확인해 보자.

 

3.일반 Assembler클래스를 이용한 DI방식

package main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import assembler.Assembler;
import spring.ChangePasswordService;
import spring.DuplicateMemberException;
import spring.MemberNotFoundException;
import spring.MemberRegisterService;
import spring.RegisterRequest;
import spring.WrongidPasswordException;

public class MainForAssembler {
	public static void main(String[] args) throws IOException {
		BufferedReader reader = 
				new BufferedReader(new InputStreamReader(System.in));
		while (true) {
			System.out.println("명령어를 입력하세요:");
			String command = reader.readLine();
			if (command.equalsIgnoreCase("exit")) {
				System.out.println("종료합니다.");
				break;
			}
			if (command.startsWith("new ")) {
				processNewCommand(command.split(" "));
				continue;
			} else if (command.startsWith("change ")) {
				processChangeCommand(command.split(" "));
				continue;
			}
			printHelp();
		}
	}

	private static Assembler assembler = new Assembler();

	private static void processNewCommand(String[] arg) {
		if (arg.length != 5) {
			printHelp();
			return;
		}
		MemberRegisterService regSvc = assembler.getMemberRegisterService();
		RegisterRequest req = new RegisterRequest();
		req.setEmail(arg[1]);
		req.setName(arg[2]);
		req.setPassword(arg[3]);
		req.setConfirmPassword(arg[4]);
		
		if (!req.isPasswordEqualToConfirmPassword()) {
			System.out.println("암호와 확인이 일치하지 않습니다.\n");
			return;
		}
		try {
			regSvc.regist(req);
			System.out.println("등록했습니다.\n");
		} catch (DuplicateMemberException e) {
			System.out.println("이미 존재하는 이메일입니다.\n");
		}
	}

	private static void processChangeCommand(String[] arg) {
		if (arg.length != 4) {
			printHelp();
			return;
		}
		ChangePasswordService changePwdSvc = 
				assembler.getChangePasswordService();
		try {
			changePwdSvc.changePassword(arg[1], arg[2], arg[3]);
			System.out.println("암호를 변경했습니다.\n");
		} catch (MemberNotFoundException e) {
			System.out.println("존재하지 않는 이메일입니다.\n");
		} catch (WrongidPasswordException e) {
			System.out.println("이메일과 암호가 일치하지 않습니다.\n");
		}
	}

	private static void printHelp() {
		System.out.println();
		System.out.println("잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.");
		System.out.println("명령어 사용법:");
		System.out.println("new 이메일 이름 암호 암호확인");
		System.out.println("change 이메일 현재비번 변경비번");
		System.out.println();
	}

}

 

 2.스프링 컨테이너를 이용한 DI방식

 

package main;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import config.AppCtx;
import spring.ChangePasswordService;
import spring.DuplicateMemberException;
import spring.MemberNotFoundException;
import spring.MemberRegisterService;
import spring.RegisterRequest;
import spring.WrongidPasswordException;

public class MainForSpring {
private static ApplicationContext ctx = null;
	
	public static void main(String[] args) throws IOException {
		ctx = new AnnotationConfigApplicationContext(AppCtx.class);
		
		BufferedReader reader = 
				new BufferedReader(new InputStreamReader(System.in));
		while (true) {
			System.out.println("명령어를 입력하세요:");
			String command = reader.readLine();
			if (command.equalsIgnoreCase("exit")) {
				System.out.println("종료합니다.");
				break;
			}
			if (command.startsWith("new ")) {
				processNewCommand(command.split(" "));
				continue;
			} else if (command.startsWith("change ")) {
				processChangeCommand(command.split(" "));
				continue;
			} 
			printHelp();
		}
	}

	private static void processNewCommand(String[] arg) {
		if (arg.length != 5) {
			printHelp();
			return;
		}
		MemberRegisterService regSvc = 
				ctx.getBean("memberRegSvc", MemberRegisterService.class);
		RegisterRequest req = new RegisterRequest();
		req.setEmail(arg[1]);
		req.setName(arg[2]);
		req.setPassword(arg[3]);
		req.setConfirmPassword(arg[4]);
		
		if (!req.isPasswordEqualToConfirmPassword()) {
			System.out.println("암호와 확인이 일치하지 않습니다.\n");
			return;
		}
		try {
			regSvc.regist(req);
			System.out.println("등록했습니다.\n");
		} catch (DuplicateMemberException e) {
			System.out.println("이미 존재하는 이메일입니다.\n");
		}
	}

	private static void processChangeCommand(String[] arg) {
		if (arg.length != 4) {
			printHelp();
			return;
		}
		ChangePasswordService changePwdSvc = 
				ctx.getBean("changePwdSvc", ChangePasswordService.class);
		try {
			changePwdSvc.changePassword(arg[1], arg[2], arg[3]);
			System.out.println("암호를 변경했습니다.\n");
		} catch (MemberNotFoundException e) {
			System.out.println("존재하지 않는 이메일입니다.\n");
		} catch (WrongidPasswordException e) {
			System.out.println("이메일과 암호가 일치하지 않습니다.\n");
		}
	}

	private static void printHelp() {
		System.out.println();
		System.out.println("잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.");
		System.out.println("명령어 사용법:");
		System.out.println("new 이메일 이름 암호 암호확인");
		System.out.println("change 이메일 현재비번 변경비번");
		System.out.println();
	}

	
}

 

단순 자바코드로 구현한 Assembler 클래스를 이용한 DI방식과 스프링 컨테이너를 이용한 DI방식의 차이점은, 객체를 꺼낼때, getBean()메소드를 사용했다는 것 정도밖에 없다.

이제 쉽게 스프링 컨테이너의 기능과 필요성을 이해할수 있을꺼라 생각한다. 

 

*getBean()메서드의 첫번째 파라미터는 빈 객체의 식별자(빈객체 설정정보에 등록된 빈객체의 식별자 @Bean 메서드의 이름)이고, 두번째 파라미터는 검색할 빈 객체의 타입이다. 주의!!! 빈 객체의 식별자(@Bean 메서드의 이름)과 실제로 그 메서드가 반환하는 객체의 이름과는 '별개'임. 스프링 컨테이너는 빈 객체를 등록시킬때 실제 객체(클래스타입)의 이름으로 구별하여 등록하는 것이 아닌, @Bean메서드의 이름으로 등록한다. 

요약: 스프링이 객체를 구별하는 이름-->@Bean메서드의 이름. 

 

*생성자로 DI하는 방식은 스프링 자바설정에서는 생성자를 이용해서 의존 객체를 주입하기 위해 해당 설정을 담은 @Bean 메서드를 호출하였다.

ex) 

@Bean
	public MemberRegisterService memberRegSvc() {
		return new MemberRegisterService(memberDao());
	}

meberDao()[ 빈 객체 설정 메서드]를 생성자로 주입,

 

이 방법은 생성자에 전달할 의존객체가 두개 이상이어도 동일한 방식으로 주입 가능하다.

 

 

page 84 공부완료.

순공부/실습시간, 1시간 30분.