코린이의 기록

[Spring] Spring 4 - 스프링 DI와 예제 프로젝트 본문

Framework/Spring

[Spring] Spring 4 - 스프링 DI와 예제 프로젝트

코린이예요 2018. 5. 3. 12:05
반응형

※ 이 포스팅은 "초보웹 개발자를 위한 스프링4 프로그래밍 입문" 책을 기반으로 작성하였습니다.

이번 포스팅에서는 Spring에서 중요한 DI(Dependency Injection) 개념에 대해서 공부하기위하여 간단한 회원가입 프로젝트를 구현해본다.

DI(Dependency Injection)의존 주입이란?
어떤 클래스가 다른 클래스의 메소드를 실행할 때 이를 의존한다라고 표현한다. 이 포스팅에서의 예제를 예로들면, MemberRegisterService 클래스는 회원가입을 처리하는 클래스인데, 여기서 MemberDao 클래스의 메소드 (selectByEmail, inject)를 사용하고 있다. 따라서 MemberRegistrerService클래스는 MemberDao 클래스에 의존한다고할 수 있다.



프로젝트 생성 및 클래스 생성 pom.xml 작성은 아래 포스팅을 참고한다.

1. 클래스 생성하기

1. 회원 데이터 
/src/main/java/spring/Member.java
/src/main/java/spring/IdPasswordNotMatchingException.java
/src/main/java/spring/MemberDao.java
2. 회원 가입 처리
/src/main/java/spring/AlreadyExistingMemberException.java
/src/main/java/spring/RegisterRequest.java
/src/main/java/spring/MemberRegisterService.java
3. 암호 변경
/src/main/java/spring/MemberNotFoundException.java
/src/main/java/spring/ChangePassowkrdService.java

Member.java

package spring; import java.util.Date; public class Member { private Long id; private String email; private String password; private String name; private Date registerDate; public Member(String email, String password, String name, Date registerDate) { this.email = email; this.password = password; this.name = name; this.registerDate = registerDate; } void setId(Long id) { this.id = id; } public Long getId() { return id; } public String getEmail() { return email; } public String getPassword() { return password; } public String getName() { return name; } public Date getRegisterDate() { return registerDate; } public void changePassword(String oldPassword, String newPassword) { if (!password.equals(oldPassword)) throw new IdPasswordNotMatchingException(); this.password = newPassword; } }

Mamber 클래스에서는 회원의 정보 (ID, Email, Password, Name, Register Date)들을 set하고 get하는 메소드와 Password를 변경하는 메소드로 구성되어있다. changePassword에서는 기존의 Password를 저장되어있는 Password정보와 일치하면 새로운 Password로 바꿔주는 메소드이다. Password정보가 일치하지 않으면 throw를 통해 IdPasswordNotMatchingException exception처리를 하는데, 해당 메소드는 아래와 같이 구현한다. 

IdPasswordNotMatchingException.java

package spring; public class IdPasswordNotMatchingException extends RuntimeException { }

MemberDao.java

package spring; import java.util.Collection; 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); map.put(member.getEmail(), member); } public void update(Member member) { map.put(member.getEmail(), member); } public Collection<Member> selectAll() { return map.values(); } }

MemberDao는 DB와 같은 기능을 한다고 할 수 있다. 이번 포스팅에서는 DB연동에 대한 내용을 다루지 않을 것이므로 Map을 통해서 일시적으로 데이터를 저장하여 사용한다. 
selectByEmail메소드는 mail로 전달받은 정보로 회원을 찾는 메소드이다. DB로치면 Select를 하는 메소드라고 할 수 있다. insert 메소드는 말그대로 멤버변수를 파라미터로 받아서 insert하는 메소드이다. ID는 시퀀셜하게 ++하여 증가시킨다. update 메소드는 마찬가지로 말그대로 멤버변수를 파라미터로 받아서 업데이트 한다. 

AlreadyExistingMemberException.java

package spring; public class AlreadyExistingMemberException extends RuntimeException { public AlreadyExistingMemberException(String message) { super(message); } }

AlreadyExistingMemberException은 회원을 생성할 때 중복처리를 해주는 exception이다.


RegisterRequest.java

package spring; 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); } }

RegisterRequest는 회원가입을 처리할때 필요한 데이터 (email, password, password확인, name)을 담고있는 클래스이다.  isPasswordEqualAToConfirmPassword는 처음에 입력한 password와 확인용 password의 값이 일치하는지 확인하는 메소드인데, 흔히 우리가 회원가입 시 비밀번호를 두번 입력하게 되어있는데 이 부분을 의미한다.

