일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- flask
- 언리얼
- JWT
- Enhanced Input System
- Bootstrap4
- 언리얼프로그래머
- Unseen
- 으
- 게임개발
- EnhancedInput
- 스터디
- 정글사관학교
- VUE
- Jinja2
- 스마일게이트
- 파이썬서버
- R
- 데이터베이스
- 프린세스메이커
- 언리얼뮤지컬
- 프메
- 알고풀자
- Ajax
- 레베카
- 마인크래프트뮤지컬
- node
- 카렌
- 디자드
- Express
- 미니프로젝트
- Today
- Total
Showing
[자바] DI(Dependency Injection) 의존 주입 본문
* 최범균님의 "스프링5 프로그래밍 입문" 책을 정리하기 위해 작성된 내용입니다.
1. 의존 = 객체 간의 의존
의존은 변경에 의해 영향을 받는 관계를 의미한다. 에를 들어 아래 코드에서 MemberDao의 insert()의 이름을 변경하면 이 메서드를 사용하는 MemberRegisterService 클래스의 소스 코드도 함께 변경된다. 이렇게 변경에 따른 영향이 전파되는 관계를 '의존'한다고 표현한다.
예제1
package ems.member.service;
import java.time.LocalDataTime;
import ems.member.Member;
import ems.member.dao.MemberDao;
public class MemberRegisterService {
private MemberDao memberDao = new MemberDao(); // 클래스 내부에서 직접 의존 객체를 생성
// 그러나, 유지보수 관점에서 문제점 유발
public void register(RegisterRequest req) {
// 이메일로 회원 데이터 조회
Member member = MemberDao.selectByEmail(req.getEmail());
if(member != null){
//같은 이메일을 가진 회원이 이미 존재하면 익셉셥 발생
throw new DuplicateMemberException("dup email"+req.getEmail());
}
//같은 이메일을 가진 회원이 존재하지 않으면 DB에 삽입
Member newMember = new Member(
req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
)
MemberDao.insert(newMember);
}
}
위 코드에서 MemberRegisterService 클래스가 DB 처리를 위해 MemberDao 클래스의 메서드를 사용한다. 회원 데이터가 존재하는지 확인 하기 위해 SelectByEmail()메서드를, 회원 데이터를 DB에 삽입하기 위해 insert()메서드를 실행한다.
2. DI를 통한 의존 처리
DI(의존 주입)는 의존하는 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식을 사용한다.
아래 예시에서는 생성자를 통해 MemberRegisterService가 의존(Dependency)하고 있는 MemberDao 객체를 주입 받고 있다.
의존 객체를 직접 구하지 않고 생성자를 통해서 전달 받기에 DI(의존 주입) 패턴을 따르고 있다고 볼 수 있다.
package ems.member.service;
import java.time.LocalDataTime;
import ems.member.Member;
import ems.member.dao.MemberDao;
public class MemberRegisterService {
private MemberDao memberDao;
public MemberRegisterService(MemberDao memberDao){ //직접 의존 객체를 생성했던 것과 달리,
this.memberDao = memberDao; //생성자를 통해서 의존 객체를 전달받는다.
}
public void register(RegisterRequest req) {
// 이메일로 회원 데이터 조회
Member member = MemberDao.selectByEmail(req.getEmail());
if(member != null){
//같은 이메일을 가진 회원이 이미 존재하면 익셉셥 발생
throw new DuplicateMemberException("dup email"+req.getEmail());
}
//같은 이메일을 가진 회원이 존재하지 않으면 DB에 삽입
Member newMember = new Member(
req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
)
MemberDao.insert(newMember);
}
}
이렇게 생성자를 통해서 의존하는 객체를 주입하는 이유는 변경의 유연함을 위해서이다.
MemberDao dao = new MemberDao();
MemberRegisterService svc = new MemberRegisterService(dao);
package ems.member.service;
import ems.member.Student;
import ems.member.dao.StudentDao;
public class StudentRegisterService {
private StudentDao studentDao;
public StudentRegisterService(StudentDao studentDao) {
this.studentDao = studentDao;
}
public void register(Student student) {
String sNum = student.getsNum();
if(verify(student.getsNum())) {
studentDao.insert(student);
} else {
System.out.println("The student has already registered.");
}
}
public boolean verify(String sNum){
Student student = studentDao.select(sNum);
return student == null ? true : false;
}
}
MemberDao 클래스는 회원 데이터를 데이터베이스에 저장한다고 가정해보자. 이 상태에서 회원 데이터의 빠른 조회를 위한 캐시를 적용해야 하는 상황이 발생했다. 그래서 MemberDao를 상속받은 CachedMemberDao 클래스를 만들 수 있다.
public class CachedMemberDao extends MemberDao {
}
아래와 같이 생성자를 통해서 의존 객체를 주입받도록 구했했다고 쳤을 때,
public class MemberRegisterService {
private MemberDao memberDao;
public MemberRegisterService(MemberDao memberDao) {
this.memberDao = memberDao;
}
}
public class ChangePasswordService {
private MemberDao memberDao;
public ChangePasswordService(MemberDao memberDao) {
this.memberDao = memberDao;
}
}
MemberDao 객체를 생성하는 코드만 변경하면 MemberDao를 사용하는 클래스가 여러개여도 변경할 곳은 의존 주입 대상이 되는 객체를 생성하는 코드 한 줄 뿐이다. 변경할 코드가 한 곳으로 집중된다.
MemberDao memberDao = new MemberDao();
MemberRegisterService reqSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);
MemberDao memberDao = new CachedMemberDao();
MemberRegisterService reqSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);
3. 인터페이스를 통한 의존 처리
주입하는 객체의 타입을 인터페이스로 설정하면 유연성을 더욱 확대할 수 있다.
public interface Battery {
public int getBatteryValue();
}
public class ChargeBattery implements Battery {
@Override
public int getBatteryValue() {
return 0;
}
}
public class NormalBattery implements Battery {
@Override
public int getBatteryValue() {
return 0;
}
}
public class ElectronicRadioToy {
private Battery battery;
public ElectronicRadioToy(Battery battery) {
this.battery = battery;
}
public void setBattery(Battery battery) {
this.battery = battery;
}
}
public class ElectronicRobotToy {
private Battery battery;
public ElectronicRobotToy() {
}
public void setBattery(Battery battery) {
this.battery = battery;
}
}
4. 예제 프로젝트 - 회원가입 및 비밀번호 변경
https://github.com/flyduck-dev/springDI.git * 소스코드는 깃에 올려두었습니다!
- 회원 데이터 관련 클래스 : Member, WrongIdPasswordException, MemberDao
- 회원 가입 처리 관련 클래스 : DuplicateMemberException, RegisterRequest, MemberRegisterService
- 암호 변경 관련 클래스 : MemberNotFoundException, ChangePasswordService
회원 데이터 관련 클래스
package org.example;
import java.time.LocalDateTime;
import org.example.WrongIdPasswordException;
public class Member {
private Long id;
private String email;
private String password;
private String name;
private LocalDateTime registerDataTime;
public Member(String email, String password,
String name, LocalDateTime regDataTime){
this.email = email;
this.password = password;
this.name = name;
this.registerDataTime = regDataTime;
}
void setId(Long id){
this.id = id;
}
public String getPassword() {
return password;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public Long getId() {
return id;
}
public LocalDateTime getRegisterDataTime() {
return registerDataTime;
}
public void changePassword(String oldPassword, String newPassword){
if(!password.equals(oldPassword)){
throw new WrongIdPasswordException();
}
this.password = newPassword;
}
}
package org.example;
import java.util.HashMap;
import java.util.Map;
public class MemberDao {
private static long nextId = 0;
private Map<String,Member> map = new HashMap<>();
public Member selectByEmail(String email){
return map.get(email);
}
public void insert(Member member){
member.setId(++nextId);
Member put = map.put(member.getEmail(), member);
map.put(member.getEmail(), member);
}
public void update(Member member){
map.put(member.getEmail(), member);
}
}
다른 StudenDao 예제
package ems.member.dao;
import java.util.HashMap;
import java.util.Map;
import ems.member.Student;
public class StudentDao {
private Map<String, Student> studentDB = new HashMap<String, Student>();
public void insert(Student student) {
studentDB.put(student.getsNum(), student);
}
public Student select(String sNum) {
return studentDB.get(sNum);
}
public void update(Student student) {
studentDB.put(student.getsNum(), student);
}
public void delete(String sNum) {
studentDB.remove(sNum);
}
public Map<String, Student> getStudentDB() {
return studentDB;
}
}
회원 가입 처리 관련 클래스
package org.example;
public class RegisterRequest {
private String email;
private String password;
private String confirmPassword;
private String name;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfirmPassword() {
return confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isPasswordEqualToConfirmPassword(){
return password.equals(confirmPassword);
}
}
서비스 클래스(회원 등록, 암호 변경)
회원등록
package org.example;
import java.time.LocalDateTime;
public class MemberRegisterService {
private MemberDao memberDao;
public MemberRegisterService(MemberDao memberDao) {
this.memberDao = memberDao;
}
public Long regist(RegisterRequest req){
Member member = memberDao.selectByEmail(req.getEmail());
if (member != null){
throw new DuplicateMemberException("dup email" + req.getEmail());
}
Member newMember = new Member(req.getEmail(), req.getConfirmPassword(), req.getName(), LocalDateTime.now());
memberDao.insert(newMember);
return newMember.getId();
}
}
암호변경
package org.example;
public class ChangePasswordService {
private MemberDao memberDao;
public void changePassword(String email, String oldPwd, String newPwd) {
Member member = memberDao.selectByEmail(email);
if(member == null)
throw new MemberNotFoundException();
member.changePassword(oldPwd, newPwd);
memberDao.update(member);
}
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
}
객체 조립기 (의존 주입 프로그래밍 예제)
생성할 때 한번 만든 Dao 객체를 여러 Service에 주입해준다.
각 서비스들은 Dao 객체에 의존하고 있다.
package assembler;
import org.example.ChangePasswordService;
import org.example.MemberDao;
import org.example.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 getPasswordService() {
return pwdSvc;
}
}
다른 assembler 예제
package ems.member.assembler;
import ems.member.dao.StudentDao;
import ems.member.service.StudentAllSelectService;
import ems.member.service.StudentDeleteService;
import ems.member.service.StudentModifyService;
import ems.member.service.StudentRegisterService;
import ems.member.service.StudentSelectService;
public class StudentAssembler {
private StudentDao studentDao;
private StudentRegisterService registerService;
private StudentModifyService modifyService;
private StudentDeleteService deleteService;
private StudentSelectService selectService;
private StudentAllSelectService allSelectService;
public StudentAssembler() {
studentDao = new StudentDao();
registerService = new StudentRegisterService(studentDao);
modifyService = new StudentModifyService(studentDao);
deleteService = new StudentDeleteService(studentDao);
selectService = new StudentSelectService(studentDao);
allSelectService = new StudentAllSelectService(studentDao);
}
public StudentDao getStudentDao() {
return studentDao;
}
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
public StudentRegisterService getRegisterService() {
return registerService;
}
public void setRegisterService(StudentRegisterService registerService) {
this.registerService = registerService;
}
public StudentModifyService getModifyService() {
return modifyService;
}
public void setModifyService(StudentModifyService modifyService) {
this.modifyService = modifyService;
}
public StudentDeleteService getDeleteService() {
return deleteService;
}
public void setDeleteService(StudentDeleteService deleteService) {
this.deleteService = deleteService;
}
public StudentSelectService getSelectService() {
return selectService;
}
public void setSelectService(StudentSelectService selectService) {
this.selectService = selectService;
}
public StudentAllSelectService getAllSelectService() {
return allSelectService;
}
public void setAllSelectService(StudentAllSelectService allSelectService) {
this.allSelectService = allSelectService;
}
}
Main
package main;
import assembler.Assembler;
import org.example.ChangePasswordService;
import org.example.DuplicateMemberException;
import org.example.MemberNotFoundException;
import org.example.MemberRegisterService;
import org.example.RegisterRequest;
import org.example.WrongIdPasswordException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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){
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.getPasswordService();
try {
changePwdSvc.changePassword(arg[1],arg[2],arg[3]);
System.out.println("암호를 변경했습니다.\n");
} catch (MemberNotFoundException e){
System.out.println("이메일과 암호가 일치하지 않습니다.\n");
}
}
private static void printHelp(){
System.out.println();
System.out.println("명령어 사용법을 확인하세요.");
System.out.println("new 이메일 이름 암호 암호확인");
System.out.println("change 이메일 현재비번 변경비번");
}
}
지금까지 DI를 이용해 의존 객체를 주입하는 방법에 대해 알아봤다. 그리고 객체를 생성하고 의존 주입을 이용해서 객체를 서로 연결해주는 조립기에 대해서 살펴봤다.
'JAVA, SPRING' 카테고리의 다른 글
[자바, 스프링] 스프링은 객체 컨테이너 (0) | 2023.04.19 |
---|