비록 시작은 코딩일기지만, 그 끝은 창대하게
어엿한 개발자 블로그로 성장할 수 있도록.
본 게시글은
최범균
저자의 스프링 5 프로그래밍 입문
과
다른 자료들을 참고하여 정리한
Spring Study Contents 입니다.
캡슐화
상속
다형성
Animal
Class or Interface 에서 작은 개념 Bird
, Dog
, Cat
등을 구현OverLoading
, OverRiding
두 가지를 활용해서 다형성을 구현한다.유연
하고 변경
이 용이하다?
세상을 역할
과 구현
으로 구분해보자
운전자
와 자동차
운전자 역할
과 자동차 역할
이 있을 때, 자동차에는 어떠한 자동차가 와도 운전자에 영향을 끼치지 않는다. 차가 바뀌어도 운전자는 그대로 운전할 수 있다.
운전자는 자동차 인터페이스
만 다룰 줄 알면 된다.
즉, 클라이언트에 영향을 끼치지 않으면서 새로운 기능을 제공할 수 있다.
로미오
와 줄리엣
(공연)로미오와 줄리엣 공연에서 로미오, 줄리엣 역할에는 배우 아무나 배역이 가능하다.
어떤 배우든지 그 배역을 맡을 수 있고, 다른 배우로 대체도 가능하다.
이 밖에도 키보드, 마우스 등 표준 인터페이스
, 정렬 알고리즘
등이 있다.
이렇게 역할
과 구현
으로 구분하면 세상이 단순
해지고 유연
해지며 변경이 용이
해진다.
그리고 다음과 같은 장점
을 얻는다.
그렇다면 Java
에서는?
역할
은 인터페이스
구현
은 인터페이스를 구현한 클래스, 구현 객체
객체를 설계할 때 역할
과 구현
을 명확하게 분리하자.
인터페이스(역할)
을 먼저 부여하고, 그 다음 이를 수행하는 객체
를 구현하자.
본질
유연
하게 변경
가능관계
에서 시작해야함인터페이스를 안정적으로 잘 설계하는 것이 굉장히 중요하다.
Robert C. Martin 이 정리한 5가지 원칙
SRP
:: 단일 책임 원칙 (Single Responsibility Principle)
OCP
:: 개방 - 폐쇄 원칙 (Open / Closed Principle)
LSP
:: 리스코프 치환 원칙 (Liskov Substitution Principle)
ISP
:: 인터페이스 분리 원칙 (Interface Segregation Principle)
DIP
:: 의존관계 역전 원칙 (Dependency Inversion Principle)
정의 : 한 클래스는 하나의 책임
만 가져야 한다.
하지만, 여기서 하나의 책임
은 모호하기 때문에 변경이 있을 때 파급효과가 적다면
SRP를 잘 따른것이다.
이 책임을 적당히 잘 배분하는 것이 능력
정의 : 확장
에는 열려
있으나 변경
에는 닫혀
있어야 한다.
다형성
을 활용
-> 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능 구현
하지만 다음과 같은 문제가 있다.
public class MemberService
{
MemberRepository m = new MemoryMemberRepository(); //기존
MemberRepository m = new JdbcMemberRepository(); // 변경
}
MemoryMemberRepository()
에서 jdbcMemberRepository()
로 변경하려면 코드를 변경해야 하는데 이러면 OCP
를 지킬 수 없다.
이를 해결하기 위해서 별도의 조립자
, 설정자
가 필요하다.
그것이 바로 Spring
에서 제공하는 Spring Container
정의 : 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서
하위 타입의 인스턴스로 바꿀 수 있어야 한다.
다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것.
다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면 이 원칙이 필요하다.
예)
정의 : 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
예)
건담
인터페이스 -> 조종
인터페이스, 정비
인터페이스 로 분리사용자
클라이언트 -> 조종사
클라이언트, 정비사
클라이언트 로 분리인터페이스가 명확해지고, 대체 가능성이 높아진다.
정의 : 구현 클래스에 의존하지 말고, 인터페이스에 의존하라
역할
과 구현
중 역할에 의존하게 해야 한다.
객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 구현체에 의존하게 되면 변경이 아주 어려워 진다.
public class MemberService
{
MemberRepository m = new MemoryMemberRepository(); //기존
MemberRepository m = new JdbcMemberRepository(); // 변경
}
사실 앞서 예로 든 이 코드는 DIP
에 어긋난다.
MemberService
가 MemberRepository
에만 의존해야 하는데
MemoryMemberRepository
(또는 JdbcMemberRepository
)
에 까지 의존하기 때문이다.
객체 지향의 핵심은 다형성
이다. 그런데 다형성 만으로는 OCP
와 DIP
를 지킬 수 없다.
무언가 더 필요하다. 그것이 바로 Spring
에서 제공하는 DI
이다.
Dependency Injection, 즉 의존성 주입.
참고로 IoC
의 한 영역으로 마틴 파울러가 주장한 스프링에 사용할 새로운 개념.
의존
이란?의존하는 객체를 직접 생성하지 않고 외부에서 주입 받는 것.
//의존 객체 직접 생성
public class MemberRegisterService
{
private MemberDao memberDao = new MemberDao();
}
//의존 객체 직접 생성하지 않고 생성자를 통해 주입받음. DI 적용
public class MemberRegisterService
{
public MemberRegisterService(MemberDao memberDao)
{
this.memberDao = memberDao;
}
}
의존 객체를 외부에서 주입받으면 나중에 MemberDao()
가 아닌 예를 들어
CachedMemberDao()
로 변경할때 클라이언트의 코드를 변경하지 않아도 된다.
그리고 의존 객체간의 결합도를 낮춘다.
(교재 p57 ~ p58 참고)
MemberDao
Class or Interface 의 실제 구현체 생성과 의존 객체 주입을 누군가 해줘야 한다.
(코드는 교재 p66 ~ p67 참고)
교재에서 작성한 Assembler Class
는 사실
Spring Container
에서 DI Assembler
로 제공된다.
Spring Container
는 개발자 대신에 Bean
을 생성하고 관리하고 제거한다. Spring Container
에서 관리하기 위해서는 객체를 Bean
으로 등록해야 한다.
Bean
?Spring Container
(IoC Container
)가 관리하는 자바 객체를 빈(Bean)이라고 한다. 우리가 new
연산자로 어떤 객체를 생성했을 때 그 객체는 빈이 아니다.Spring
에서의 빈은 ApplicationContext
가 알고있는 객체, 즉 ApplicationContext가 만들어서 그 안에 담고있는 객체를 의미한다.Spring Container
의 종류
Bean Factory
Bean Factory
는 스프링 설정파일에 등록된 Bean 객체를 생성하고 관리하는 기본적인 기능만 제공한다.
컨테이너가 구동될 때 Bean 객체를 생성하는 것이 아니라 클라이언트의 요청에 의해서 Bean 객체가 사용되는 시점(Lazy Loading
) 에 객체를 생성하는 방식을 사용하고 있다.
일반적으로 스프링 프로젝트에서는 사용될 일이 없지만,
Application Context
는 Bean Factory
를 상속받고 있다는 것을 알아두자.
Application Context
Bean Factory
와 마찬가지로, Bean 객체를 생성하고 관리하는 기능을 가지고 있다.
뿐만 아니라 트랜잭션 관리, 메시지 기반의 다국어 처리, AOP
처리 등 DI
와 IoC
외에도 많은 부분을 지원하고 있다.
컨테이너가 구동되는 시점에 객체들을 생성하는 Pre-Loading
방식이 Bean Factory
와 가장 큰 차이점이다.
XML
파일에 직접 등록하기<bean id="aaa" class="xxx.yyy.zzz.AAA">
<property name="prop"></property>
</bean>
위와 같은 방법으로 XML
파일에 직접 등록할 수 있다.
하지만 다음의 문제점이 있다.
빈의 성격 구분을 하기 힘들어진다.
어플리케이션의 크기가 커지면 빈의 양이 늘어 관리하기 버거울 수 있다.
그래서 스프링에서는 클래스에 특정 Annotation
을 부여하고 부여한 클래스를 찾아 자동으로 빈 등록을 해주는 빈 스캐닝 이라는 기능을 제공해준다.
.java
파일을 이용하여 등록하기import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig
{
@Bean
public MemberRepository memberRepository()
{
return new MemberRepository();
}
@Bean
public MemberService memberService()
{
MemberService memberService = new MemberService();
memberService.setMemberRepository(memberRepository());
return memberService;
}
}
별도의 xml
파일을 생성하지 않고 config.java
를 만들어 빈으로 등록할 수 있다.
Class 레벨에는 @Configuration
을 붙이고 Method 레벨에는 @Bean
을 붙이면 된다.
Component Scan
을 이용하여 등록하기필요한 Annotation
을 달면 Spring
의 Component Scanner
가 쭉 훑은 다음 빈으로 등록시킨다.
public class MemberRegisterService
{
public MemberRegisterService(MemberDao memberDao)
{
this.memberDao = memberDao;
}
}
권장하는 이유
DI Container
에서 관리되는 클래스는 특정 DI Container
에 의존하지않고 POJO
여야 한다.DI Container
를 사용하지 않고도 인스턴스화 할 수 있고, 단위 테스트도 가능하다.Constructor Injection
에서는 필드를 final
로 선언할 수 있다.Field Injection
방식은 final
을 선언할 수 없기 때문에 객체가 변경 가능한 상태가 된다.MemberService
가 MemberRepository
없이 제대로 동작할 수 없다면 MemberService
입장에서 MemberRepository
는 반드시 있어야 하는 객체이다.BeanCurrentlyCreationExcepiton
을 발생시켜 순환 의존성을 알 수 있다.@Service
public class MemberService
{
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository)
{
this.memberRepository = memberRepository;
}
}
@Service
public class MemberService
{
@Autowired
private MemberRepository memberRepository;
}
Field Injection
에서는 같은 타입이 2개일때 문제가 발생한다.
@Configuration
public class AppConfig
{
@Bean(name = "modelMapperMain")
public ModelMapper modelMapper()
{
return new ModelMapper();
}
@Bean(name = "modelMapperSub")
public ModelMapper modelMapperSub()
{
return new ModelMapper();
}
}
다음과 같이 타입을 기준으로 주입을 받을 때 같은 타입이 2개라면 에러가 난다.
따라서 다음과 같이 @Qualifier
를 붙여 해결해야 한다.
@Controller
public class XXXController
{
@Qualifier("modelMapperMain")
private ModelMapper modelMapper;
}
@Autowired
관련해서는 chapter04 의존 자동 주입
단원에 자세히 나와있다.