✨KB IT's Your Life✨ 5기 TIL 기자단 활동
KB IT's Your Life 5기에서 학습한 내용을 복습하고자 정리한 글
기간: 8/5 ~ 8/11
학습 내용: Spring 기반지식 및 개요 & Spring MVC

Spring Framework

Java와 밀접하게 관련되어 있고, Java의 기능을 활용하여 강력한 개발 환경을 제공하며 IoC와 AOP를 지원하는 경량의 컨테이너 프레임워크

프레임워크란?

framework = 뼈대, 체제, 틀
애플리케이션 개발을 위한 기반을 제공하여 애플리케이션을 보다 효율적으로 구축할 수 있도록 하는 소프트웨어 구조

프레임워크 사용 ⇒ 개발자는 뼈대를 가져다가 살을 덧붙이는 작업만 하면 된다!

프레임워크의 장점

  • 빠른 구현시간
    • 비즈니스 로직만 구현하면 됨 → 코드에 집중 가능
  • 쉬운 관리
    • 동일한 프레임워크, 유지보수에 들어가는 인력과 시간 절약
  • 개발자들의 역량 획일화
    • 초급 개발자도 프레임워크를 통해 세련되고 효율적인 코드 생성가능
  • 검증된 아키텍처의 재사용과 일관성 유지

Spring 특징

IoC(Inversion of Control)

Inversion of Control = 제어의 역행

객체의 생성과 생명주기를 개발자가 아닌 spring 컨테이너가 관리한다는 점에서 역행/역전이라는 표현 사용!

개발 전체 흐름을 개발자가 아닌 프레임워크가 주도
→ 개발자가 주도하던 게 역행되었다는 의미

JAVA = 객체지향 기반(낮은 결합도 & 높은 응집도)
→ JAVA 프레임워크인 스프링도 낮은 결합도와 높은 응집도 만족 목표

프로젝트에서 IoC 구현 방법

  • XML 설정 파일에서 설정
  • JAVA 파일에서 어노테이션 기반으로 설정

→ 설정해두면 프레임워크가 알아서 DI 해주기 때문에 IoC 적용되는 것

JAVA 설정 방식

  • 코드 기반 설정
  • 장점
    • 타입 안정성
    • IDE 지원
    • 유연성
    • 가독성
  • 단점
    • 초기 학습 곡선
    • 복잡성 증가
import com.multi.spring2_2.config.AppConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        // ApplicationContext: 스프링 컨테이너의 인터페이스
        // AnnotationConfigApplicationContext: 어노테이션을 통해 스프링 컨텍스트를 구성하는 구현 클래스
        // AppConfig.class: 객체 생성 정의되어있는 스프링 설정 자바 파일. 애플리케이션의 Bean 정의와 의존성 설정 포함

        Car car = context.getBean(Car.class);
        // context.getBean(Car.class): 스프링 컨테이너에서 Car.java의 Bean을 Car 클래스 타입으로 가져오는 코드

        car.drive();

    }
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
// 해당 클래스가 스프링 설정 클래스임을 나타내며, Bean 정의를 포함하고 있음을 스프링 컨테이너에 알림
@ComponentScan(basePackages =  "com.multi.spring2_2.java_test")
// 스프링이 지정된 패키지(com.multi.spring2_2.java_test) 내의 컴포넌트를 검색하고 자동으로 Bean으로 등록
// beanPackages 속성은 스캔할 패키지를 지정
public class AppConfig {
	// 외부 클래스 관련 설정
}

XML 설정

  • XML 파일 기반 설정
  • 장점
    • 분리된 설정 파일
    • 표준화된 포맷
    • 레거시 지원
  • 단점
    • 가독성 저하
    • 타입 안정성 부족
    • IDE 지원 제한
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 빈(bean) 정의 -->
    <bean id="engine" class="com.multi.spring2_2.xml_test.Engine"/>
    <bean id="car" class="com.multi.spring2_2.xml_test.Car">
        <property name="engine" ref="engine"/>
        <!-- setEngine() -->
    </bean>
