비록 시작은 코딩일기지만, 그 끝은 창대하게
어엿한 개발자 블로그로 성장할 수 있도록.
본 게시글은
최범균 저자의 스프링 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 의존 자동 주입 단원에 자세히 나와있다.