일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- npm
- 국가직
- 성신여대맛집
- 통영여행
- 통영에어비앤비
- 방이편백육분삼십
- 영화추천
- 성북구맛집
- 방이편백육분삼십성신여대
- 퇴사후공무원
- springboot
- 돈암동맛집
- JavaScript
- 자바스크립트에러처리
- 통영예쁜카페
- tomcat7
- 서울숲누룽지통닭구이
- 뚝섬역맛집
- ubuntu자바설치
- 통영
- ELK
- 성신여대편백집
- 파이썬
- 한남동맛집
- 공무원
- 꼴뚜기회
- gradle
- react
- 한성대맛집
- 스페인여행
- Today
- Total
코린이의 기록
[Spring] Spring 4 - 스프링 DI와 예제 프로젝트 본문
※ 이 포스팅은 "초보웹 개발자를 위한 스프링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
Mamber 클래스에서는 회원의 정보 (ID, Email, Password, Name, Register Date)들을 set하고 get하는 메소드와 Password를 변경하는 메소드로 구성되어있다. changePassword에서는 기존의 Password를 저장되어있는 Password정보와 일치하면 새로운 Password로 바꿔주는 메소드이다. Password정보가 일치하지 않으면 throw를 통해 IdPasswordNotMatchingException exception처리를 하는데, 해당 메소드는 아래와 같이 구현한다.
IdPasswordNotMatchingException.java
MemberDao.java
MemberDao는 DB와 같은 기능을 한다고 할 수 있다. 이번 포스팅에서는 DB연동에 대한 내용을 다루지 않을 것이므로 Map을 통해서 일시적으로 데이터를 저장하여 사용한다.
selectByEmail메소드는 mail로 전달받은 정보로 회원을 찾는 메소드이다. DB로치면 Select를 하는 메소드라고 할 수 있다. insert 메소드는 말그대로 멤버변수를 파라미터로 받아서 insert하는 메소드이다. ID는 시퀀셜하게 ++하여 증가시킨다. update 메소드는 마찬가지로 말그대로 멤버변수를 파라미터로 받아서 업데이트 한다.
AlreadyExistingMemberException.java
AlreadyExistingMemberException은 회원을 생성할 때 중복처리를 해주는 exception이다.
RegisterRequest.java
RegisterRequest는 회원가입을 처리할때 필요한 데이터 (email, password, password확인, name)을 담고있는 클래스이다. isPasswordEqualAToConfirmPassword는 처음에 입력한 password와 확인용 password의 값이 일치하는지 확인하는 메소드인데, 흔히 우리가 회원가입 시 비밀번호를 두번 입력하게 되어있는데 이 부분을 의미한다.
MemberRegisterService.java (방법1)
MemberRegisterService는 본격적으로 회원가입을 처리하는 클래스이다. regist 메소드에서 회원 가입시 필요한 데이터인 req를 전달받는다. 먼저 해당 데이터의 이메일을 사용하여 해당 데이터가 이미 존재하는지 확인한다. 없다면 새로운 Member객체를 생성하여 전달받은 req의 email과 password, name을 insert한다.
(중요!!) 추가로 MemberRegisterService 클래스에서 DI(Dependency Injection)에 대한 개념을 집고갈 수 있다.
MemberRegisterService.java (방법2) -> 의존 객체 직접 생성하는 방법 (좋은 방법이 아님)
위 (방법2)는 의존 객체를 직접 생성하는 방식의 클래스이다. (방법1)과 차이점이 있다면,
new를 통하여 MemberDao객체를 직접 생성하였다. 즉 MemberRegisterService 객체를 생성함과 동시에 MemberDao 객체도 동시에 생성시켰다.
반면에 (방법1)는 MemberRegisterService가 의존하는 객체인 MemberDao를 직접 생성하지 않고 생성자를 통해서 의존 객체를 전달받는 방식이다.
그렇다면 왜 DI 방식으로 구현해야 하는가?
이유는 소스 코드 변경 시 효율적으로 소스를 관리하기 위해서이다.
예를들어 우리가 의존 객체를 전달받아 사용하는 방식이 아닌, 직접 생성하는 방식으로 구현을 했다고 하자.
MemberDao라는 객체를 멤버를 등록하는 클래스인 MemberRegisterService에서도 사용하고 멤버 암호 변경하는 클래스인 ChangePasswordService에서 사용한다.
- 멤버를 등록하는 클래스
- 멤버 암호를 변경하는 클래스
그런데 추가 요구사항이 생겼다. 회원 데이터 조회 속도 성능을 높이기 위해 캐시를 사용해야 한다. 그래서 MemberDao클래스를 상속받은 CachedMemberDao 클래스가 새로 만들어졌다.
이렇게 되버리면 위에서 생성해준 의존 객체들의 이름을 모두 바꿔주어야 한다. 예시에서는 MemberRegisterService와 ChangePasswordService 두개뿐이지만 몇십개 혹은 몇백개의 클래스에서 MemberDao 객체를 의존하고 있으면 모두 바꿔주어야하는 작업을 해야한다.
- 멤버를 등록하는 클래스
public class MemberRegisterService{
private MemberDao memberDao = new CachedMemberDao();
...
}
- 멤버 암호를 변경하는 클래스
public class ChangePasswordService{
private MemberDao memberDao = new CachedMemberDao();
...
}
이렇게 의존 객체를 직접 생성하는 경우의 불편함을 확인해보았다. 그렇다면 의존객체를 생성자를 통해 전달받아 사용하는 방식은 어떻게 사용할까? 즉 의존객체는 언제 어디에서 생성해야할까? 의존객체를 전달받는 것은 해당 의존 객체를 쓰고자 하는 각 클래스(MemberRegisterService, ChangePasswordService)에서 하면된다. 생성을 어디서 하느냐가 관건인데, 우선 의존 객체를 생성하고 주입시키는 코드의 예시는 아래와 같다.
만약 캐시를 사용하는 요구사항이 들어오면 위 코드에서 한줄만 수정하면 된다.
MemberDao memberDao = new CachedMemberDao();
MemberRegisterService regSvc = new MemberRegisterService(memberDao);
ChangePasswordService pwdSvc = new ChangePasswordService(memberDao);
이런식으로 의존 객체를 생성하고 수정할 수 있다. 이 의존객체를 생성하는 위치는 (2. 객체 조립하기)에서 언급하도록 한다.
3. 암호 변경
ChangePassowordService.java
changePasswordService에서는 password를 변경해주는 클래스이다. 우선 mail로 등록된 회원인지 검색한 후, 회원이 있으면 password를 변경한후에 update 해준다. 등록된 회원정보가 없으면 throw를 통해 MemberNotFoundException exception처리를 하는데, 해당 메소드는 아래와 같이 구현한다.
MemberNotFoundException.java
2. 객체 조립하기
1. 클래스 생성하기에서 DI와 관련하여 의존 객체 생성에 대하여 설명하였다. 의존 객체를 생성자를 통해 전달받는 방법은 보았는데 그렇다면 어디에 의존객체를 생성해주어야 할까? 바로 두가지 방법이 있다.
- 방법 1. Assembler class 구현
- 방법 2. Spring을 이용하여 객체조립 (-> 결과적으로 우리가 Spring을 사용하는 이유가 된다)
방법 1. Assembler class 구현
의존 객체를 사용하고자 하는 객체와, 의존객체 이 두 객체를 조립한다는 의미에서 assembler라는 이름의 클래스를 별도로 생성하여 이 클래스에서 객체를 생성하고 의존객체를 주입할 것이다.
assembler.java
여기서 유심히 볼 부분은 아래와 같다.
객체를 생성
의존 객체 주입
객체를 생성하고 각 객체(memberDao, regSvc, pwdSvc)가 의존하는 의존 객체(memberDao)를 주입해주었다.
이제 회원 가입을 처리하는 클래스 MemberRegisterService와 회원 password를 변경하는 클래스 ChangePassword를 수행하는 메인클래스를 구현한다.
mainForAssembler.java
방법 2. Spring을 이용하여 객체조립
앞서 설명한 의존, DI, 조립기에 대한 내용들은 Spring을 이용하는 이유에 대한 이해를 돕기위해서 살펴본 내용들이다. Spring이 제공하는 기능을통해 의존 주입(DI)를 편리하게 사용할 수 있다.
Spring을 이용한 객체조립은 .java가 아닌 .xml 파일을 통해 작성한다. 이전 포스팅에서 작성했던 "Spring 4 - Maven 프로젝트 생성"에도 언급하였지만 spring 설정에 대한 파일들은 resources에서 관리하도록 한다. 따라서 관련 xml파일은 resources/appCtx.xml으로 생성하도록 하자.
appCtx.xml
여기서 중요한것은 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
spring 컨테이너 사용하지 않고 assembler.java에서 구현한 방식 MainForSpring.java와의 차이점은 다음과 같이 3부분이다.
1. 객체를 생성하는 방식
-> GenericXmlApplicationContext 객체를 생성하여 appCtx.xml 파일을 읽어와 bean객체를 생성한다.
2. MemberRegisterService 객체
MainForAssembler.java
MainForspring.java
-> memberRegSvc라는 이름을 가진 bean 객체를 구한다.
3. ChangePasswordService 객체
MainForAssembler.java
MainForspring.java
-> changePwdSvc라는 이름을 가진 bean 객체를 구한다.
3. 의존객체 주입 방식
앞서 언급한 의존 객체 생성 및 주입에는 두가지 방식이 있다.
1. 생성자 방식
2. 설정 메소드 방식
3.1 DI 방식 1 : 생성자 방식
우리가 앞에서 봤던 스프링 설정 방식이 바로 DI 방식1. 생성자 방식이다.
그렇다면 의존 객체가 두개 이상일 경우에는 어떻게 설정해야 할까? 스프링 XML 파일에서 <constructor-arg ref=”객체” /> 를 아래줄에 추가해주면 된다. 의존 객체가 두개 이상일 경우에 대한 예시를 위해 아래 파일들을 추가하여 살펴보도록 한다.
<추가할 클래스 및 설정 파일>
- MemberPrinter.java
- MemberListPrinter.java
MemberPrinter.java
멤버의 정보를 출력하는 메소드
MemberListPrinter.java
멤버 정보 리스트를 출력하는 메소드에서 두개의 객체를 전달받고 있다. MemberDao, MemberPrinter
MainForSpring.java
MainForSpring.java에 list 관련된 부분을 추가한다.
processListCommand를 추가하였다. processListCommand에서는 listPrinter라는 이름의 Bean객체를 가져와 MemberListPrinter의 생성자를 통하여 그 객체를 주입한다. listPrinter의 설정은 아래 appCtx2.xml을 참고한다. MemberListPrinter 객체는 memberDao와 memberPrinter에 의존한다.
appCtx.xml
appCtx.xml에 아래 설정을 추가한다.
3.2 DI 방식 2 : 설정 메서드 방식
설정 메서드 방식은 말 그대로 set으로 시작하는 프로퍼티 메소드를 이용하여 의존 객체를 주입하는 방식이다. 이 설정 메서드 방식은 다음과 같은 규칙을 따른다.
- 메소드의 이름은 set으로 시작함
- set 뒤에오는 프로퍼티 이름의 첫글자는 대문자로 치환한다.
- 설정 메소드는 한개의 파라미터를 가지며, 파라미터의 타입은 프로퍼티의 타입이다.
예를들어, 설정하려는 프로퍼티 이름이 "memberDao"이고 프로퍼티 타입이 "MemberDao"라면 아래와 같이 표현할 수 있다.
설정 메소드 방식으로 의존 객체 주입하는 방식에 대한 예시를 위해 아래 파일을 추가하도록 한다.
<추가할 클래스 및 설정 파일>
- MemberInfoPrinter.java
MemberInfoPrinter.java
MemberInfoPrinter.java는 멤버의 정보를 출력하는 클래스이다. MemberListPrinter.java와 차이점이 있다면 둘다 두개의 의존객체 MemberDao와 MemberPrint를 사용하고 있으나, MemberListPrinter.java에서는 두개의 객체를 파라미터로 전달받아 생성자를 통해 두개의 객체를 구했다. 반면에 MemberInfoPrinter.java에서는 설정메소드 setXXX 형태의 메소드를 정의하여 MemberDao와 MemberPrinter 타입의 객체에 의존을 주입하여 사용하도록 하였다.
MemberListPrinter.java (생성자 방식)
MemberInfoPrinter.java (설정 메소드 방식)
설정 메소드를 통해서 의존 객체를 주입하기위해서는 아래와 같은 xml 설정을 해주어 한다. appCtx.xml에서 infoPrinter에 관련된 설정을 추가해준다.
여기서 중요한것은 infoPrinter 라는 이름의 bean객체를 생성해준 후에 property 태그로 각각의 의존 객체를 주입시키고 있다. name="memberDao"가 의미하는 것은, 앞서 설명한 설정 메소드 규칙에따라 앞에 set을 추가하고 프로퍼티이름의 앞글자를 대문자로 치환한 setMemberDao()메소드를 이용하여 ref 속성에 명시되어 있는 객체를 주입하겠다는 의미이다. 마찬가지로 name="printer"가 의미하는것은 setPrinter()메소드를 이용하여 ref속성에 명시되어 있는 객체를 주입하겠다는 의미이다.
MainForSpring.java
멤버 정보를 출력하기위해 MainForSpring에서 info command를 추가한다.
4.기본 데이터 타입값으로 의존객체 설정하기
앞서 언급한 내용은 다른 객체를 주입 할 때 객체 타입으로 설정하였다. 하지만 객체 타입이 아닌 일반 데이터 타입 int, string 등과 같은 타입은 어떻게 설정할까? 해당 예시를 위해 아래 코드를 추가하도록 한다.
VersionPrinter.java
위 소스와 같이 int 타입의 기본 데이터 타입의 파라미터를 설정하였다.
생성자 방식과 설정 메소드 방식 각각 아래와 같이 ref 대신에 value에 integer 값을 넣어주면 된다.
1. 생성자 방식
2. 설정 메소드 방식
name="majorVersion"과 같이 value 속성으로 값을 넣을 수도 있지만 name="minorVersion"과 같이 value tag로 별도로 설정해줄 수 있다.
5. 두 개 이상의 xml 설정 파일 사용하기
xml 설정이 많을 경우 여러개의 xml 설정 파일로 나눈 후 위에서 설정한 appCtx.xml 설정파일을 conf1.xml과 conf2.xml 두 파일로 나누어 보도록 한다.
conf1.xml
conf2.xml
xml 설정 파일을 나눈 후 이 설정파일을 배열을 이용하여 생성하도록 한다.
↓
or
다른 방식으로 import tag 를 사용하는 방식이 있다.
configImport.xml
<import resource = "classpath:conf2.xml" /> 를 추가하면 configImport.xml 파일 한개만 지정해서 사용하면 된다.
'Framework > Spring' 카테고리의 다른 글
[Spring] Spring 4 - 스프링 AOP(Aspect Oriented Programming) 구현 (0) | 2018.05.03 |
---|---|
[Spring] Spring 4 - 의존 자동 주입 (0) | 2018.05.03 |
[Spring] Spring 4 - Maven 프로젝트 생성 (0) | 2018.05.03 |
[Spring Boot] Spring Boot 시작하기 (0) | 2018.05.03 |
[Spring Boot] SSO(Single Sign On), Scalable Authentication Example with JSON Web Token (JWT) and Spring Boot (0) | 2018.05.03 |