</beans>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // ApplicationContext: 스프링 컨테이너의 인터페이스
        // ClassPathXmlApplicationContext: XML 파일을 통해 스프링 컨텍스트를 구성하는 구현 클래스
        // applicationConext.xml: 스프링 설정 파일. 애플리케이션의 Bean 정의와 의존성 설정 포함

        Car car = context.getBean("car", Car.class);
        // context.getBean("car", Car.class): 스프링 컨테이너에서 car라는 이름의 Bean을 가져오는 코드
        // Car.class -> 반환 타입 지정. car 라는 이름의 Bean을 Car 클래스 타입으로 가져옴

        car.drive();
        // 가져온 Car 객체의 drive() 메서드 호출
    }
}

DI(Dependency Injection)

dependency = 의존성

  • 한 객체가 다른 객체를 필요로 하는 관계

injection = 주입

  • 필요한 의존성을 외부에서 제공해주는 행위
  • 객체가 램의 어느 곳에 위치하는지 주소를 찾아서 넣어주는 것

객체지향 프로그래밍에서 객체가 필요로 하는 의존성을 외부에서 주입해주는 설계 패턴

Spring 프레임워크에서는 DI를 통해 객체 간의 결합도를 낮추고 코드의 유연성과 테스트 용이성 높임

A 객체에서 B 객체를 가져와서 사용하려고 할 때
→ A 객체가 B 객체를 필요로 하는 관계 = 의존성!

해당 객체가 위치하는 램의 주소를 가져와서 넣어주는 것
= 의존성을 주입해주는 것!

지금까지는 개발자가 직접 넣었으나, 스프링 프레임워크를 사용하면 스프링이 자동으로 주소를 찾아와서 넣어준다!!(=의존성을 주입해준다)

의존성은 어떤 라이브러리, 기술 필요한지 명시 필요
명시 방법은 빌드 시스템(Maven, Gradle)에 따라서 약간씩 달라짐에 주의!

기존 DI 과정

Spring을 사용한 DI

기존 방식과 다르게 new를 통해 객체 생성을 하지 않아도 동작한다!
→ Spring이 객체를 생성해주기 때문!

Spring 주요 어노테이션

클래스 역할과 목적에 따라 많은 어노테이션이 존재한다!(응집도를 높여주는 효과)

Spring 내부 구조와 Spring MVC에 대해 이해한 뒤에 각 어노테이션에 대한 설명을 읽어본다면 이해가 더욱 쉬워질 것이라 생각한다!

@Component : 해당 클래스를 싱글턴 객체로 만들어주는 어노테이션(최근에는 잘 사용하지 않음)

컴포넌트 = 구성요소 → 객체지향에서의 구성요소를 의미

객체지향에서 핵심적인 구성요소 = 객체

@Configuration : 스프링 설정 관련 클래스임을 알려주는 어노테이션

Spring MVC 관련

@Controller : 해당 클래스가 Spring MVC에서 컨트롤러 역할임을 알려주는 어노테이션

@Service : 해당 클래스가 Spring에서 서비스(DAO 이전에 위치하는 전처리 담당 계층) 역할임을 알려주는 어노테이션

@Repository : 해당 클래스가 DB와 직접 연결되어 처리 담당하는 DAO 역할임을 알려주는 어노테이션

Spring DI 관련

@Autowired : Spring의 DI 기능을 사용하여 빈 = Bean(객체)을 자동으로 주입하도록 하는 어노테이션(타입 기준)

Auto = 자동, wired = 두리번거리며 찾다
매개변수로 들어간 클래스의 객체가 어디서 만들어졌는지 주소 찾아가서 자동으로 넣어줌

@Inject : 타입 기준으로 빈을 주입하도록 하는 표준 어노테이션

@Named : 이름을 기준으로 구분하여 빈을 주입하도록 하는 어노테이션

독립적 사용 X(@Inject와 함께 사용)

@Resource : 이름을 기준으로 구분하여 빈을 주입하며, 이름이 없을 경우 타입 기준으로 주입하도록 하는 어노테이션

@Qualifier : 동일한 타입의 여러 빈 중 특정 빈을 이름으로 구분하여 주입하도록 하는 어노테이션

독립적 사용 X(@Autowired 또는 @Inject와 함께 사용)

