[Spring] 스프링 구조와 의존성

보람·2023년 4월 24일
0

Spring

목록 보기
2/18

1. 스프링 MVC 구조

🙆‍♀️ 알아두기

파란색 : spring framewor가 관장하는 방식
보라색 : 개발자가 관장하는 부분(MVC 패턴)
초록색 : view = html or jsp 순수 html 사용 비중 증가

① 클라이언트가 request -> 프론트 컨트롤러 역할의 dispatcher servlet은 그 요청을 처리하기 위한 컨트롤러 객체를 검색
② dispatcher servlet이 직접 컨트롤러를 검색하지 않고 HandlerMapping이라는 빈 객체에게 컨트롤러 검색을 요청

HandlerMapping은 클라이언트의 요청 경로를 이요해서이를 처리할 컨트롤러 빈 객체를 dispatcherservlet에 전달

ex. 웹 요청 경로가 /hello -> 컨트롤러 빈 중 /hello요청 경로를 처리할 controller 리턴

③ dispatcher servlet은 handler mapping이 찾아준 컨트롤러 객체를 처리할 수 있도록 handlerAdapter 빈에게 요청 처리를 위임

④ handlerAdapter는 controller의 알맞은 메서드를 호출해서 요청을 처리

⑤ 컨트롤러 처리 결과를 리턴

⑥ HandlerAdapter로 부터 컨트롤러의 요청 처리 결과를 받으면 dispatcher Adapter는 결과를 보여줄 뷰를 찾기 위해 view resolver 빈 객체 사용 -> view resolver는 이 뷰 이름에 해당하는 view 객체를 찾거나 생성해서 리턴

⑦ dispatcherservlet은 view resolver가 리턴한 view 객체에게 응답 결과 생성을 요청

⑧ view 객체는 웹 브라워에 응답결과를 생성

🙆‍♀️ 알아두기

Model : 사용자가 원하는 데이터나 정보를 제공(db)

dto : 양쪽으로 전송되어 오고가는 데이터들을 담은 객체
dao : 데이터에 접근, 데이터를 관리하기 위한 객체
서비스 : 핵심 비즈니스 로직 구현

View : 보여지는 화면
Controller : 사용자의 요청을 처리하고, 그 요청에 따른 전체적은 흐름을 제어

2. 웹 애플리케이션의 계층 구조

컨트롤러는 서비스의 기능을 사용하며, 서비스에 의존
서비스는 리포지토리의 기능을 사용하며, 리포지토리에 의존

  • 컨트롤러 : http 요청 처리, 웹 MVC의 컨트롤러 역할
  • 서비스 : 핵심 비즈니스 로직 구현
  • 레파지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리한다.
  • 도메인 : 비즈니스 도메인 객체

3. 의존성

(1) 제어역전

  • IoC, Inversion of Control

  • 제어의 흐름이 기존에는 개발자가 작성한 코드에서 객체를 생성하고 관리하는 방식에서 프레임워크나 컨테이너로 넘어가게 되는 것

  • 개발자가 프레임워크 기능을 호출하던 것에서 -> 프레임워크가 개발자 코드를 호출

    • 제어의 흐름 : 개발자가 작성한 코드 -> 프레임워크
    • 개발자 -> 전체를 직접 구현 x, 자신의 코드를 부분적으로 "끼워넣기" 하는 형태로 구현
    • 개발자는 비즈니스 로직에 집중 가능
  • 프레임워크 기능

    • 객체의 생성, 소멸과 같은 라이프 사이클을 관리
    • 스프링으로부터 필요한 객체를 얻어올 수도 있음
    • 즉, 프레임워크가 개발자가 작성한 코드를 호출하면서, 필요한 객체를 생성하고, 관리하고, 필요한 때에 호출

    💡 예시

    • 서비스 객체가 리포지토리 객체를 사용
    • 기존 : 서비스 객체가 직접 리포지토리 객체를 생성하고 사용
    • 제어 역전 후 :
      • 리포지토리 객체의 생성과 관리를 프레임워크에 맡김
      • 서비스 객체는 직접 리포지토리 객체를 생성하거나 호출하지 않고, 프레임워크에서 제공하는 기능을 이용하여 사용
  • 결론 : 객체의 의존성을 역전 함으로서 객체간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여 가독성 강화 및 코드 중복 최소화, 편리한 유지보수 할 수 있게 함

📌 스프링 프레임워크에서 객체를 생성하고 관리하는 곳 : 컨테이너

(2) Spring Container

  • 스프링은 실행 시 객체들을 담고 있는 Container가 존재
    • 자바객체 = Bean
    • 자바 객체의 생명 주기 관리
      • 개발자는 객체를 생성 소멸할 수 있는 데 스프링 컨테이너가 이 역할을 대신 해줌
      • 즉, 제어의 흐름을 개발자 x -> 프레임워크가 관리
    • 생성된 자바 객체들에게 추가적인 기능을 제공
    • 객체들 간의 의존관계를 스프링 컨테이너가 런타임 과정에서 만듦

(3) MemberController

  • 생성자에 @Autowired -> 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어줌
  • 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependecy Injection) = 의존성 주입
  • 이전에는 개발자가 직접 주입 -> 현재, @Autowired에 의해 스프링이 주입