MemberRegisterService.java (방법1)

package spring; import java.util.Date; public class MemberRegisterService { private MemberDao memberDao; public MemberRegisterService(MemberDao memberDao) { this.memberDao = memberDao; } public void regist(RegisterRequest req) { Member member = memberDao.selectByEmail(req.getEmail()); if (member != null) { throw new AlreadyExistingMemberException("dup email " + req.getEmail()); } Member newMember = new Member( req.getEmail(), req.getPassword(), req.getName(), new Date()); memberDao.insert(newMember); } }

MemberRegisterService는 본격적으로 회원가입을 처리하는 클래스이다. regist 메소드에서 회원 가입시 필요한 데이터인 req를 전달받는다. 먼저 해당 데이터의 이메일을 사용하여 해당 데이터가 이미 존재하는지 확인한다. 없다면 새로운 Member객체를 생성하여 전달받은 req의 email과 password, name을 insert한다. 

(중요!!) 추가로 MemberRegisterService 클래스에서 DI(Dependency Injection)에 대한 개념을 집고갈 수 있다.

MemberRegisterService.java (방법2) -> 의존 객체 직접 생성하는 방법 (좋은 방법이 아님)

public class MemberRegisterService { private MemberDao memberDao = new MemberDao(); public void regist(RegisterRequest req) { Member member = memberDao.selectByEmail(req.getEmail()); if (member != null) { throw new AlreadyExistingMemberException("dup email " + req.getEmail()); } Member newMember = new Member( req.getEmail(), req.getPassword(), req.getName(), new Date()); memberDao.insert(newMember); } }

위 (방법2)는 의존 객체를 직접 생성하는 방식의 클래스이다. (방법1)과 차이점이 있다면, 

private MemberDao memberDao = new MemberDao();


new를 통하여 MemberDao객체를 직접 생성하였다. 즉 MemberRegisterService 객체를 생성함과 동시에 MemberDao 객체도 동시에 생성시켰다. 
반면에 (방법1)는 MemberRegisterService가 의존하는 객체인 MemberDao를 직접 생성하지 않고 생성자를 통해서 의존 객체를 전달받는 방식이다. 

public MemberRegisterService(MemberDao memberDao){ this.memberDao = memberDao; }

그렇다면 왜 DI 방식으로 구현해야 하는가? 
이유는 소스 코드 변경 시 효율적으로 소스를 관리하기 위해서이다. 
예를들어 우리가 의존 객체를 전달받아 사용하는 방식이 아닌, 직접 생성하는 방식으로 구현을 했다고 하자.
MemberDao라는 객체를 멤버를 등록하는 클래스인 MemberRegisterService에서도 사용하고 멤버 암호 변경하는 클래스인 ChangePasswordService에서 사용한다.
- 멤버를 등록하는 클래스

public class MemberRegisterService{ private MemberDao memberDao = new MemberDao(); ... }

- 멤버 암호를 변경하는 클래스

public class ChangePasswordService{ private MemberDao memberDao = new MemberDao(); ... }

그런데 추가 요구사항이 생겼다. 회원 데이터 조회 속도 성능을 높이기 위해 캐시를 사용해야 한다. 그래서 MemberDao클래스를 상속받은 CachedMemberDao 클래스가 새로 만들어졌다. 

public class CachedMemberDao extends MemberDao{ . . }

이렇게 되버리면 위에서 생성해준 의존 객체들의 이름을 모두 바꿔주어야 한다. 예시에서는 MemberRegisterService와 ChangePasswordService 두개뿐이지만 몇십개 혹은 몇백개의 클래스에서 MemberDao 객체를 의존하고 있으면 모두 바꿔주어야하는 작업을 해야한다. 
- 멤버를 등록하는 클래스
public class MemberRegisterService{
    private MemberDao memberDao = new
 CachedMemberDao();
   ...
}
- 멤버 암호를 변경하는 클래스
public class ChangePasswordService{
   private MemberDao memberDao = new 
CachedMemberDao();
   ...
}

이렇게 의존 객체를 직접 생성하는 경우의 불편함을 확인해보았다. 그렇다면 의존객체를 생성자를 통해 전달받아 사용하는 방식은 어떻게 사용할까? 즉 의존객체는 언제 어디에서 생성해야할까? 의존객체를 전달받는 것은 해당 의존 객체를 쓰고자 하는 각 클래스(MemberRegisterService, ChangePasswordService)에서 하면된다. 생성을 어디서 하느냐가 관건인데, 우선 의존 객체를 생성하고 주입시키는  코드의 예시는 아래와 같다.