// 예제
MemberService mService; // 보통 메소드 바깥에 필드로 선언해두고 주입해서 사용하는 편
// 해당 변수에 램에 있는 싱글톤 객체 주소 넣는 편
// MemberService 만들 때 바로 클래스로 만드는게 아닌 인터페이스로 정의되어있다고 가정

// 멤버 기능 정의
MemberService implements MInterface
MemberService2 implements MInterface // 추가적 구현
// 상황에 따라 MemberService 여러개 존재 가능

// 주입 시 어떤게 더 좋을까?
MemberService ms; // 클래스 변수에 DI

@Autowired // -> 객체 대상 몇개?
// 대상 = 자식 클래스 객체들 -> 2개
// 2개라서 어떤거에 주입해야할 지 모르겠다! = 에러발생함
// @Qualifier -> 어떤 객체인지 필터링하는것
MInterface mi; // 인터페이스에 DI
// 인터페이스는 업캐스팅 대상
// 확장 여지가 충분하다면 인터페이스에 주입하는게 결합도 낮출 수 있기에 인터페이스에 주입하는게 더 좋다

DI 어노테이션 사용 예제

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class MyService {
	private final MyDao myDao;
	
	@Autowired
	public MyService(@Qualifier("mySecondDao") MyDao myDao) {
			this.myDao = myDao;
	}
	
	public String processData() { 
		return myDao.getData();
	}
}
import javax.inject.Inject; import javax.inject.Named;

@Service
public class MyService {

	@Inject
	@Named("specificRepository")
	private MyRepository myRepository;
	
	@Inject
	public MyService(MyRepository myRepository) {
		this.myRepository = myRepository;
	}
	
	@Resource(name = "mySecondDao")
	public void setMyDao(MyDao myDao) {
		this.myDao = myDao;
	}
}

Spring 주요 디자인 패턴

싱글턴 패턴(Singleton Pattern)

객체지향 설계에서 특정 클래스의 인스턴스가 하나만 생성되어야 할 때 사용하는 디자인 패턴

스프링 프레임워크에서 핵심적인 역할을 하는 Service, DAO, Controller 등만 싱글톤으로 생성
→ Why? 사이트 죽지 않는 한 싱글톤 객체가 램에 계속 상주하기 때문에 서버 부하로 이어지기 때문!

팩토리 패턴(Factory Pattern)

객체를 생성하는 인터페이스를 정의하되, 어떤 클래스의 인스턴스를 생성할지는 서브클래스에서 결정하도록 하는 디자인 패턴

Factory = 공장이라는 이름답게 일정한 틀에 맞춰서 클래스를 생성하고, 내용물은 각 클래스별로 구현을 다르게 하는 패턴이라고 생각하면 이해하기 쉬운 것 같다!

스프링 프레임워크에서는 객체 생성을 스프링이 담당하기 때문에 스프링이 일종의 객체 생성 팩토리인 것!

프록시 패턴(Proxy Pattern)

실제 객체에 대한 접근을 제어하기 위해 대리자(proxy) 객체를 제공하는 디자인 패턴

프록시 객체는 실제 객체와 동일한 인터페이스를 구현하며, 클라이언트는 프록시를 통해 실제 객체와 상호작용

스프링이 나(개발자) 대신 알아서 필요한 메소드 호출해주는 proxy 역할!

의존성 주입 패턴(Dependency Injection Pattern)

객체의 의존성을 외부에서 주입받아 객체 간의 결합도를 낮추고, 코드의 유연성과 테스트 용이성을 높이는 디자인 패턴

스프링은 어노테이션 기반의 DI를 지원!

의존성 주입 방식 3가지

  1. 생성자 주입(Constructor Injection)
    • 객체 생성 시점에 필요한 의존성을 생성자의 매개변수로 주입받는 방식
  2. 세터 주입(Setter Injection)
    • 객체 생성 이후 필요한 의존성을 setter를 통해 주입받는 방식
  3. 인터페이스 주입(Interface Injection)
    • 의존성을 주입받기 위한 메소드를 정의한 인터페이스를 객체가 구현하도록 하는 방식

이하 내용 추가 정리 및 수정 예정

Spring 프로젝트 내부 구조

delegate = 위임하다

프론트 컨트롤러

클라이언트 → 서블릿 엔진에게 동적 처리 관련 요청 전달

