회원 가입 프로젝트 #3

정의정·2023년 6월 16일
0

Spring

목록 보기
7/12
post-thumbnail
post-custom-banner

💡 회원 가입 프로젝트 #3

지난 2편에서는 Assembler와 이를 사용하는 Main 클래스를 작성해봤다.
이번에는 객체 조립에 Assembler 대신 스프링을 사용하도록 하겠다.

📌 순서

  1. 스프링 설정 정보 작성하기
  2. MainForSpring 클래스 작성하기
  3. DI 방식 살펴보기
  4. 프로젝트 정리하기

🌟 설정 정보 작성하기

스프링을 사용하기 위해 어떤 객체를 생성하고, 의존을 어떻게 주입할지 정의한 설정 정보를 작성해야 한다.

1. 설정 정보

설정 코드는 config 패키지에 생성하도록 한다.

AppXtx.java✏️

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;
	}
}

@Configuration 애노테이션은 스프링 설정 클래스를 의미한다.
@Bean 애노테이션은 해당 메서드가 생성한 객체를 스프링 빈이라고 설정한다. 이 때 메서드의 이름을 빈 객체의 이름으로 사용한다.

생성자 주입✔️

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

위 코드에서 MemberRegisterService 생성자를 호출하는 부분을 보면, memberDao()가 생성한 객체를 생성자를 통해 주입하는 것을 확인할 수 있다.

세터 주입✔️

	@Bean
	public ChangePasswordService changePwdSvc() {
		ChangePasswordService pwdSvc = new ChangePasswordService();
		pwdSvc.setMemberDao(memberDao());
		return pwdSvc;
	}

위 코드에서는 setMemberDao() 메서드를 통해 MemberDao 객체를 주입하고 있다. 이를 세터를 이용한 DI라고 한다.


🌟 MainForSpring 클래스 작성하기

지금껏 작성한 설정 클래스를 이용해 스프링 컨테이너를 생성해야 한다.

1. MainForSpring

스프링 컨테이너를 사용하여 빈 객체를 구하고 사용할 수 있다.
MainForSpring 클래스를 작성하자.

MainForSpring.java✏️

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.print("종료합니다.");
				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();
	}
}

2편에서 작성했던 MainForAssembler 클래스와 거의 같은 코드다. 다른 점은 Assembler 클래스 대신 스프링 컨테이너(ApplicationContext)를 사용한 점이다.
달라진 코드를 위주로 살펴보자.

ctx 초기화✔️

	private static ApplicationContext ctx = null;

ApplicationContext를 사용하기 위해 ctx를 초기화한 부분이다.

		ctx = new AnnotationConfigApplicationContext(AppCtx.class);

ctx 객체에 스프링 설정 클래스인 AppCtx를 사용해 생성한 스프링 컨테이너를 담는다.

getBean()✔️

		MemberRegisterService regSvc = ctx.getBean("memberRegSvc", MemberRegisterService.class);
		ChangePasswordService changePwdSvc = ctx.getBean("changePwdSvc", ChangePasswordService.class);

이 두 코드는 Assembler 대신 스프링 컨테이너의 getBean() 메서드를 통해 빈 객체를 가져오고 있다.


🌟 DI 방식 살펴보기

앞서 스프링 설정 클래스를 작성하며 설명했듯 의존 주입에는 생성자를 이용한 방식과 세터 메서드를 이용한 방식이 있다. 간단한 기능을 추가하며 더 살펴보도록 하자.

1. DI: 생성자 방식

생성자 방식을 이용한 의존 주입을 살펴보기 위해 회원 정보를 출력하는 기능을 추가할 것이다.

MemberDao.java 에 코드 추가✏️

	public Collection<Member> selectAll() {
		return map.values();
	}

담아둔 회원 정보를 반환하는 selectAll() 메서드를 MemberDao 클래스에 추가한다.

MemberPrinter.java✏️

package spring;

public class MemberPrinter {

	public void print(Member member) {
		System.out.printf("회원 정보: 아이디=%d, 이메일=%s, 이름=%s, 등록일=%tF\n", member.getId(), member.getEmail(),
				member.getName(), member.getRegisterDateTime());
	}
}

파라미터로 받은 회원 정보를 출력하는 print() 메서드를 담은 클래스다.

MemberListPrinter.java✏️

모든 회원들의 정보를 출력하기 위해 필요한 클래스를 작성한다.

package spring;

import java.util.Collection;

public class MemberListPrinter {

	private MemberDao memberDao;
	private MemberPrinter printer;

	public MemberListPrinter(MemberDao memberDao, MemberPrinter printer) {
		this.memberDao = memberDao;
		this.printer = printer;
	}

	public void printAll() {
		Collection<Member> members = memberDao.selectAll();
		members.forEach(m -> printer.print(m));
	}
}

이 클래스는 생성자의 파라미터로 두 객체를 전달받는다.
printAll() 메서드는 memberDao의 selectAll() 메서드를 통해 모든 회원들의 정보를 불러와서 전달받은 MemberPrinter 객체를 통해 하나씩 print() 메서드로 출력한다.

AppCtx.java 에 설정 추가✏️

이제 AppCtx 클래스에도 두 인자를 받는 생성자를 사용하는 설정을 추가한다.

	@Bean
	public MemberPrinter memberPrinter() {
		return new MemberPrinter();
	}

	@Bean
	public MemberListPrinter listPrinter() {
		return new MemberListPrinter(memberDao(), memberPrinter());
	}