MemberDao memberDao = new MemberDao(); MemberRegisterService regSvc = new MemberRegisterService(memberDao); ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);

만약 캐시를 사용하는 요구사항이 들어오면 위 코드에서 한줄만 수정하면 된다.

 MemberDao memberDao = new CachedMemberDao
();
MemberRegisterService regSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);


이런식으로 의존 객체를 생성하고 수정할 수 있다. 이 의존객체를 생성하는 위치는 (2. 객체 조립하기)에서 언급하도록 한다.

3. 암호 변경

ChangePassowordService.java

package spring; public class ChangePasswordService { private MemberDao memberDao; public ChangePasswordService(MemberDao memberDao) { this.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); } }

changePasswordService에서는  password를 변경해주는 클래스이다. 우선 mail로 등록된 회원인지 검색한 후, 회원이 있으면 password를 변경한후에 update 해준다. 등록된 회원정보가 없으면 throw를 통해 MemberNotFoundException  exception처리를 하는데, 해당 메소드는 아래와 같이 구현한다. 

MemberNotFoundException.java

package spring; public class MemberNotFoundException extends RuntimeException { }

2. 객체 조립하기
1. 클래스 생성하기에서 DI와 관련하여 의존 객체 생성에 대하여 설명하였다. 의존 객체를 생성자를 통해 전달받는 방법은 보았는데 그렇다면 어디에 의존객체를 생성해주어야 할까? 바로 두가지 방법이 있다.
- 방법 1. Assembler class 구현
- 방법 2. Spring을 이용하여 객체조립 (-> 결과적으로 우리가 Spring을 사용하는 이유가 된다)

방법 1. Assembler class 구현
의존 객체를 사용하고자 하는 객체와, 의존객체 이 두 객체를 조립한다는 의미에서 assembler라는 이름의 클래스를 별도로 생성하여 이 클래스에서 객체를 생성하고 의존객체를 주입할 것이다. 

assembler.java

package assembler; 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(memberDao); } public MemberDao getMemberDao() { return memberDao; } public MemberRegisterService getMemberRegisterService() { return regSvc; } public ChangePasswordService getChangePasswordService() { return pwdSvc; } }

여기서 유심히 볼 부분은 아래와 같다. 

private MemberDao memberDao; private MemberRegisterService regSvc; private ChangePasswordService pwdSvc;

객체를 생성

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

의존 객체 주입

객체를 생성하고 각 객체(memberDao, regSvc, pwdSvc)가 의존하는 의존 객체(memberDao)를 주입해주었다. 

이제 회원 가입을 처리하는 클래스 MemberRegisterService와 회원 password를 변경하는 클래스 ChangePassword를 수행하는 메인클래스 구현한다. 

mainForAssembler.java