톰캣 아래에서 여러개 사이트 운영되고 있다면 어떤 사이트가 응답해야할지 결정 필요(사이트 주소에 따라서 결정)

각 사이트마다 내부에 동적 요청 받는 프론트 컨트롤러 클래스가 존재(HTTP 요청 받고 응답 해주는 역할 → 서블릿으로 생성해야함)

요청 위임 = DispatcherServlet

이미 만들어서 서블릿 들어가있는 상태

요청 받아서 어떤 세부적인 클래스에게 위임해야하는지 찾아서 전달

→ 요청 하나 당 함수(메소드) 하나(대원칙)

사이트 내부적으로 회원, 게시판, 물건, 장바구니 등등에 대한 처리 여러개 존재

세부적으로 도메인별로 존재하는 컨트롤러 여러개 생성

프론트 컨트롤러가 받은 요청 어떤 컨트롤러에게 처리 요청 위임할 것인지 결정 필요

각 컨트롤러마다 요청 담당하는 메소드 포함하고 있음

어떤 컨트롤러의 어떤 메소드 호출해야하는지 내부 처리

프론트 컨트롤러 이미 존재 → 신경 X

컨트롤러에서 어떤 요청 들어왔을 때 메소드 호출해달라고 전달

컨트롤러, 서비스, DAO 생성 필요

DAO → DB 처리하는 특정 프로그램(= 프레임워크) 이미 존재

메소드만 기능별로 만들고 프레임워크 가져다 사용

DAO에 의해 서비스 단에서 결과 받아서 컨트롤러에게 넘김

컨트롤러 메소드가 결과 받은 걸 HTTP 응답으로 만들어서 결과 전달

응답 페이지 = view file

web-app/views 하위에 처리 결과에 따른 응답 페이지 jsp로 작성

세션 - 브라우저 완전 끊어져서 새로운 요청 하기 전까지의 유지하고싶은 데이터

한번 요청하고 한번 응답하기 전까지만 데이터 유지하고 싶은 데이터 = request 객체의 속성으로 지정하면 응답 완료 시마다 객체 소멸

멤버 컨트롤러 → view template까지 보낼 수 있는 객체 = model

프론트 컨트롤러에 특정 요청 들어왔을 때 특정 메소드 호출하게 하는 매핑 클래스 따로 존재 → 자동 등록

프론트 컨트롤러는 요청 들어오면 매핑 클래스에 해당 요청이 어떤 메소드 호출해야하는지 물어보고 호출 담당하는 어댑터에게 해당 컨트롤러의 메소드 호출 요청 위임

스프링에서는 web-app/views 위치 어떻게 찾아가고 많은 jsp 파일 중 어떤 파일 호출해야하는지 어떻게 판단?

→ 호출할 페이지 판단해서 불러주는 클래스 따로 존재(= view resolver?)

파일명 → 멤버 컨트롤러 각 메소드마다 실행 한 뒤 결과 담을 파일명 프론트 컨트롤러에게 String으로 전달

프론트 컨트롤러가 해당 파일명 받아서 views 디렉토리 경로 와 파일명과 접미사 파일확장자 jsp 붙여서 view resolver에게 전달하여 페이지 불러옴?

매핑 클래스, 프론트 컨트롤러, view resolver 이미 생성되어 존재

직접 만들어야 하는 것

멤버 컨트롤러, 서비스, DAO, 결과페이지 JSP

Spring MVC2

<이미지 - 스프링 mvc + 프로젝트 폴더 구조 → 2개 결합해서 어떤 파일이 mvc에서 어떤 역할 하는지 시각적으로 알기 쉽게>

IoC → 웬만한건 스프링 프레임워크에서 자동으로 돌아가게끔 되어있음

스프링 프레임워크가 원하는 위치에 파일 넣어주고, 개발자가 생성한 폴더 스프링이 인식 가능하도록 알려주는 작업 필요

프론트 컨트롤러

앞단에서 동적인 처리 다 받아서 어떻게 세부적 처리 할지 주소가지고 판단해서 어느 단의 어떤 메소드가 처리할지 분배

HTTP 요청/응답 필요하기 때문에 서블릿으로 생성(이미 내장되어 있음?)