📌 @Autowired

  • 필요한 의존 객체의 "타입"에 해당하는 빈을 찾아 주입
  • 기본값이 true이기 때문에 의존성 주입을 할 대상을 찾지 못한다면 애플리케이션 구동에 실패

📌 POJO

  • Plain Old Java Object, 단순한 자바 오브젝트
  • 객체 지향적인 원리에 충실하면서 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로 설계된 오브젝트

(4) 컴포넌트 스캔의 대상

  • Component Scan

    • 어노테이션을 사용하여 스프링에서 관리하는 Bean으로 등록
    • 자동으로 이루어지며 스프링 컨테이너가 지정된 패키지에서 모든 클래스를 검색하고, 스프링에서 관리하는 Bean으로 등록
  • 컴포넌트 스캔의 대상

    • main메서드가 있는 class의 동일 패키지 또는 하위 패키지만 spring이 scan
      • 예를 들어, main 메서드가 있는 클래스가 "com.example" 패키지에 있다면, "component scan"은 "com.example" 패키지와 그 하위 패키지에서만 검색을 수행
    • @Component, @Controller, @Service, @Repository 등이 지정된 클래스를 검색하여 Bean으로 등록

⭐ (5) 의존성 주입 방법

  • 의존성 주입(Dependency Injection, DI) : 제어역전을 구현하는 방법
  • 객체간의 의존성을 낮추기 위해 외부에서 필요한 객체를 주입받는 방식

① Field Injection (필드 주입)

- 순환참조 때문에 권장 x
	@Autowired 
    private MemberRepository memberRepository;

② Setter Injection (수정자 주입)

- 바뀔(수정될) 가능성이 있으므로 사용 x
	private MemberRepository memberRepository;

 	@Autowired
	 public void setMemberRepository(MemberRepository memberRepository) {
     		this.memberRepository = memberRepository;
 	 } 

③ Constructor Injection (생성자 주입)

- 가장 많이 사용되는 방법
	private final MemberRepository memberRepository;
	
	@Autowired
	public MemberService(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}
    
	
	// 현재 클래스에서만 접근 가능하도록 막아두기
	// 의존 관계가 한번 만들어지면 아예 수정이 불가능한 상태로 만들어주기

⭐ 4. SOLID

  • SOLID : 클린코드로 유명한 로버트 마틴이 정리한 좋은 객체 지향 설계의 5가지 원칙을 정리
  • 스프링 프레임워크는 SOLID 원칙을 지키도록 하여, 유지보수, 확장성, 재사용성, 테스트 용이성 등을 보장하며, 객체지향적으로 프로그래밍하는 데 도움을 줌
    • SRP : 단일 책임 원칙 (Single responsibility principle)
    • OCP : 개방-폐쇄 원칙 (Open-closed principle)
    • LSP : 리스코프 치환 원칙 (Liskov substitution principle)
    • ISP : 인터페이스 분리 원칙 (Interface segregation principle)
    • DIP : 의존관계 역전 원칙 (Dependenct inversion principle)

(1) SRP : 단일 책임 원칙

  • 한 클래스는 하나의 책임만
  • 하나의 책임이라는 것은 모호
    - 클 수도 있고, 작을 수도
    - 문맥과 상황에 따라 다름
  • 어떻게 하는 게 설계가 잘 된건지 중요한 판단 기준은 변경
  • 변경이 있을 때 파급효과가 적으면 단일 책임 원칙을 잘 따른 것
  • EX) ui 변경할 때 하나 바꾸면 다른 거 다 바꿔야 하는 것 ❌

(2) OCP : 개방-폐쇄 원칙

  • 클래스가 확장에는 열려있고, 수정과 변경에는 닫힘
  • 수정과 변경은 오류를 야기할 수 있으므로

    기존 코드 변경 없이 코드 확장하는 법
    1. 다형성 활용
    인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현 -> 기존 코드 변경 x
    2. 스프링 활용
    객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자 = 스프링

(3) LSP : 리스코프 치환 원칙

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위타입의 인스턴스로 바꿀 수 있어야 함
  • 다형성에서 하위클래스는 인터페이스 규약을 다 지켜야 함
    • 인터페이스를 구현한 구현체를 믿고 사용하려면, 이 원칙이 필요
    • 단순히 컴파일에 성공하는 것을 넘어서 이야기

      EX. 악셀 구현 -> 악셀은 앞으로 가야함 BUT, 뒤로 가게 구현 한다면?
      컴파일 오류는 없음 -> BUT, 인터페이스 규약이 깨짐 -> 인터페이스 규약을 지켜줘야됨

(4) ISP : 인터페이스 분리 원칙

  • 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 나음
  • 인터페이스가 명확해지고, 대체 가능성이 높아짐

    EX. 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
    인터페이스를 분리하면 정비 인터페이스 자체가 변해도 운전 인터페이스는 영향을 주지 않는다.

(5) DIP : 의존관계 역전 원칙

  • 프로그래머는 추상화에 의존해야지 구체화에 의존하면 ❌
  • 의존성 주입은 이 원칙을 따르는 방법 중 하나
  • 즉, 구현 클래스에 의존하지 말고 인터페이스에 의존할 것

    EX. 운전자가 자동차역할에 대해 잘 알아야지 k3, 테스라 각각에 집중하면 운전이 힘들어 짐

profile
안녕하세요, 한보람입니다.

0개의 댓글