AWS Back Day 66. "Spring Boot 시작하기 : 초보자를 위한 튜토리얼 및 단계별 가이드"

이강용·2023년 4월 5일
1

Spring Boot

목록 보기
1/20
Typing SVG
  • 엔터프라이즈용 Java 애플리케이션 개발을 편하게 할 수 있게 해주는 오픈소스 경량급 애플리케이션 프레임워크

Spring Boot의 특징

  • 빠른 개발 및 실행 : 내장된 웹 서버를 포함하고 있어 별도의 웹 서버 설치 및 구성 없이 애플리케이션을 실행할 수 있음, 이를 통해 개발 및 테스트 시간을 절약할 수 있음.

  • 의존성 관리 : 버전이 호환되는 의존성들을 모아놓은 스타터(Starter) 모듈을 제공, 이를 사용하면 개발자는 복잡한 의존성 관리를 걱정하지 않고 필요한 기능을 쉽게 추가할 수 있음.

  • 자동 구성(Auto-configuration) : 애플리케이션의 특성과 의존성에 따라 기본 설정을 자동으로 적용. 이를 통해 설정 작업을 줄이고 개발 속도를 향상시킬 수 있으며, 필요한 경우 수동으로 설정을 변경할 수도 있음.

  • 프로덕션 레디 : 프로덕션 환경에서 필요한 기능들을 제공. 예를 들어, 모니터링 및 관리를 위한 Actuator, 메트릭 수집, 로깅 설정 등의 기능이 내장되어 있음.

  • 다양한 플러그인 및 확장성 : 다양한 빌드 플러그인을 지원하며, 그레이들(Gradle)이나 메이븐(Maven)과 같은 빌드 도구와 통합하기 쉬움. 또한, Spring 생태계의 다양한 프로젝트와 호환되어 높은 확장성을 제공.

  • 컨벤션 오버 설정(Convention over Configuration) : 개발자가 꼭 필요한 설정만 하도록 설계되어 있어, 개발자가 신경 써야 할 설정 요소를 줄여줌. 이를 통해 개발자는 비즈니스 로직에 더 집중할 수 있음.

  • POJO 지향 개발 : 프레임워크와의 결합도를 낮추고 순수한 자바 객체를 사용하여 유지보수와 테스트 용이성을 향상시킴.

POJO (Plain Old Java Object)란 무엇인가?

  • Java 프로그래밍 언어에서 사용되는 용어로, 간단하고 기능이 제한된 객체를 말함. POJO는 특별한 제약이나 규칙 없이 일반적인 Java 클래스와 객체를 정의하며, 주로 데이터 저장, 전달 및 조작을 담당하는 역할을 함

POJO의 특징

  • 유지보수성 : POJO를 사용하면 애플리케이션의 코드가 간결하고 이해하기 쉬워짐. 이로 인해 유지보수가 쉬워지고 프로젝트의 수명이 연장.
  • 테스트 용이성 : POJO는 특정 프레임워크나 라이브러리에 종속되지 않기 때문에 테스트가 쉬워짐. 따라서 단위 테스트를 작성하고 실행하는 데에 드는 비용이 줄어들게 됨
  • 이식성 : POJO를 사용하면 다른 기술이나 프레임워크로의 이식이 쉬워짐. 이는 기술의 변화에 따른 유연성을 확보할 수 있게 함

POJO 예시

public class Student {
    private String name;
    private int age;

    // Getter and Setter methods
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Builder Pattern을 적용해보자

public class Student {
    private final String name;
    private final int age;

    private Student(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public static class Builder {
        private String name;
        private int age;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Student build() {
            return new Student(this);
        }
    }
}

그렇다면, Builder Pattern을 사용했을 때에도 POJO를 지향한다고 할 수 있을까??? (feat. chatGPT)

chatGPT의 답변

  • 빌더 패턴을 적용한 경우에도 POJO를 지향한다고 할 수 있습니다. 빌더 패턴은 객체 생성과 관련된 로직을 구분하여 가독성유지보수성을 향상시키는 패턴입니다. 이 예에서도 빌더 패턴이 적용된 Student 클래스는 여전히 순수한 자바 객체로, 특정 프레임워크나 기술에 종속되지 않습니다.