MainForSpring.java 에 코드 추가✏️

마지막으로 MainForSpring 클래스에 이 기능을 사용하기 위한 코드를 추가하자.

		while (true) {
			System.out.println("명령어를 입력하세요: ");
			String command = reader.readLine();
			if (command.equalsIgnoreCase("exit")) {
				System.out.print("종료합니다.");
				break;
			}
			if (command.startsWith("new ")) {
				processNewCommand(command.split(" "));
				continue;
			} else if (command.startsWith("change ")) {
				processChangeCommand(command.split(" "));
				continue;
			} else if (command.startsWith("list")) {
				processListCommand();
				continue;
			}

list 명령어를 받았을 때 processListCommand() 메서드를 실행하도록 한다.

	private static void processListCommand() {
		MemberListPrinter listPrinter = ctx.getBean("listPrinter", MemberListPrinter.class);
		listPrinter.printAll();
	}

processListCommand() 메서드는 listPrinter 빈 객체를 불러와 printAll() 메서드를 실행한다.

2. MainForSpring 실행

MainForSpring을 실행하여 새로 추가한 기능을 사용해보자.

명령어를 입력하세요: 
new abc@def.com abc abc123 abc123
등록했습니다.

명령어를 입력하세요: 
new def@def.com def def123 def123
등록했습니다.

명령어를 입력하세요: 
list
회원 정보: 아이디=2, 이메일=def@def.com, 이름=def, 등록일=2023-06-16
회원 정보: 아이디=1, 이메일=abc@def.com, 이름=abc, 등록일=2023-06-16
명령어를 입력하세요: 

정상적으로 작동하는 것을 확인할 수 있다.

3. DI: 세터 메서드 방식

세터 메서드를 이용한 의존 주입을 살펴보기 위해 지정한 회원 데이터를 콘솔에 출력하는 기능을 추가할 것이다.

MemberInfoPrinter.java✏️

package spring;

public class MemberInfoPrinter {

	private MemberDao memberDao;
	private MemberPrinter printer;

	public void printMemberInfo(String email) {
		Member member = memberDao.selectByEmail(email);
		if (member == null) {
			System.out.println("데이터 없음\n");
			return;
		}
		printer.print(member);
		System.out.println();
	}

	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

	public void setPrinter(MemberPrinter printer) {
		this.printer = printer;
	}
}

이 클래스는 지정한 이메일을 갖는 Member를 찾아서 정보를 콘솔에 출력한다.

AppCtx.java에 설정 추가✏️

AppCtx에 세터 메서드를 이용해 의존을 주입하는 설정 코드를 추가한다.

	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		infoPrinter.setMemberDao(memberDao());
		infoPrinter.setPrinter(memberPrinter());
		return infoPrinter;
	}

infoPrinter 빈이 세터 메서드를 이용해 memberDao 빈과 memberPrinter 빈을 주입하고 있다.

MainForSpring.java에 코드 추가✏️

		while (true) {
			System.out.println("명령어를 입력하세요: ");
			String command = reader.readLine();
			if (command.equalsIgnoreCase("exit")) {
				System.out.print("종료합니다.");
				break;
			}
			if (command.startsWith("new ")) {
				processNewCommand(command.split(" "));
				continue;
			} else if (command.startsWith("change ")) {
				processChangeCommand(command.split(" "));
				continue;
			} else if (command.startsWith("list")) {
				processListCommand();
				continue;
			} else if (command.startsWith("info ")) {
				processInfoCommand(command.split(" "));
				continue;
			}
			printHelp();
		}

info email 명령어를 받았을 때 processListCommand() 메서드를 실행하도록 한다.

	private static void processInfoCommand(String[] arg) {
		if (arg.length != 2) {
			printHelp();
			return;
		}
		MemberInfoPrinter infoPrinter = ctx.getBean("infoPrinter", MemberInfoPrinter.class);
		infoPrinter.printMemberInfo(arg[1]);
	}

processInfoCommand() 클래스는 콘솔로 입력받은 email을 인자로 printMemberInfo() 메서드를 실행한다.

4. MainForSpring 실행

MainForSpring을 실행하여 새로 추가한 기능을 사용해보자.

명령어를 입력하세요: 
new abc@def.com abc abc123 abc123
등록했습니다.

명령어를 입력하세요: 
new def@def.com def def123 def123
등록했습니다.

명령어를 입력하세요: 
info abc@def.com
회원 정보: 아이디=1, 이메일=abc@def.com, 이름=abc, 등록일=2023-06-16

명령어를 입력하세요: 

정상적으로 작동하는 것을 확인할 수 있다.


🌟 프로젝트 정리하기

회원 가입 프로젝트는 이렇게 3편으로 마무리 짓도록 하겠다. 하지만, 앞으로도 이 프로젝트의 코드를 이용해 여러 이론과 실습을 공부하도록 할 것이기 때문에 질리게 보게 될 것 같다.


🎀 줄이며

이번 포스트에서도 추가적으로 DI의 두 방식을 다뤘듯이,

기본 데이터 타입 값 설정, 싱글톤 객체에 대한 설명, 여러 개의 설정 파일 사용법, 의존 자동 주입, DB 연동 등

앞으로도 다룰 것들이 많으니 차차 공부하도록 하겠다.

📖 교재 및 출처

  • 초보 웹 개발자를 위한 스프링5 프로그래밍 입문 | 최범균님 저
profile
배움 기록
post-custom-banner

0개의 댓글