package main; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import spring.AlreadyExistingMemberException; import spring.ChangePasswordService; import spring.IdPasswordNotMatchingException; import spring.MemberNotFoundException; import spring.MemberRegisterService; import spring.RegisterRequest; import assembler.Assembler; 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 (AlreadyExistingMemberException 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 (IdPasswordNotMatchingException 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. Spring을 이용하여 객체조립
앞서 설명한 의존, DI, 조립기에 대한 내용들은 Spring을 이용하는 이유에 대한 이해를 돕기위해서 살펴본 내용들이다. Spring이 제공하는 기능을통해 의존 주입(DI)를 편리하게 사용할 수 있다. 
Spring을 이용한 객체조립은 .java가 아닌 .xml 파일을 통해 작성한다. 이전 포스팅에서 작성했던 "Spring 4 - Maven 프로젝트 생성"에도 언급하였지만 spring 설정에 대한 파일들은 resources에서 관리하도록 한다. 따라서 관련 xml파일은 resources/appCtx.xml으로 생성하도록 하자.

appCtx.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"> <bean id="memberDao" class="spring.MemberDao"> </bean> <bean id="memberRegSvc" class="spring.MemberRegisterService"> <constructor-arg ref="memberDao" /> </bean> <bean id="changePwdSvc" class="spring.ChangePasswordService"> <constructor-arg ref="memberDao" /> </bean> </beans>

여기서 중요한것은 bean tag인데 스프링이 생성하는 객체를 빈(bean)객체라고 부른다. 
<bean id="memberDao" class="spring.MemberDao"> </bean>
-> MemberDao객체를 생성하여 memberDao라는 이름으로 보관한다. 

<bean id="memberRegSvc" class="spring.MemberRegisterService"> 
<constructor-arg ref="memberDao" /> 
</bean> 

-> MemberRegisterService 객체를 생성하여 memberRegSvc라는 이름으로 보관한다. 이때 memberDao라는 이름의 객체를 주입시킨다. 

<bean id="changePwdSvc" class="spring.ChangePasswordService"> 
<constructor-arg ref="memberDao" /> 
</bean>

-> ChangePasswordService 객체를 생성하여 changePwdSvc라는 이름으로 보관한다. 이때 memberDao라는 이름의 객체를 주입시킨다.

constructor-arg ref : 생성자를 이용하여 의존 객체를 주입한다. ref 속성 값으로 bean id가 들어간다. 즉 bean id(혹은 name)를 "xxx"로 갖는 bean을 생성자의 인자로 넘겨주겠다는 의미이다.

마찬가지로 Spring을 이용한 객체조립을 완성한 것이 끝이 아니다. 방금 xml 파일은 설정파일이고 이 설정파일을 이용하여 스프링컨테이너를 생성해야 한다. 이는 "GenericXmlApplicationContext"라는 클래스를 사용하여 객체조립을 수행하는 스프링컨테이너를 생성할 것이다. 이 작업은 
메인클래스에서 구현하도록 한다. 

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.support.GenericXmlApplicationContext; import spring.AlreadyExistingMemberException; import spring.ChangePasswordService; import spring.IdPasswordNotMatchingException; import spring.MemberNotFoundException; import spring.MemberRegisterService; import spring.RegisterRequest; public class MainForSpring { private static ApplicationContext ctx = null; public static void main(String[] args) throws IOException { ctx = new GenericXmlApplicationContext("classpath:appCtx.xml"); 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 (AlreadyExistingMemberException 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 (IdPasswordNotMatchingException 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(); } }

spring 컨테이너 사용하지 않고 assembler.java에서 구현한 방식 MainForSpring.java와의 차이점은 다음과 같이 3부분이다.

1. 객체를 생성하는 방식

private static ApplicationContext ctx = null; public static void main(String[] args) throws IOException { ctx = new GenericXmlApplicationContext("classpath:appCtx.xml"); .... }

-> GenericXmlApplicationContext 객체를 생성하여 appCtx.xml 파일을 읽어와 bean객체를 생성한다. 

2. MemberRegisterService 객체

MainForAssembler.java

MemberRegisterService regSvc = assembler.getMemberRegisterService();

MainForspring.java

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

-> memberRegSvc라는 이름을 가진 bean 객체를 구한다. 

3. ChangePasswordService 객체

MainForAssembler.java

ChangePasswordService changePwdSvc = assembler.getChangePasswordService();

MainForspring.java

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

-> changePwdSvc라는 이름을 가진 bean 객체를 구한다.

3. 의존객체 주입 방식

앞서 언급한 의존 객체 생성 및 주입에는 두가지 방식이 있다. 
1. 생성자 방식
2. 설정 메소드 방식

3.1 DI 방식 1 : 생성자 방식
우리가 앞에서 봤던 스프링 설정 방식이 바로 DI 방식1. 생성자 방식이다. 
그렇다면 의존 객체가 두개 이상일 경우에는 어떻게 설정해야 할까스프링 XML 파일에서 <constructor-arg ref=”객체” /> 를 아래줄에 추가해주면 된다. 의존 객체가 두개 이상일 경우에 대한 예시를 위해 아래 파일들을 추가하여 살펴보도록 한다.

<추가할 클래스 및 설정 파일>
-       MemberPrinter.java
-       MemberListPrinter.java

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.getRegisterDate()); } }

멤버의 정보를 출력하는 메소드

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(); for (Member m : members) { printer.print(m); } } }

멤버 정보 리스트를 출력하는 메소드에서 두개의 객체를 전달받고 있다. MemberDao, MemberPrinter

MainForSpring.java
MainForSpring.java에 list 관련된 부분을 추가한다.

// 생략 if (command.startsWith("new ")) { processNewCommand(command.split(" ")); continue; } else if (command.startsWith("change ")) { processChangeCommand(command.split(" ")); continue; } else if (command.equals("list")) { processListCommand(); continue; } printHelp(); } } // 생략 private static void processListCommand() { MemberListPrinter listPrinter = ctx.getBean("listPrinter", MemberListPrinter.class); listPrinter.printAll(); }