  • 빌더 패턴을 사용하면 객체 생성 과정이 더 명확해지고, 각 필드의 설정을 체이닝(chaining) 방식으로 구현할 수 있어 가독성이 좋아집니다. 이는 `POJO의 핵심 원칙에 어긋나지 않으며, 오히려 코드의 간결성과 유지보수성을 높이는 효과를 줍니다.


위와 같은 예를 롬복(lombok)을 적용하면 POJO를 지향한다고 할 수 있을까? (feat.chatGPT)

롬복(lombok)을 적용한 Builder Pattern

import lombok.Getter;
import lombok.Setter;
import lombok.Builder;

@Getter
@Setter
@Builder
public class Student {
    private String name;
    private int age;
}

chatGPT의 답변

  • 롬복을 사용한 경우에도 POJO를 지향한다고 할 수 있습니다. 롬복은 주로 boilerplate 코드(반복적으로 작성되는 코드)를 줄이기 위해 사용되는 라이브러리입니다. 롬복은 어노테이션을 통해 getter, setter, builder 등을 자동으로 생성해주어 코드의 간결성을 높이고 유지보수성을 향상시킵니다.

  • 롬복을 사용한 Student 클래스 역시 순수한 자바 객체이며, 특정 프레임워크나 기술에 종속되지 않습니다. 롬복은 컴파일 시점에 코드를 생성하기 때문에, 실행 시점에는 생성된 코드만 존재하게 됩니다. 이런 방식으로 코드의 간결성을 유지하면서도 필요한 기능을 제공합니다.

  • 따라서 롬복을 사용한 경우에도 POJO를 지향하는 것이 맞습니다. 다만, 롬복은 코드 생성이 컴파일 시점에 이루어지기 때문에, 롬복이 제공하는 어노테이션을 이해해야 코드를 올바르게 해석할 수 있습니다. 이 점을 감안하면서 사용하면, 롬복을 통해 POJO를 지향할 수 있습니다.


Spring Boot 설치

스프링 부트 사이트

Ctrl + Enter

Github Repository 생성

설치파일 작업 환경으로 가지고 오기

C:\junil\AWS_LKY\workspace\SpringBoot_study

작업 환경 > 오른쪽 우클릭 > Git Bash Here

Git Bash에 입력하기

echo "# AWS_SpringBoot_20230405" >> README.md
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/leesfact/AWS_SpringBoot_20230405.git
git push -u origin main

Github에 정상 등록된 모습

SOURCETREE 방법

갖고 있는 파일 탐색버튼으로 업로드 후 추가


SpringBoot를 실행시켜 보자!

CTRL+F11

pom.xml Spring Boot 버전 변경

ALT + F5

새 패키지 생성

새 클래스 생성

POSTMAN 접속

controller (package) > HelloController (Class)

package com.web.study.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@GetMapping("/hello")
	public ResponseEntity<String> hello() {
		return ResponseEntity.ok("Hello Spring Boot !!!");
	}
}

CTRL + F11 실행

PID 번호 및 Tomcat 자동실행 확인(Tomcat이 내장됨)

POSTMAN

http://localhost:8080/hello

package com.web.study.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@GetMapping("/hello")
	public Map<String, String> hello() {
		
		Map<String, String> testMap = new HashMap<>();
		testMap.put("name","이강용");
		testMap.put("age", "32");
		testMap.put("address", "창원시 의창구");
		return testMap;
	}
}

실행 화면

  • 자동으로 json 형태로 날아감

응용 예

package com.web.study.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@GetMapping("/hello")
	public Map<String, String> hello(String name) {
		
		Map<String, String> testMap = new HashMap<>();
		testMap.put("name", name);
		testMap.put("age", "32");
		testMap.put("address", "창원시 의창구");
		return testMap;
	}
}

postman에서 name = 홍길동으로 요청

MVC란 무엇인가??

  • MVC( Model, View, Controller) 는 소프트웨어 설계에서 널리 사용되는 아키텍처 패턴이다.
  • 이 패턴은 애플리케이션의 구조를 논리적으로 세 가지 컴포넌트로 나누어 관심사의 분리(Separation of Concerns)를 달성하고 코드의 재사용성, 유지보수성을 향상시킨다.
요소설명
Model데이터와 비즈니스 로직을 나타내는 부분으로, 애플리케이션의 핵심 정보를 저장하고 처리합니다.
View사용자 인터페이스를 담당하는 부분으로, Model의 데이터를 시각적으로 표현합니다.
ControllerModel과 View 사이의 중재자 역할을 하는 부분으로, 사용자의 입력을 받아 Model의 상태를 변경하거나, 적절한 View를 선택하여 응답을 반환합니다.
  • MVC 패턴은 애플리케이션의 각 부분이 서로 독립적으로 동작할 수 있도록 설계되어 있어, 개발 및 유지보수가 용이함. 예를 들어, 사용자 인터페이스를 수정하려 할 때 ModelController는 영향을 받지 않습니다. 이러한 분리를 통해 개발 효율성과 코드의 재사용성이 증가함.

Spring MVC란 무엇인가?

  • Spring Framework에서 제공하는 웹 애플리케이션 개발을 위한 모델-뷰-컨트롤러(Model-View-Controller, MVC) 아키텍처 패턴을 구현한 모듈.
구성 요소설명
DispatcherServlet스프링 MVC의 핵심 구성 요소로, 모든 클라이언트 요청을 받아 적절한 컨트롤러로 전달하는 프론트 컨트롤러 역할을 수행합니다.
Controller클라이언트의 요청을 처리하고, Model의 상태를 변경하거나 적절한 View를 선택하여 응답을 반환하는 역할을 합니다. 스프링 MVC에서는 컨트롤러를 개발자가 직접 구현할 수 있습니다.
Model애플리케이션의 비즈니스 로직과 데이터를 포함하는 부분으로, 일반적으로 POJO(Plain Old Java Object)를 사용하여 구현합니다.
View클라이언트에게 보여지는 사용자 인터페이스를 구성하는 부분으로, 주로 JSP(Java Server Pages), Thymeleaf, FreeMarker 등의 템플릿 엔진을 사용하여 구현됩니다.
Handler Mapping클라이언트의 요청을 실제로 처리할 컨트롤러를 찾아주는 역할을 합니다. 스프링 MVC에서는 다양한 유형의 핸들러 매핑 전략을 제공합니다.
  • 스프링 MVC를 사용하면, 개발자는 이러한 구성 요소들을 활용하여 웹 애플리케이션을 구축할 수 있으며, 웹 애플리케이션의 구조를 명확하게 정의할 수 있음. 이를 통해 관심사의 분리를 달성하고, 유지보수성과 확장성을 향상시킬 수 있음.

서블릿 디스패처(DispatcherServlet)

  • 스프링 MVC에서 핵심적인 역할을 하는 프론트 컨트롤러(Front Controller). 클라이언트로부터 들어오는 모든 요청을 중앙 집중식으로 처리하며, 적절한 컨트롤러(Controller)에 요청을 전달하는 역할을 수행
작업설명
요청 수신클라이언트로부터 들어오는 모든 요청을 받아 처리합니다.
핸들러 매핑요청을 처리할 적절한 컨트롤러를 찾아주는 역할을 합니다. 핸들러 매핑은 요청 URL, HTTP 메서드 등을 기반으로 컨트롤러를 찾습니다.
요청 처리찾은 컨트롤러의 메서드를 호출하여 요청을 처리하고, 처리 결과를 Model 객체에 저장합니다.
뷰 선택 및 렌더링컨트롤러가 반환한 뷰 이름을 이용하여 실제 뷰를 찾고, 렌더링하여 응답으로 전달합니다. 스프링 MVC는 JSP, Thymeleaf, FreeMarker 등 다양한 템플릿 엔진을 지원합니다.
응답 반환렌더링된 뷰를 클라이언트에게 전달합니다.
  • 서블릿 디스패처는 웹 애플리케이션의 요청 처리 흐름을 관리하고, 각 컴포넌트들이 서로 독립적으로 작동할 수 있도록 하여 관심사의 분리를 달성합니다. 이를 통해 유지보수성과 확장성이 향상되며, 개발자는 비즈니스 로직에 더 집중할 수 있습니다.

@Controller 와 @RestController 차이

어노테이션설명
@Controller기본적인 스프링 MVC 컨트롤러를 정의하는 데 사용되는 어노테이션입니다. @Controller로 선언된 클래스는 요청을 처리하고, 해당하는 뷰 이름을 반환하여 뷰를 렌더링합니다. 반환된 뷰 이름과 매핑되는 뷰를 찾아 클라이언트에게 전달합니다. 주로 웹 페이지를 반환할 때 사용됩니다.
@RestController@Controller와 @ResponseBody 어노테이션이 합쳐진 형태로, RESTful 웹 서비스를 구현하는 데 사용되는 어노테이션입니다. @RestController로 선언된 클래스는 요청을 처리하고, 반환 값을 직접 응답 본문에 작성합니다. 주로 JSON, XML 등의 데이터 형식을 반환할 때 사용됩니다. 뷰를 렌더링하지 않고, 데이터를 직접 클라이언트에게 전달합니다.

BasicController

RequestMapping 방법

package com.web.study.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;



@Controller
public class BasicController {
	
	@RequestMapping(value = "/view/test", method = RequestMethod.GET)
	public String view() {
		return "test";
	}
}
  • 요청 URL과 메서드를 지정하는 가장 기본적인 방법
  • value 속성을 사용하여 요청 URL을 지정
  • method 속성을 사용하여 요청 HTTP메서드를 지정
  • RequestMethod.GET으로 설정하여 GET 요청을 처리

    @Controller > ViewResolver 로 감 (Error)

GetMapping 방법

package com.web.study.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class BasicController {
	
	@GetMapping("/view/test")
	public String view() {
		return "test";
	}
}
  • @GetMapping 어노테이션은 @RequestMapping의 단축 버전으로, GET 요청을 처리하는 데 사용
  • @GetMapping은 내부적으로 @RequestMapping을 사용하며, 메서드는 기본적으로 GET으로 설정

package com.web.study.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BasicController {
	
	@ResponseBody
	@GetMapping("/view/test")
	public String view() {
		return "test";
	}
}
  • @Controller 어노테이션을 사용하여 컨트롤러를 정의하고, @ResponseBody 어노테이션을 사용하여 요청 처리 메서드의 반환값이 HTTP 응답 본문에 직접 작성되도록 함
  • 이렇게 하면 "test" 문자열이 클라이언트에게 직접 반환됨
  • @RestController
package com.web.study.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BasicController {
	
	@GetMapping("/view/test")
	public String view() {
		return "test";
	}
}
  • @RestController 어노테이션은 @Controller어노테이션 +@ResponseBody 어노테이션을 합친 것으로 생각할 수 있음
  • @RestController 어노테이션은 Restful 웹 서비스를 구현할 때 사용하며, 뷰를 렌더링할 필요가 없는 경우에 사용됨

다양한 방식으로 GET 요청의 파라미터 처리하기

package com.web.study.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.web.study.dto.DataResponseDto;
import com.web.study.dto.ResponseDto;

@RestController
public class BasicRestController {
	
	// GET 요청의 param을 처리하는 방법
	@GetMapping("/read")
	public ResponseEntity<? extends ResponseDto> read(@RequestParam("age") int userAge) {
		return ResponseEntity.ok().body(DataResponseDto.of(userAge));
	}
}


수정 전

package com.web.study.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.web.study.dto.DataResponseDto;
import com.web.study.dto.ResponseDto;

@RestController
public class BasicRestController {
	
	// GET 요청의 param을 처리하는 방법
	@GetMapping("/read")
	public ResponseEntity<? extends ResponseDto> read(
			@RequestParam("age") int userAge,
			@RequestParam("name") String userName) {
		
		String userInfo = userName + "("+ userAge + ")";
		
		return ResponseEntity.ok().body(DataResponseDto.of(userInfo));
	}
}

수정 후

package com.web.study.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.web.study.dto.DataResponseDto;
import com.web.study.dto.ResponseDto;

@RestController
public class BasicRestController {
	
	// GET 요청의 param을 처리하는 방법
	@GetMapping("/read")
	public ResponseEntity<? extends ResponseDto> read(int age, String name) {
		
		String userInfo = name + "("+ age + ")";
		
		return ResponseEntity.ok().body(DataResponseDto.of(userInfo));
	}
}

업그레이드 버전

package com.web.study.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.web.study.dto.DataResponseDto;
import com.web.study.dto.ResponseDto;

import lombok.Data;


@Data
class Params {
	private int age;
	private String name;
}


@RestController
public class BasicRestController {
	
	// GET 요청의 param을 처리하는 방법
	@GetMapping("/read")
	public ResponseEntity<? extends ResponseDto> read(Params params) {
		
		String userInfo = params.getName() + "("+ params.getAge() + ")";
		
		return ResponseEntity.ok().body(DataResponseDto.of(userInfo));
	}
}

dto > request > BasicTestDto.java

package com.web.study.dto.request;

import lombok.Data;

@Data
public class BasicTestDto {
	
	private int age;
	private String name;

}
package com.web.study.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.web.study.dto.DataResponseDto;
import com.web.study.dto.ResponseDto;
import com.web.study.dto.request.BasicTestDto;



@RestController
public class BasicRestController {
	
	// GET 요청의 param을 처리하는 방법
	@GetMapping("/read")
	public ResponseEntity<? extends ResponseDto> read(BasicTestDto basicTestDto) {
		
		String userInfo = basicTestDto.getName() + "("+ basicTestDto.getAge() + ")";
		
		return ResponseEntity.ok().body(DataResponseDto.of(userInfo));
	}
}

PathVariable

  • Spring Framework에서 사용되는 어노테이션
  • URI의 일부분을 메소드의 매개변수로 전달할 수 있게 해주며 이를 통해 URI를 통해 동적으로 값을 전달하고 받을 수 있음
@GetMapping("/read2/{id}")
	public ResponseEntity<? extends ResponseDto> read2(@PathVariable("id") int userId) {
		Map<Integer, String> userMap = new HashMap<>();
		
		userMap.put(1, "김준일");
		userMap.put(2, "김준이");
		userMap.put(3, "김준삼");
		userMap.put(4, "김준사");
		userMap.put(5, "김준오");
		
		return ResponseEntity.ok().body(DataResponseDto.of(userMap.get(userId)));
	}


다양한 방식으로 POST 요청의 데이터 처리하기

PostMapping

@GetMapping("/read2/{id}")
	public ResponseEntity<? extends ResponseDto> read2(@PathVariable("id")Integer id) {
		Map<Integer, String> userMap = new HashMap<>();
		
		userMap.put(1, "김준일");
		userMap.put(2, "김준이");
		userMap.put(3, "김준삼");
		userMap.put(4, "김준사");
		userMap.put(5, "김준오");
		
		return ResponseEntity.ok().body(DataResponseDto.of(userMap.get(id)));
	}


ResponseEntity를 쓰는 이유?

  • 다양한 HTTP 응답에 대한 제어를 가능하게 하기 위함
  • ResponseEntity를 사용하면 HTTP 응답의 상태 코드뿐만 아니라, 헤더와 본문에 대한 설정도 가능
  1. HTTP 상태 코드 설정
  • ResponseEntity를 사용하면 다양한 HTTP 상태 코드를 설정할 수 있음, 이를 통해 클라이언트에게 요청의 성공,실패, 리소스 상태 등에 대한 정보를 정확하게 전달할 수 있음
return ResponseEntity.status(HttpStatus.CREATED).body(dataResponseDto);
  1. 헤더 설정
  • ResponseEntity를 사용하면 응답 헤더를 설정할 수 있음, 이를 통해 캐싱 정책, 컨텐츠 협상, CORS(Cross Origin Resource Sharing) 등과 같은 추가 정보를 전달 할 수 있음
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Custom-Header", "Custom-Value");
return new ResponseEntity<>(dataResponseDto, headers, HttpStatus.OK);
  1. 본문 설정
  • ResponseEntity를 사용하면 응답 본문에 전달할 데이터를 설정할 수 있음, 이를 통해 클라이언트에게 필요한 데이터를 효과적으로 전달할 수 있음
return ResponseEntity.ok().body(dataResponseDto);
profile
HW + SW = 1

0개의 댓글