-
01 의존이란?
DI는 (Dependency Injection)의 약자로 , 우리말로 의존주입이라고 주로 번역한다.
여기서 의존의 이해가 필요하다.
여기서 말하는 의존이라 객체간의 의존을 의미하는데. 이해를 돕기 위해 회원 가입을 처리하는 기능을 구현한 다음에 코드를 보자 .
public class MemberRegisterService{
public void resgist(RegisterRequest req){
//이메일로 회원 데이터(Member)조회
Member member = memberDAO.selectByEmail(req.getEmail());
if(member != null) {
//같은 이메일을 가진 회원이 이미 존재하면 익셉션 발생
throw new AlreadyExistingMemberException("dup email"+req.getEmail());
}
//같은 이메일을 가진 회원이 존재하지 않으면 DB에 삽입
Member newMember = new Member(req.getEmail(),req.getPassword,req.getName(),new Date());
memberDao.insert(newMember);
}
}
서로 다른 회원은 같은 이메일 주소를 사용할 수 없다는 요구사항이 있다고 가정해보자.
이 요구사항을 처리하기 위해 MemberRegisterService 클래스는 MemberDao 객체의(selectByEmail()) 메서드를 이용해서 데이터베이스(Database, 이하 DB로 명명)
동일한 이메일을 가진 회원 데이터가 존재하는지 확인한다. 만약에 같은 이메일을 가진 회원데이터가 존재한다면 위코드처럼 익셉션을 발생시킨다. 같은 이메일을 가진 회원 데이터가
존재하지 않음녀 회원 정보를 담은 Member 객체를 생성하고 MemberDao 객체의 insert() 메서드를 이용해서DB 회원 데이터를 삽입한다.
Service 클래스가 Dao 메서드를 사용하고 회원데이터가 존재하는 확인하기 위해 MemberDao 객체의 selectByEmail 메서드를 실행하고 회원데이터를 DB에 삽입하기 위해 insert 메서드를 실행하고 있다.
이렇게 한 클래스가 다른 클래스의 메서드를 실행 할 때 . 이를 의존한다고 표현한다. 앞서 코드에서 MemberResgisterService 클래스가 MemberDao 클래스에 의존한다고 표현할 수 있다.
의존하는 대상이 있면 그대상을 구하는 방법이 필요하다.
1. 의존 대상 객체를 직접 생성하는 것이다.
클래스 내부에서 의존 객체를 생성하는 것이 쉽기는 하지만 유지보수 관점에서 문제점을 유발할 수 있다.
이 방법 이외에 또 다른 방법이 있는데
DI 와 서비스 로케이터 이다.
02 DI를 이용한 의존 처리
DI는 의존하는 객체를 직접 생성하지 않고 읜 조객체를 전달하는 방식을 사용한다.방금 코드를 DI 방식으로 바꾸면public class MemberRegisterService{
private MemberDao memberDao;
public MemberRegisterService(MemberDao memberDao){
this.memberDao = memberDao;
}
public void resgist(RegisterRequest req){
//이메일로 회원 데이터(Member)조회
Member member = memberDAO.selectByEmail(req.getEmail());
if(member != null) {
//같은 이메일을 가진 회원이 이미 존재하면 익셉션 발생
throw new AlreadyExistingMemberException("dup email"+req.getEmail());
}
//같은 이메일을 가진 회원이 존재하지 않으면 DB에 삽입
Member newMember = new Member(req.getEmail(),req.getPassword,req.getName(),new Date());
memberDao.insert(newMember);
}
}
전과 바뀐코드는 의존 객체를 직접생성하지 않는다 대신 8~10행과 같이 생성자를 통해서 의존객체를 전달받는다.
MemberRegisterService가 의존하고 있는 MemberDao 객체를 주입 받은 것이다. 의존객체를 직접 구하지 않고 생성자를 통해서
전달 받기 때문에 이코드는 DI패턴을 따르고 있다.
DI를 적용한 결과 MemberRegisterService 객체를 생성 할때는 MeberDao 객체를 전달해 주어야한다.
DI를 사용하는 이유는 변경에 유연함에 있다.
03 DI와 의존 객체 변경의 유연함
의존 객체를 직접 생성한느 방식은 다음 코드처럼 필드 또는 생성자에서 new 연산자를 이용해서 객체를 생성한다.의존객체를 직접 생성하는 방식은필드 또는 생성자에서 new 연산자를 이용해서 생성회원의 암호 변경 기능을 제공하는 ChangePasswordService 클래스도 다음과 같이 의존 객체를 직접 생산한다고 생각해보자.public class ChangePasswordService(){private MemberDao memberDao = new MemberDao();...}MemberDao 클래스는 회원데이터를 DB에 저장한다고 가정
빠른 조회를 위해 캐시를 적용해야하는 상황이 발생했다. 그래서
MemberDAO 클래스는 상속받은 ChachedMemberDao를 만들었다.
public class CachedMemberDao extends MemberDao{
...
}
캐시 기능이 적용된 이 클래스를 사용하려면 MemberRegisterService 클래스와 ChangePasswordService클래스의 코드를 다음과 같이 변경해주어야한다.
public class ChangePasswordService(){private MemberDao memberDao = new CashedMemberDao();...}public class MemberRegisterService(){private MemberDao memberDao = new CashedMemberDao();...}Dao 클래스가 3개 였다면 , 세클래스 모두 동일하게 소스코드를 변경해야 할 것이다.
만약에 DI 방식을 이용했다면
MemberDao memberDao = new MemberDao();
MemberRegisterService regSvc = new MemberRegisterService(memberDao);
MemberPasswordService pwSvc = new ChangePasswordService(memberDao);
MemberDao memberDao = new CachedMemberDao();
MemberRegisterService regSvc = new MemberRegisterService(memberDao);
MemberPasswordService pwSvc = new ChangePasswordService(memberDao);
사용할 클래스 한곳에만 클래스의 변경을 한곳에서만 해주면 된다.
DI를 사용하면 MemberDao 객체를 사용하는 클래스가 세개 여도 변경할 곳은 의존 주입 대상이 되는 객체를 생성하는 코드 한곳뿐이다.
예제
회원 데이터 관련클래스- Member- IdPasswordNotMatchingException- MemberDao회원가입 처리 관련 클래스- AlreadyExsitingMemberException- RegisterRequest- MemberRegisterService : 리스트 3.1의 코드 사용암호 변경 관련 클래스- MemberNotFoundException-ChangePasswordServiceMember.java123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657package 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) {super();this.id = id;this.email = email;this.password = password;this.name = name;this.registerDate = registerDate;}public Long getId() {return id;}public void setId(Long id) {this.id = 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;}}change 메소드는
현재 패스워드와 입력한 옛날 패스워드가 같지 않으면 예외를 발생시키고 아니면 새로운 패스워드를 입력해준다.
123456package spring;public class IdPasswordNotMatchingException extends RuntimeException{}cs 예외 발생 시키는 클래스
MemberDao 클래스 작성
123456789101112131415161718192021222324252627282930package 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();}}cs DB연동 말고 Map으로 간단히 구현
회원가입 처리 관련 클래스
123456789package spring;public class AlreadyExistingMemberException extends RuntimeException{public AlreadyExistingMemberException(String message) {super(message);}}cs 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package spring;public class RegisterRequest {private String email;private String password;private String comfirmPassword;private String name;public RegisterRequest() {super();}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 getComfirmPassword() {return comfirmPassword;}public void setComfirmPassword(String comfirmPassword) {this.comfirmPassword = comfirmPassword;}public String getName() {return name;}public void setName(String name) {this.name = name;}public boolean isPasswordEqualToComfirmPassword() {return password.equals(comfirmPassword);}}cs RegisterRequest는 회원가입을 처리할때 필요한 이메일 암호 이름 데이터를 담고있는 간단한 클래스이다.
AlreadyExistingMemberException 클래스는 동일한 이메일을 가지고 있는 회원이 존재할경우 MemberResgisterService가 익셉션을 발생
암호 변경 관련 클래스
ChangePassWordService 클래스1234567891011121314151617181920package spring;public class ChangePasswordService {private MemberDao memberDao;public ChangePasswordService(MemberDao memberDao) {this.memberDao = memberDao;}public void ChangePasswordService(String email , String oldPwd, String newPwd) {Member member = memberDao.selectByEmail(email);if(member==null) {throw new MemberNotFoundException();}member.changePassword(oldPwd, newPwd);memberDao.update(member);}}cs 클래스의 암호를 변경할 Member 데이터를 찾기 위해 email 을 사용한다.만약 email에 해당하는 Member가 존재하지 않으면 13-14행에서 익셉션을 발생시킨다.멤버가 존재하면 암호를 변경하고, 17행에서 암호를 변경된 데이터를 보관;
멤버가 없을경우 발생하는 예외
123456package spring;public class MemberNotFoundException extends RuntimeException {}cs 멤버 등록 서비스
12345678910111213141516171819202122package 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);}}cs 05 객체 조립기
조립기에 대한 내용DI설명을 통해 객체를 주입해주는 코드 한곳만 변경해 준다는것을 알았다.실제 객체를 생성하는 코드는 어디있을까?쉽게 생각하면 메인 메서드에서 생성한다고 생각할 수있다.MemberDao memberDao = new MemberDao();
MemberRegisterService regSvc = new MemberRegisterService(memberDao);
MemberPasswordService pwSvc = new ChangePasswordService(memberDao);
메인 메서드에서 조립하는 방법과
객체를 주입시켜 주는 클래스를 따로 생성하는 방법이있다.
의존객체를 주입한다는 것은 서로다른 두객체를 조립 해준다고 생각할 수 있는데.
이 클래스를 조립기(assembler)라고도 표현한다.
예를 들어 앞에 작성했던 회원 가입이나 암호 변경 기능을 제공하는 클래스의 객체를 생성하고 대상이 되는 객체를 주입해주는 조립기
클래스는 다음과 같이 작성가능하다.
12345678910111213141516171819202122232425262728293031323334package assembler;import spring.ChangePasswordService;import spring.MemberDao;import spring.MemberRegisterService;public class Assembler {private MemberDao memberDao;private MemberRegisterService regSvc;private ChangePasswordService pwdSvc;public Assembler() {super();this.memberDao = new MemberDao();this.regSvc = new MemberRegisterService(memberDao);this.pwdSvc = new ChangePasswordService(memberDao);}public MemberDao getMemberDao() {return memberDao;}public MemberRegisterService getRegSvc() {return regSvc;}public ChangePasswordService getPwdSvc() {return pwdSvc;}}cs 객체생성할때 생성자를 통해 MemberDao객체를 주입하는 것을 알 수있다.
Assembler 클래스는 객체를 생성하고 각 객체가 의존하고 있는 객체를 주입해 준다.
다음 그림과 같이
사용하려면
Assembler assembler = new Assembler();
ChangePasswordService changePwdSvc = assembler.getChangePasswordService();
changePwdSvc.changePassword("이메일","비밀번호","새로운 비밀번호")
이런 식으로 사용하고 의존 객체를 변경하려면 Assembler에서 객체 초기화 부분만 변경해주면 된다.
조립기는 객체를 생성하고 의존객체를 주입하는 역할을한다.
또 한 특정객체를 필요로하는 곳에 객체를 제공한다.
조립기 사용예제
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788package 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.getRegSvc();RegisterRequest req = new RegisterRequest();req.setEmail(arg[1]);req.setName(arg[2]);req.setPassword(arg[3]);req.setComfirmPassword(arg[4]);if (!req.isPasswordEqualToComfirmPassword()) {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.getPwdSvc();try {changePwdSvc.ChangePasswordService(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();}}cs 어플리케이션으로 실행한결과
12345678910111213141516171819202122232425262728명렁어를 입력하세요:new잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.명령어 사용법:new 이메일 이름 암호 암호확인change 이메일 현재비번 변경비번명렁어를 입력하세요:new aaa@aaa.com 김주현 1111 1111등록했습니다.명렁어를 입력하세요:change aaa@aaa.com bad new 1234잘못된 명령입니다. 아래 명령어 사용법을 확인하세요.명령어 사용법:new 이메일 이름 암호 암호확인change 이메일 현재비번 변경비번명렁어를 입력하세요:change aaa@aaa.com bad 1111이메일과 암호가 일치하지 않습니다.명렁어를 입력하세요:exit종료합니다.cs 06 스프링 DI 설정
의존이 무엇이고 의존 객체를 주입하는 방법에 대하 알아봄스프링을 설명하는 책에서 스프링 자체가 아닌 의존 , DI , 조립기에 대해서 살펴본 이유는스프링이 DI를 지원해주는 조립기이기 때문이다.실제로 스프링은 앞서 구현한 조립기와 동일한 기능을 제공한다. 즉 스프링은Assembler 클래스의 생성자 코드처럼 필요한 객체를 생성하고 생성한 객체에 의존을 주입해준다.또한 스프링은 Assembler의 getMemberRegisterService나 MemberDao와 같이 특정 타입의 클래스만 생성 할 수있지만 ,스프링은 범용적으로 사용할 수있는 조립기라는 점이다.스프링을 이용한 객체 조립과 사용
Assember대신에 스프링을 사용하는 코드를 작성해본다.resourses/appCtx,xml123456789101112131415161718<?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/beanshttp://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>cs 위 코드는 다음 코드와 같은 결과를 만든다.
spring.MemberDao memberDao = new spring.MemberDao();
spring.MemberRegisterService mebmerRegSvc = new spring.MemberRegistService(memberDao);//생성자로 객체 주입
객체를 생성하고 의존 객체를 주입하는것은 스프링 컨테이너 이므로
설정파일을 이용해서 컨테이너를 생성해야한다. 2장에서 사용했던 GenericXmlapplicationContext 클래스를 이용해서 스프링 컨테이너를
생성가능하다.
아까 Main 코드를 xml을 설정파일을 이용하는 스프링 컨테이너를 사용하는 코드를 구현하면 다음과 같다.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394package 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 br = new BufferedReader(new InputStreamReader(System.in));while (true) {System.out.println("명렁어를 입력하세요:");String command = br.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.setComfirmPassword(arg[4]);if (!req.isPasswordEqualToComfirmPassword()) {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.ChangePasswordService(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();}}cs 둘의 차이는
Assembler 클래스 대신 스프링 컨테이너인 ApplicationContext를 사용했다는 것 하나 뿐이다.
차이가 나는 부분은
ctx = new GenericXmlApplicationContext("classpth:appCtx.xml");
스프링 컨테이너를 생성 , 어셈블러와 차이는
어셈블러는 직접 객체를 생성하지만 GenericXmlApplicationContext는 외부의 설정파일로부터 생성할 객체와 의존 주입대상을 정한다는 점이다.
ctx.getBean("memberRegSvc", MemberRegisterService.class);
스프링 컨테이너로부터 memberRegSvc 인 빈 객체를 구한다.
ctx.getBean("changePwdSvc", ChangePasswordService.class);
스프링 컨테이너로부터 changePwdSvc 인 빈 객체를 구한다.
DI 방식 1 : 생성자 방식
생성자를 통해 의존객체를 주입받는 방식을 사용했다. 앞서 작성한 MemberRegisterService 클래스를 보면 아래 코드 처럼 생성자르 통해 의존객체를받아 필드 (this.memberDao)에 할당했고, 그필드에 할당된 객체의 메소드를 실행했다.123456789101112131415161718192021public 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);}}cs 스프링 XML 설정에 생성자를 이용해서 의존 객체를 주입할때 사용한 태그는 <contructor-arg>태그이다.
123456<bean id="memberDao" class="spring.MemberDao"></bean><bean id="memberRegSvc" class="spring.MemberRegisterService"><constructor-arg ref="memberDao" /></bean>cs 만약 생성자에 의존 객체가 두개 이상이면 <contructor-arg>태그를 순서대로 사용하면 된다.
생성자 파라미터 두개인 예제 실행전에 필요한 코드 추가 .
MemberDao
1234public Collection<Member> selectAll() {return map.values();}cs MebmerPrinter 클래스 추가
12345678910111213package 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());}}cs 이제 생성자로 두개의 파라미터를 전달받는 클래스 작성
파라미터로 MemberDao 객체와 MemberPrint 객체를 전달 받는다.
12345678910111213141516171819202122232425package spring;import java.util.Collection;public class MemberListPrinter {private MemberDao memberDao;private MemberPrinter printer;public MemberListPrinter(MemberDao memberDao, MemberPrinter printer) {super();this.memberDao = memberDao;this.printer = printer;}public void printAll() {Collection<Member>members = memberDao.selectAll();for(Member m : members) {printer.print(m);}}}cs 생성자에 파라메타가 2개이상이면 <contructor-args> 태그를 사용하면 된다.
실제 XML파일에 사용한 예
12345678910<bean id="memberPrinter" class="spring.MemberPrinter"></bean><bean id="listPrinter" class="spring.MemberListPrinter"><constructor-arg ref="memberDao" /><constructor-arg ref="memberPrinter" /></bean>cs appCtx.xml 에 추가
확인을위해 MainForSpring에 코드 추가
else if 분기문 추가
12345678910111213141516171819202122232425public static void main(String[] args) throws IOException {ctx = new GenericXmlApplicationContext("classpath:appCtx.xml");BufferedReader br = new BufferedReader(new InputStreamReader(System.in));while (true) {System.out.println("명렁어를 입력하세요:");String command = br.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;} else if(command.equals("list")) {processListCommand();continue;}printHelp();}}cs 12345//목록을 출력하는 메소드private static void processListCommand() {MemberListPrinter listPrinter = ctx.getBean("listPrinter",MemberListPrinter.class);listPrinter.printAll();}cs 결과
1234567891011121314151617181920212223242526273월 15, 2019 6:25:07 오전 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions정보: Loading XML bean definitions from class path resource [appCtx.xml]3월 15, 2019 6:25:08 오전 org.springframework.context.support.GenericXmlApplicationContext prepareRefresh정보: Refreshing org.springframework.context.support.GenericXmlApplicationContext@66a29884: startup date [Fri Mar 15 06:25:08 KST 2019]; root of context hierarchy명렁어를 입력하세요:new aaa 에이 1111 1111등록했습니다.명렁어를 입력하세요:new bbb 비 2222 2222등록했습니다.명렁어를 입력하세요:new ccc 씨ㅣ 333 3333암호와 확인이 일치하지 않습니다.명렁어를 입력하세요:new ccc 씨 3333 3333등록했습니다.명렁어를 입력하세요:list회원 정보 : 아이디 =1, 이메일= aaa, 이름 =에이 , 등록일=2019-03-15회원 정보 : 아이디 =3, 이메일= ccc, 이름 =씨 , 등록일=2019-03-15회원 정보 : 아이디 =2, 이메일= bbb, 이름 =비 , 등록일=2019-03-15명렁어를 입력하세요:cs DI 방식 2 : 설정 메서드 방식
스프링은 생성자 뿐만 아니라 set으로 시작하는 프로퍼티 설정 메서드를 통해서의존 객체를 주입받는 방법을 지원 하고 있다. 프로퍼티 설정 메서드는다음과 같은 형식을 가진다.메서드이름이 set으로 시작한다.
set 뒤에는 프로퍼티 이름의 첫글자를 대문자로 치환한 이름을 사용한다.
한개의 파라미터를 가지며, 파라미터의 타입은 프로퍼티의 타입이다.
예를 들어 설정하려는 프로퍼티 이름이 memberDao 이고 프로퍼티 타입이 MemberDao라면
이경우 설정 메서드는 다음과 같은 시그니처를 갖는다.
public void setMemberDao(MemberDao Dao)
생성자 방식과 비슷하게 설정 메서드는 파라미터를 전달받은 객첼르 필드에 젖아해서 필요할때 사용한다.
자바빈즈 프로퍼티?
프로퍼티는 빈이 관리하는 데이터
- 프로퍼티 값을 구하는 메서드는 get으로 시작
- 프로퍼티 값을 변경하는 메서드는 set으로 시작
- get과 set 뒤에는 프로퍼티 이름의 첫글자를 대문자로 바꾼 이름을 사용
- set메서드는 1개의 파라미터를 갖는다.
설정 메서드(setter)를 통해서 의존 객체를 주입받는 코드를 작성
1234567891011121314151617181920212223242526272829package spring;public class MemberInfoPrinter {private MemberDao memberDao;private MemberPrinter printer;public void setMemberDao(MemberDao memberDao) {this.memberDao = memberDao;}public void setPrinter(MemberPrinter printer) {this.printer = printer;}public void printMemberInfo(String email) {Member member = memberDao.selectByEmail(email);if(member==null) {System.out.println("데이터없음");return;}printer.print(member);System.out.println();}}cs 설정 메서드 방식으로 app.xml파일에 설정을 추가
1234<bean id ="infoPrinter" class="spring.MemberInfoPrinter"><property name="memberDao" ref="memberDao"/><property name="printer" ref="memberPrinter"/></bean>cs <property>태그의 name속성 값으로 사용한 것이 필드 이름이 아닌 set 메서드 이름인 것을 알 수있다.
관련코드를 추가해서 확인하자
분기문 추가
12345678910111213141516171819202122232425262728public static void main(String[] args) throws IOException {ctx = new GenericXmlApplicationContext("classpath:appCtx.xml");BufferedReader br = new BufferedReader(new InputStreamReader(System.in));while (true) {System.out.println("명렁어를 입력하세요:");String command = br.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;} else if(command.equals("list")) {processListCommand();continue;} else if(command.startsWith("info ")) {processInfoCommand(command.split(" "));continue;}printHelp();}}cs 관련 메소드 추가
12345678910//정보를 출력 메소드private static void processInfoCommand(String[] args) {if(args.length !=2) {printHelp();return;}MemberInfoPrinter infoPrinter = ctx.getBean("infoPrinter",MemberInfoPrinter.class);infoPrinter.printMemberInfo(args[1]);}cs 실행 결과
12345678910111213143월 15, 2019 7:07:50 오전 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions정보: Loading XML bean definitions from class path resource [appCtx.xml]3월 15, 2019 7:07:51 오전 org.springframework.context.support.GenericXmlApplicationContext prepareRefresh정보: Refreshing org.springframework.context.support.GenericXmlApplicationContext@66a29884: startup date [Fri Mar 15 07:07:51 KST 2019]; root of context hierarchy명렁어를 입력하세요:new aaa 김주현 1111 1111등록했습니다.명렁어를 입력하세요:info aaa회원 정보 : 아이디 =1, 이메일= aaa, 이름 =김주현 , 등록일=2019-03-15명렁어를 입력하세요:cs 생성자 VS 설정 메소드
지금까지 두가지 방식을 공부했다 무엇이더 좋을까?물론 장단점이 있다.생성자 방식 : 빈객체를 생성하는 시점에 모든 의존 객체가 주입된다.설정 메서드 방식 : <property> 태그의 name 속성을 통해 어떤 객체가 주입되는지 알 수 있다.각 방식의 장점은 곧 다른 방식의 단점이다.생성자의 파라미터 객수가 많을경우 각 <contructor-arg> 태그가 어떤 의존 객체를 설정하는지 알아내려면 생성자의 코드를 확인해야한다.설정 매서드 방식은 name 속성만으로 어떤 객체를 주입받는 지 알수 잇다.반면에 생성자 방식은 빈객체를 생성하는 시점에 필요한 모든 의존 객체를 주입받기 때문에 , 이후 객체를 사용할때 완전한 상태로 사용가능하다.설정 메서드 방식은 <property> 태그가 누락되어도 빈 객체가 생성되기 때문에 , 객체를 사용하는 시점에 NullPointException이 발생할 수 있다.기본 데이터 타입 값 설정
객체를 주입받기 위해 <contructor arg> 태그와 <property> 태그의 ref 속성을 사용 했다.객체가 아닌 기본 데이터 값은 어떻게 설정 할까버전을 보는 기능
123456789101112131415161718package spring;public class VersionPrinter {private int majorVersion;private int 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);}}cs String은 객체지만 다른 기본 자료형과 같은 방식으로 설정하므로 기본자료형의 범주로 넣어서 생각한다.
둘 다 set이 int 타입 이럴경우ref속성 대신에 value 속성을 이용한다.
appCtx.xml
12345<bean id="versionPrinter" class="spring.VersionPrinter"><property name="majorVersion" value="4"/><property name="minorVersion" value="1"/></bean>cs 확인을 위해 관련코드 추가
1234567891011121314151617181920212223242526272829303132while (true) {System.out.println("명렁어를 입력하세요:");String command = br.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;} else if(command.equals("list")) {processListCommand();continue;} else if(command.startsWith("info ")) {processInfoCommand(command.split(" "));continue;} else if(command.equals("version")) {processVersionCommand();continue;}printHelp();}}private static void processVersionCommand() {VersionPrinter versionPrinter = ctx.getBean("versionPrinter",VersionPrinter.class);versionPrinter.print();}cs 실행결과
123456명렁어를 입력하세요:version이 프로그램의 버전은 4.1입니다.명렁어를 입력하세요:cs <contuctor-arg> 태그에서 value 속성을 사용하려면
생성자에서 기본타입을 주입받는 객체가 있다고 가정하자 .
그리고 이런식으로 처리
<bean id="versionPrinter" class="sprint.VersionPrinter">
<contructor-arg value="4"/>
<contructor-arg value="1"/>
</bean>
(1) 기본 데이터 타입의 변환 처리
value의 타입을 지정하지 않아도 알아서 타입을 변환해 준다.
값을 지정하는 방법은 value태그를 중접해서 사용 할 수도 있다.
<bean id="versionPrinter" class="spring.VersionPrinter"><property name="majorVersion" value="4"/><property name="minorVersion"><value>1</value></property></bean>07 두 개 이상의 설정 파일 사용하기
스프링은 설정파일을 효과적으로 관리하기 위해 스프링은 두개이상의 설정파일을 이용해서 컨테이너를 생성 할 수있도록 하고 있다.2개의 XML 파일을 생성 지금까지 생성한 appCtx,xml을 두개 나눈것이다.conf11234567891011121314<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="memberDao" class="spring.MemberDao"></bean><bean id="memberPrinter" class="spring.MemberPrinter"></bean></beans>cs conf2
en12345678910111213141516171819202122232425262728293031<?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/beanshttp://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>cs 두 개 이상을 사용할때는 배열을 이용하거나 클래스 패스를 따로 설정해준다.
String[]conf = {"classpath:conf1.xml ,"classpath:conf2.xml"};
ctx= new GenericXmlApplicationContext(conf);
바로 파라미터로 전달
ctx= new GenericXmlApplicationContext("classpath:conf1.xml ,"classpath:conf2.xml");
<import>태그 사용
<import>태그는 하나의 XML 설정에서 다른 XML 설정을 함께 사용한다는 것을 지정할때 사용.12345678910111213141516<?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/beanshttp://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>cs 이런식으로 하나에 XML 설정을 포함가능하고
아까 스프링컨테이너를 생성할때 configimport.xml의 클래스 패스만 추가 하면 된다.
'git' 카테고리의 다른 글
GitLab 도입 시 작성한 매뉴얼 (이클립스 위주) (2) 2022.06.24 pull request (0) 2019.04.01