processListCommand를 추가하였다. processListCommand에서는 listPrinter라는 이름의 Bean객체를 가져와 MemberListPrinter의 생성자를 통하여 그 객체를 주입한다. listPrinter의 설정은 아래 appCtx2.xml을 참고한다.  MemberListPrinter 객체는 memberDao와 memberPrinter에 의존한다. 

<bean id="listPrinter" class="spring.MemberListPrinter"> <constructor-arg ref="memberDao" /> <constructor-arg ref="memberPrinter" /> </bean>

appCtx.xml 

appCtx.xml에 아래 설정을 추가한다.

// 생략 <bean id="listPrinter" class="spring.MemberListPrinter"> <constructor-arg ref="memberDao" /> <constructor-arg ref="memberPrinter" /> </bean> <bean id="memberPrinter" class="spring.MemberPrinter"> </bean> </beans>


3.2 DI 방식 2 : 설정 메서드 방식
설정 메서드 방식은 말 그대로 set으로 시작하는 프로퍼티 메소드를 이용하여 의존 객체를 주입하는 방식이다. 이 설정 메서드 방식은 다음과 같은 규칙을 따른다. 
- 메소드의 이름은 set으로 시작함
- set 뒤에오는 프로퍼티 이름의 첫글자는 대문자로 치환한다.
- 설정 메소드는 한개의 파라미터를 가지며, 파라미터의 타입은 프로퍼티의 타입이다. 

예를들어, 설정하려는 프로퍼티 이름이 "memberDao"이고 프로퍼티 타입이 "MemberDao"라면 아래와 같이 표현할 수 있다.

public void setMemeberDao(MemberDao dao)

설정 메소드 방식으로 의존 객체 주입하는 방식에 대한 예시를 위해 아래 파일을 추가하도록 한다.
<추가할 클래스 및 설정 파일>
-       MemberInfoPrinter.java

MemberInfoPrinter.java

package spring; public class MemberInfoPrinter { private MemberDao memDao; private MemberPrinter printer; public void setMemberDao(MemberDao memberDao) { this.memDao = memberDao; } public void setPrinter(MemberPrinter printer) { this.printer = printer; } public void printMemberInfo(String email) { Member member = memDao.selectByEmail(email); if (member == null) { System.out.println("데이터 없음\n"); return; } printer.print(member); System.out.println(); } }

MemberInfoPrinter.java는 멤버의 정보를 출력하는 클래스이다. MemberListPrinter.java와 차이점이 있다면 둘다 두개의 의존객체 MemberDao와 MemberPrint를 사용하고 있으나, MemberListPrinter.java에서는 두개의 객체를 파라미터로 전달받아 생성자를 통해 두개의 객체를 구했다. 반면에 MemberInfoPrinter.java에서는 설정메소드 setXXX 형태의 메소드를 정의하여 MemberDao와 MemberPrinter 타입의 객체에 의존을 주입하여 사용하도록 하였다. 

MemberListPrinter.java (생성자 방식)

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

MemberInfoPrinter.java (설정 메소드 방식)

private MemberDao memDao; private MemberPrinter printer; public void setMemberDao(MemberDao memberDao) { this.memDao = memberDao; } public void setPrinter(MemberPrinter printer) { this.printer = printer; }

설정 메소드를 통해서 의존 객체를 주입하기위해서는 아래와 같은 xml 설정을 해주어 한다. appCtx.xml에서 infoPrinter에 관련된 설정을 추가해준다.

// 생략 <bean id="memberDao" class="spring.MemberDao"> </bean> // 생략 <bean id="memberPrinter" class="spring.MemberPrinter"> </bean> <bean id="infoPrinter" class="spring.MemberInfoPrinter"> <property name="memberDao" ref="memberDao" /> <property name="printer" ref="memberPrinter" /> </bean>

여기서 중요한것은 infoPrinter 라는 이름의 bean객체를 생성해준 후에 property 태그로 각각의 의존 객체를 주입시키고 있다. name="memberDao"가 의미하는 것은, 앞서 설명한 설정 메소드 규칙에따라 앞에 set을 추가하고 프로퍼티이름의 앞글자를 대문자로 치환한 setMemberDao()메소드를 이용하여 ref 속성에 명시되어 있는 객체를 주입하겠다는 의미이다. 마찬가지로 name="printer"가 의미하는것은 setPrinter()메소드를 이용하여 ref속성에 명시되어 있는 객체를 주입하겠다는 의미이다.

