Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 패스트캠퍼스
- UXUIPrimary
- 환급챌린지
- baekjoon
- 부트캠프
- UXUI기초정복
- Spring
- 국비지원취업
- mysql
- 디자인강의
- 디자인챌린지
- 국비지원교육
- 오블완
- 백준
- Java
- 내일배움캠프
- 백엔드
- 오픈패스
- Be
- 국비지원
- OPENPATH
- 내일배움카드
- 백엔드개발자
- UXUI챌린지
- KDT
- 객체지향
- 디자인교육
- 오픈챌린지
- 티스토리챌린지
- 백엔드 부트캠프
Archives
- Today
- Total
군만두의 IT 공부 일지
[스터디7] 02. 스프링 컨텍스트: 추상화 본문
목차
4장. 스프링 컨텍스트: 추상화
이 장에서 다룰 내용
- 인터페이스를 사용하여 계약 정의하기
- 스프링 컨텍스트에서 빈 추상화 사용하기
- 추상화와 함께 의존성 주입 사용하기
4.1 계약 정의를 위한 인터페이스 사용
- 인터페이스: 자바에서 특정 책임을 선언하는 데 사용하는 추상 구조. '무엇이 발생해야 하는지(필요 대상)'를 지정.
- 인터페이스를 구현하는 객체: '어떻게 그것이 발생해야 하는지(발생 방법)'를 지정.
4.1.1 구현 분리를 위해 인터페이스 사용
- 예시 1) 목적지로 이동하려고 우버(Uber) 같은 차량 공유 앱을 사용할 때,
- 차량 공유 앱 = 인터페이스
- 고객 = 이동을 요청함
- 서비스를 제공할 수 있는 차를 가진 드라이버 = 고객 요청에 응할 수 있음
- 고객과 드라이버는 앱(인터페이스)으로 분리되어 있어 서로에 대해 알 수 없다.
- 예시 2) 배송 앱에서 배송할 패키지의 세부 정보를 인쇄해야 하는 객체를 구현한다고 가정할 때,
- 인쇄된 세부 정보는 목적지 주소별로 정렬되어야 함
- 세부 정보를 인쇄하는 객체는 배송 주소별로 패키지를 정렬하는 책임을 다른 객체에 위임해야 함
- 객체의 책임을 변경할 때 변경된 책임을 사용하는 다른 객체까지 변경할 필요가 없도록 해야 한다.
4.1.2 시나리오 요구 사항
- 예시 3) 팀 업무 관리용 앱을 구현한다고 가정할 때,
- 사용자가 업무에 대한 댓글을 담길 수 있도록 함
- 사용자가 댓글을 게시하면 해당 댓글은 데이터베이스 등 어딘가에 저장되고, 앱은 설정된 특정 주소로 이메일을 보냄
- 이 기능을 구현하려면 객체를 설계하고 올바른 책임과 추상화를 찾아야 한다.
4.1.3 프레임워크 없이 요구 사항 구현
- 서비스(service): 사용 사례를 구현하는 객체
- 요구 사항에서 사용 사례가 댓글 저장과 댓글을 이메일로 보내는 두 가지 행동(action)으로 구성되어 있음을 알 수 있다. 서로 다른 두 책임(responsibility)으로 간주하여 두 개의 객체로 구현해야 한다.
- 리포지토리(repository): 데이터베이스와 직접 작업하는 객체가 있을 때의 객체 이름. 데이터 액세스 객체(Data Access Object)라고도 한다.
- 프록시(proxy): 실제 앱에서 앱 외부와 통신을 담당하는 객체 이름
- 댓글 게시를 구현하는 서비스의 객체 이름은 'CommentService', 댓글 저장 기능을 구현하는 객체 이름은 'CommentRepository', 이메일 전송을 담당하는 객체 이름을 'CommentNotificationProxy' 로 지정한다.
- 의존성을 변경해야 할 때, 의존성을 사용하는 객체까지 변경할 필요가 없도록 CommentService를 의존성 구현과 확실하게 분리해야 한다.
public interface CommentRepository {
void storeComment(Comment comment);
}
public class DBCommentRepository implements CommentRepository {
@Override
public void storeComment(Comment comment) {
System.out.println("Storing comment: " + comment.getText());
}
}
public interface CommentNotificationProxy {
void sendComment(Comment comment);
}
public class EmailCommentNotificationProxy implements CommentNotificationProxy {
@Override
public void sendComment(Comment comment) {
System.out.println("Sending notification for comment: " + comment.getText());
}
}
public class CommentService {
private final CommentRepository commentRepository;
private final CommentNotificationProxy commentNotificationProxy;
public CommentService(
CommentRepository commentRepository,
CommentNotificationProxy commentNotificationProxy
) {
this.commentRepository = commentRepository;
this.commentNotificationProxy = commentNotificationProxy;
}
public void publishComment(Comment comment) {
// '댓글 저장'과 '알림 전송' 책임을 의존성에 위임
commentRepository.storeComment(comment);
commentNotificationProxy.sendComment(comment);
}
}
4.2 추상화와 함께 의존성 주입
4.2.1 스프링 컨텍스트에 포함될 객체 정하기
- 스프링 컨텍스트에 객체를 추가하는 이유: 스프링이 객체를 제어하고 프레임워크가 제공하는 기능으로 객체를 보강할 수 있도록 하는 것
- 객체가 컨텍스트로부터 주입해야 하는 의존성이 있거나 그 자체가 의존성인 경우 해당 객체를 스프링 컨텍스트에 추가해야 한다.
- 객체 예시:
- CommentService: CommentRepository와 CommentNotificationProxy 의존성 두 개를 갖고 있다.
- DBCommentRepository: CommentRepository 인터페이스를 구현하며 CommentService의 의존성이다.
- EmailCommentNotificationProxy: CommentNotificationProxy 인터페이스를 구현하며 CommentService의 의존성이다.
- 스프링 컨텍스트에 객체를 추가하면 프레임워크가 제공하는 특정 기능을 사용하여 객체를 관리할 수 있다. 프레임워크에서 얻는 이점도 없는데 스프링이 관리할 객체만 추가하는 것은 오버엔지니어링(over-engineering) 구현을 하는 것이다.
@Component
public class DBCommentRepository implements CommentRepository {
@Override
public void storeComment(Comment comment) {
System.out.println("Storing comment: " + comment.getText());
}
}
@Component
public class EmailCommentNotificationProxy implements CommentNotificationProxy {
@Override
public void sendComment(Comment comment) {
System.out.println("Sending notification for comment: " + comment.getText());
}
}
@Component
public class CommentService {
private final CommentRepository commentRepository;
private final CommentNotificationProxy commentNotificationProxy;
public CommentService(
CommentRepository commentRepository,
CommentNotificationProxy commentNotificationProxy
) {
this.commentRepository = commentRepository;
this.commentNotificationProxy = commentNotificationProxy;
}
public void publishComment(Comment comment) {
commentRepository.storeComment(comment);
commentNotificationProxy.sendComment(comment);
}
}
@Configuration
@ComponentScan(basePackages = {"proxies", "services", "repositories"})
public class ProjectConfiguration {
}
4.2.2 추상화에 대한 여러 구현체 중에서 오토와이어링할 것을 선택
- 서로 다른 두 클래스로 생성된 빈이 두 개 있고 이 두 빈이 CommentNotificationProxy 인터페이스를 구현한다고 가정할 때, 스프링은 빈 선택 메커니즘을 사용한다.
- @Primary 애너테이션으로 구현할 빈 중 하나를 기본값으로 표시한다.
- @Qualifier 애너테이션으로 빈 이름을 지정한 후 DI를 위해 해당 이름으로 참조한다.
@Component
public class CommentPushNotificationProxy implements CommentNotificationProxy {
@Override
public void sendComment(Comment comment) {
System.out.println(
"Sending push notification for comment: " + comment.getText()
);
}
}
@Primary로 주입에 대한 기본 구현 표시하기
@Component
@Primary
public class CommentPushNotificationProxy implements CommentNotificationProxy {
@Override
public void sendComment(Comment comment) {
System.out.println(
"Sending push notification for comment: " + comment.getText()
);
}
}
@Quilfier로 의존성 주입에 대한 구현 이름 지정하기
@Component
@Qualifier("PUSH")
public class CommentPushNotificationProxy implements CommentNotificationProxy {
// 코드 생략
}
@Component
@Qualifier("EMAIL")
public class EmailCommentNotificationProxy implements CommentNotificationProxy {
// 코드 생략
}
@Component
public class CommentService {
private final CommentRepository commentRepository;
private final CommentNotificationProxy commentNotificationProxy;
public CommentService(
CommentRepository commentRepository,
@Qualifier("PUSH") CommentNotificationProxy commentNotificationProxy
) {
this.commentRepository = commentRepository;
this.commentNotificationProxy = commentNotificationProxy;
}
// 코드 생략
}
4.3 스테레오타입 애너테이션으로 객체의 책임에 집중
- 실제 프로젝트에서는 스테레오타입 애너테이션을 명시적으로 사용하여 컴포넌트 목적을 정의한다.
- @Component가 사용되며 구현하는 객체의 책임에 대한 세부 정보는 제공하지 않는다.
- 서비스(@Service)는 사용 사례를 구현하는 책임이 있는 객체이며, 리포지토리(@Repository)는 데이터 지속성을 관리하는 객체이다.
- 세 가지(@Component, @Service, @Repository) 모두 스테레오타입 애너테이션이며 스프링이 애너테이션된 클래스의 인스턴스를 생성하고 스프링 컨텍스트에 추가하도록 지시한다.
이 글은 『스프링 교과서』 책을 학습한 내용을 정리한 것입니다.
'프로그래밍 > Java' 카테고리의 다른 글
[스터디7] 04. 스프링 부트와 스프링 MVC 이해 (0) | 2025.05.06 |
---|---|
[스터디7] 03. 스프링 컨텍스트: 빈의 스코프 및 수명 주기 (0) | 2025.04.29 |
[스터디7] 01. 스프링 컨텍스트: 빈 정의 (0) | 2025.04.11 |
[스터디4] 09. 자바 21에서 강화된 언어 및 라이브러리 (0) | 2025.04.02 |
[스터디4] 08. 스트림 요소 처리 (0) | 2025.03.21 |
Comments