⇒ 분배하는 역할의 서블릿 = DispatcherServlet

HTTP 헤더 주소 가지고 어떤 요청인지 판단해서 분배

요청 하나 당 메소드 하나

카테고리별로 컨트롤 생성해두는 편

컨트롤러 안에는 요청 하나 당 메소드 하나로 매핑시켜서 정의해둠

주소별로 메소드 정의하는건 개발자가 해야할 작업

어떤 요청 당 어떤 메소드 호출할지 정의

동적 요청 들어오면

프론트 컨트롤러가 요청 받고 어떤 컨트롤러가 어떤 메소드 가지고 있는지 정보 가지고 있어야 해당 컨트롤러와 메소드 불러서 처리 가능

어노테이션 달아주면 주소만 관리하는 클래스에 등록됨(매핑 핸들러)

요청에 대한 정보 받아서 매핑핸들러에 전달하면서 어떤 컨트롤러의 어떤 메소드에 전달할지

적절한 요청에 대한 메소드호출하는 핸들러 어댑터가 컨트롤러의 메소드 호출

내부적으로 서버 어떤 처리할지 컨트롤러에 넣어두면 X

컨트롤러는 요청 받고, 다른 클래스에서 처리한 결과 받아와서 넘겨주는 작업만 해야함

DB 작업 → DAO필요, DAO 전처리 등 담당하는 서비스 필요

암호화, 비즈니스 로직 등 처리 = 서비스

처리 된 값 DB로 전달하는게 DAO

컨트롤러에서 서비스의 메소드 호출해서 비즈니스 로직 처리

컨트롤러에서 부를 수 있는 메소드 생성

서비스 객체 필요 → 이미 싱글톤으로 생성되어 있어야 함

이미 생성된 서비스객체 컨트롤러에 주입 필요(DI)

⇒ 메소드 컨트롤러에서 호출 가능

DAO 메소드 → 서비스에서도 호출

DAO 주소 싱글톤으로 서비스에 주입 필요(DI)

컨트롤러에 서비스 주입

서비스에 DAO 주입

(DAO에 mybatis 싱글톤 객체 주입)

(mybatis 내에서는 DBCP라는 DB 연결만 담당하는 객체? 주입해서 사용예정)

해서 사용하게 됨

서비스가 전달받은 내용 컨트롤러에게 전달

클라이언트 응답 필요 → 응답 출력 가능한 페이지 생성 필요

클라이언트는 HTML, JSON, String 형태만 받을 수 있기 때문

출력 페이지 지정 필요

브라우저 → HTML 기반

HTML 페이지 만들 수 있는 걸 사용해야 함(JSP, Thymeleaf 등)

출력용 JSP 많이 생성 → 어떤 JSP에 출력할지 알려줘야함

컨트롤러에서 어떤 페이지에 출력할지 String으로 전달하게 됨

데이터 전달 객체 = model 객체(속성으로 전달함?)

출력하는 페이지까지만 전달되고 소멸(하나의 요청이 유지되는 동안만 model 객체 유지 - 응답되면 소멸함)

views 폴더 밑에 출력용 jsp 모아둘 예정

→ 카테고리별로 폴더 만들어서 분류하는 편

임의로 만든 폴더 위치 프론트 컨트롤러가 알고있어야 JSP 호출 가능

파일명과 어떤 데이터 전달할지 모델 통해서 전달

→ View Resolver에게 전달하면 전체 풀 주소 만들어줌(이 과정을 렌더링이라고 함?)

프론트 컨트롤러는 View Resolver에게 파일 위치 전체 주소 전달받고, JSP에 model로 전달받은 데이터 출력해서 HTML 생성된 걸 웹 서버로 전달?

웹서버는 HTTP 응답으로 만들어서 클라이언트에게 전달


Model

애플리케이션의 데이터와 상태를 관리

DB와 상호작용 처리

데이터구조 정의, 데이터 상태와 관련된 로직 포함

View

사용자에게 데이터를 표시하는 역할

모델 데이터 기반으로 사용자 인터페이스 렌더링

사용자 입력 받아서 컨트롤러에게 전달

(전체 패턴 중심으로 정리하는게 중요)

Controller

사용자 요청 처리, 요청에 따라 모델 업데이트하거나 적절한 뷰 선택해서 응답