MainForSpring.java
멤버 정보를 출력하기위해 MainForSpring에서 info command를 추가한다. 

// 생략 private static void processInfoCommand(String[] arg) { if (arg.length != 2) { printHelp(); return; } MemberInfoPrinter infoPrinter = ctx.getBean("infoPrinter", MemberInfoPrinter.class); infoPrinter.printMemberInfo(arg[1]); }
4.기본 데이터 타입값으로 의존객체 설정하기

앞서 언급한 내용은 다른 객체를 주입 할 때 객체 타입으로 설정하였다. 하지만 객체 타입이 아닌 일반 데이터 타입 int, string 등과 같은 타입은 어떻게 설정할까? 해당 예시를 위해 아래 코드를 추가하도록 한다.

VersionPrinter.java

package spring; public class VersionPrinter { private int majorVersion; private int minorVersion; public VersionPrinter() { } // 생성자 방식 public VersionPrinter(int majorVersion, int minorVersion) { this.majorVersion = majorVersion; this.minorVersion = minorVersion; } // 설정 메소드 방식 public void setMajorVersion(int majorVersion) { this.majorVersion = majorVersion; } public void setMinorVersion(int minorVersion) { this.minorVersion = minorVersion; } public void print() { System.out.printf("이 프로그램의 버전은 %d.%d입니다.\n\n", majorVersion, minorVersion); } }

위 소스와 같이 int 타입의 기본 데이터 타입의 파라미터를 설정하였다.
생성자 방식과 설정 메소드 방식 각각 아래와 같이 ref 대신에 value에 integer 값을 넣어주면 된다.
1. 생성자 방식

<bean id="versionPrinter" class="spring.VersionPrinter"> <constructor-arg value="4" /> <constructor-arg value="1" /> </bean>

2. 설정 메소드 방식

<bean id="versionPrinter" class="spring.VersionPrinter"> <property name="majorVersion" value="4" /> <property name="minorVersion"> <value>1</value> </property> </bean>

name="majorVersion"과 같이 value 속성으로 값을 넣을 수도 있지만 name="minorVersion"과 같이 value tag로 별도로 설정해줄 수 있다.

5. 두 개 이상의 xml 설정 파일 사용하기

xml 설정이 많을 경우 여러개의 xml 설정 파일로 나눈 후 위에서 설정한 appCtx.xml 설정파일을 conf1.xml과 conf2.xml 두 파일로 나누어 보도록 한다.

conf1.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"> <bean id="memberDao" class="spring.MemberDao"> </bean> <bean id="memberPrinter" class="spring.MemberPrinter"> </bean> </beans>


conf2.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"> <bean id="memberRegSvc" class="spring.MemberRegisterService"> <constructor-arg ref="memberDao" /> </bean> <bean id="changePwdSvc" class="spring.ChangePasswordService"> <constructor-arg ref="memberDao" /> </bean> <bean id="listPrinter" class="spring.MemberListPrinter"> <constructor-arg ref="memberDao" /> <constructor-arg ref="memberPrinter" /> </bean> <bean id="infoPrinter" class="spring.MemberInfoPrinter"> <property name="memberDao" ref="memberDao" /> <property name="printer" ref="memberPrinter" /> </bean> <bean id="versionPrinter" class="spring.VersionPrinter"> <property name="majorVersion" value="4" /> <property name="minorVersion" value="1" /> </bean> </beans>

xml 설정 파일을 나눈 후 이 설정파일을 배열을 이용하여 생성하도록 한다.

ctx = new GenericXmlApplicationContext("classpath:appCtx2.xml");

String[] conf = { "classpath:conf1.xml", "classpath:conf2.xml" }; ctx = new GenericXmlApplicationContext(conf);


or

ctx= new GenericXmlApplicationContext("classpath:conf1.xml, "classpath:conf2.xml");

다른 방식으로 import tag 를 사용하는 방식이 있다.

configImport.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"> <import resource="classpath:conf2.xml" /> <bean id="memberDao" class="spring.MemberDao"> </bean> <bean id="memberPrinter" class="spring.MemberPrinter"> </bean> </beans>

<import resource = "classpath:conf2.xml" /> 를 추가하면 configImport.xml 파일 한개만 지정해서 사용하면 된다.

String[] conf = { "classpath:configImport.xml" }; ctx = new GenericXmlApplicationContext(conf);


반응형
Comments