스프링은 자바 기반의 프레임워크이다. 자바의 가장 큰 특징은 객체지향 언어라는 점이다.
그 말인즉슨, 스프링의 가장 큰 특징도 객체지향이다.
자바와 객체지향을 배우지만 순수 자바개발은 생각보다 많이 경험하지 못한다. 그 이유는 결국 그것은 대체해 줄 프레임워크인 스프링이 있기 때문이다.
스프링의 가장 근본이 되는 특징을 말하라고 한다면 IoC, DI 일 것이다.
제어의 역전(IoC)
객체지향 프로그램을 실행하기 위해선 클래스를 선언하고 객체를 생성하여 사용한다. 대표적으로 new 키워드를 활용해 생성한다.
즉, 사용하려는 객체를 선언하고 해당 객체의 의존성을 생성한 후 객체에서 제공하는 기능을 사용한다. 객체가 사용하는 일련의 과정을 개발자가 직접 제어를 한다.
하지만 제어의 역전(IoC)을 특징으로 하는 스프링에서는 제어의 흐름을 개발자가 직접 제어할 필요가 없다. IoC환경에서는 사용할 객체를 직접 생성하지 않고 객체의 생명주기 관리를 외부로 위임한다.
여기에서 외부는 스프링 컨테이너 또는 IoC 컨테이너를 의미한다. 결론적으로 객체의 관리를 컨테이너에 맡겨 제어권이 넘어간 것을 제어의 역전이라 부른다.
스프링은 이러한 제어의 역전 환경에서 의존성 주입(DI) 나 관점지향프로그래밍(AOP)등을 가능하게 해 준다.
의존성 주입(DI)
의존성 주입이란 사용할 객체를 직접 생성하지 않고 외부 컨테이너가 생성한 객체를 주입받아 사용하는 방식을 의미한다. 설명을 보면 알겠지만 제어의 역전 방법 중 하나이다.
의존성 주입을 알기 전에 객체지향에서 '의존'이라는 단어에 대한 이해가 필요하다.
회원가입을 처리하는 로직을 구현한 아래 코드를 이용하여 '의존'을 이해해 보자.
public class MemberRegisterService {
private MemberDao memberDao = new MemberDao();
public void regist(RegisterRequest req){
// 이메일로 회원 데이터 조회(Member)
Member member = memberDao.seletByEmail(req.getEmail);
if(member != null){
// 같은 이메일 존재 시 익셉션 발생
throw new AlreadyExstingMemberException("");
}
Member newMember = new Member(
...
);
// 같은 이메일을 가진 회원이 없으면 DB에 삽입
memberDao.insert(newMember);
}
}
로직을 살펴보면, 해당 서비스 클래스에서 회원가입을 처리하기 위해 MemberDao클래스를 객체로 선언하여 객체의 포함된 메서드를 사용하는 것을 알 수 있다.
즉, 한 클래스에서 다른 클래스의 메서드를 사용할 때, 객체지향에서는 이것을 '의존' 한다고 표현한다. 코드 기준에서는 MemberRegisterService 클래스가 MemberDao 클래스에 의존한다고 표현할 수 있다.
그럼 스프링에서의 의존성 주입도 위와 같은 방법으로 처리되는 것일까?
public class MemberRegisterService(){
// 의존 객체를 직접 생성
private MemberDao memberDao = new MemberDao();
[ ... ]
}
위에서 사용되는 방법은 의존 대상인 MemberDao 객체를 직접 선언하여 할당하였다고 표현한다. 위 방식은 가장 쉽게 의존성을 주입하는 방식이지만 유지보수 관점에서 문제점을 유발할 수 있다. 객체지향 관점에서 본다면 객체를 직접 생성하는 방식은 변경에 있어 문제점을 발생시킨다.
만약 다른 서비스에서 해당 Dao를 사용한다고 하자.
Public class OtherService {
private MemberDao memberDao = new MemberDao();
[ ... ]
}
그리고 MemberDao 클래스가 빠른 조회를 위해 캐시를 적용해야 하는 상황이 발생하였다. 그래서 MemberDao 클래스를 상속받은 CachedMemberDao 클래스를 만들었다.
public class CachedMemberDao extends MemberDao {
[ ... ]
}
캐시 기능이 적용된 해당 클래스를 사용하려면 기존에 MemberDao 클래스를 사용하고 있던 Service들을 모두 변경해줘야 하는 수고가 발생된다. (강한 결합도)
여기서 필요한 것이 DI를 통한 의존 처리이다. 대표적인 의존성 주입처리 방식은 생성자를 이용하는 것이다. 해당 방식을 사용하면 의존하는 객체를 직접 선언하지 않고 의존 객체를 전달받는 방식으로 사용된다.
public class MemberRegisterService {
private MemberDao memberDao;
public MemberRegisterService(MemberDao memberDao) {
this.memberDao = memberDao;
}
public void regist(RegisterRequest req){
[ ... ]
}
}
DI 패턴을 이용하면 위와 같은 상황이 발생하더라도 MemberDao 객체를 사용하는 클래스가 많더라도 변경할 곳은 의존 주입 대상이 되는 객체를 생성하는 코드 한 곳뿐이다.
// 변경 전
MemberDao memberDao = new MemberDao();
MemberRegisterService regSvc = new MemberRegisterService(memberDao);
OtherService thSvc = new OtherService(memberDao);
// 변경 후
MemberDao memberDao = new CachedMemberDao();
MemberRegisterService regSvc = new MemberRegisterService(memberDao);
OtherService thSvc = new OtherService(memberDao);
앞선 방식과 비교하면 변경에 있어 유연함을 제공하는 것을 알 수 있다.
순수자바로 DI 패턴을 적용하려면 사실 더 많은 구현이 필요하다. 하지만 스프링에는 이런 의존성을 쉽게 처리할 수 방법이 구현되어 있다. 대표적으로 아래 세 가지 방법을 제공한다.
- 생성자를 통한 의존성 주입
- 필드 객체 선언을 통한 의존성 주입
- setter 메서드를 통한 의존성 주입
또한, @Autowired라는 어노테이션을 제공해 의존성 주입할 수 있다. (자세한 건 아래 링크 참고)
2023.10.04 - [Framework/Spring] - [Spring] 스프링 프레임워크 vs 스프링 부트
Reference
[스프링부트 핵심가이드]와 [초보 웹 개발자를 위한 스프링 4 프로그래밍 입문] 책을 참고하여 작성하였습니다.
'Framework > Spring' 카테고리의 다른 글
[스진초5기/Spring] 그래서 Controller가 뭐야? (1) | 2023.11.03 |
---|---|
[스진초5기/Spring] Controller, Service, Repository (3계층) (0) | 2023.10.31 |
[Spring] 로그를 남겨주는 라이브러리 - Logback (0) | 2023.10.13 |
[Spring] REST API를 명세하는 방법 - Swagger (1) | 2023.10.11 |
[Spring] POST, PUT, DELETE API (0) | 2023.10.11 |