사용자 요청을 받아서 모델과 상호작용

그 결과를 뷰에 전달하여 사용자에게 응답

@Controller, @RestController, @RequestController 등

Config

애플리케이션 설정 담당(디렉토리)

WebAppInitializer

서블릿 컨테이너 초기화, DispatcherServlet 설정 등

AbstractAnnotationConfigDispatcherServletInitializer 상속 받아서 작성

AppConfig

어떤 걸 싱글턴 bean으로 만들어줘야하는지 메소드 단위로 넣어주게 됨

mybatis 객체 등

사용자 정의용 bean 설정 주로 AppConfig에 들어감

WebConfig

view resolver 객체 → prefix, subfix 붙이는 설정 등

view resolver

InternalResourceViewResolver 객체

setPrefix(), setSuffix() 메소드 존재 → 지정해두면 멤버 컨트롤러가 반환한 파일명 가져다가 붙여서

정적 지원 매핑

내부적으로 이미 존재하는 객체 세팅해서 요청, 전체적인 프로그램에서 설정필요한 부분

WebAppInitializer에 config 설정 파일들 등록

AppConfig 설정

WebConfig 설정

DispatcherServlet 언제 호출될지 메소드 오버라이딩

→ 3개 설정들 클래스 배열로 집어넣음?

AppConfig

외부에서 가져온 객체 관련

외부에서 가져온 객체 여기다 넣어서 빈 만들어달라고 요청하면 싱글톤 객체로 생성됨

빈 하나당 메소드 하나 → @Bean 달아둠?

WebConfig

직접 만들어둔 객체 관련

뷰 리졸버 객체 생성 요청

정적 파일 넣어둔 위치 설정

Service

비즈니스 로직과 데이터 접근 관리

비즈니스 규칙 적용 및 처리

DAO 사용해서 DB와 상호작용

트랜잭션 관리 수행

주의사항

객체 생성

자바 → new 사용

xml → <bean> 사용

객체 생성시 이름 생김 → 이름 가지고 객체 찾아서 변수에 넣어주는 게 아님!

타입(클래스)로 만들어진 객체가 어디있는지를 찾아서 주소 주입하는 것

주입 → 램에서 주소 찾아서 넣는 것

변수에 언제 주입할 것인가?

필드 위에 어노테이션 달아서 바로 주입 가능

생성자 호출 시 필드에 주입

필드에 주입 가능한 메소드 = setter


프로젝트 폴더 구조 관련

첫 페이지에 index.jsp 둘 수도 있지만 views 하위에 다 모아두기도 함(선택사항)

views 하위 파일 → 클라이언트가 a 태그, form 태그로 직접 호출 불가

서버에서 불러서 실행하고 결과 넘겨주는 작업 스프링한테 시켜야 함

→ 첫 페이지용 컨트롤러 따로 필요

일반적으로 환경 등록 컨트롤러, 첫 페이지용 컨트롤러 사용?

카테고리별(구현하고자 하는 도메인별) 패키지(디렉토리) 나눠서 관리

주소 어떻게 지정해야 카테고리별로 나눠서 호출

→ 이름 동일하면 경로 헷갈림

일반적으로 많이 사용하는 방식

member, board → 동일한 CRUD 필요

member/insert, board/insert 형태로 카테고리 이름 붙여서 주소 지정

실습 프로젝트 구조

mapper → mybatis에서 필요한 패키지

공통 모듈 넣어두고

컨트롤러만 따로 모아두는 구조도 사용함

이번 주 수업에 대해서

전공자임에도 백엔드와 Spring에 대해서는 처음 배우는 것이나 다름 없는 입장이었는데, 지난 수업인 Servlet과 JSP에서 확장되어 배우다 보니 이해하는데 도움이 많이 되는 것 같다!
특히 MVC와 Spring 내부 동작 방식에 대한 그림과 실제 프로젝트 디렉토리 구조를 비교해보니 실행 흐름에 대한 이해가 빨리 되었다!

기자단ID카드

profile
256의 우연

1개의 댓글

comment-user-thumbnail
2024년 8월 12일

코드 흐름을 도식화해주셔서 이해가더 잘되네요~ bb

답글 달기