반응형

# 스프링 MVC 기본 기능

## 프로젝트 생성.

  • 아래와 같이 설정 후 GENERATE 진행.

Packaging : War가 아닌 Jar 선택 이유.

  • JSP를 사용하지 않기 때문에 Jar를 사용하는 것이 좋음.
  • 스프링 부트를 사용하면 이 방식을 주로 사용.
  • Jar를 사용하면 항상 내장 서버(톰캣등)을 사용, webapp 경로도 사용하지 않는니다. 내장 서버 사용에 최적화 되어 있는 기능으로 최근에는 주로 이 방식을 사용.
  • War를 사용하면 내장 서버도 사용가능 하지만, 주로 외부 서버에 배포하는 목적으로 사용.

build.gradle

plugins {
   id 'org.springframework.boot' version '2.6.4'
   id 'io.spring.dependency-management' version '1.0.11.RELEASE'
   id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
}

repositories {
   mavenCentral()
}

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
   implementation 'org.springframework.boot:spring-boot-starter-web'
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
   useJUnitPlatform()
}

lombok 세팅

  • File > Settings 에서 annotation processors 검색 후 Enable annotation processing 체크.

Port 변경.

  • 8080 사용 중일 경우, run > edit configurations > Environment variable 에 아래와 같이 입력하여 port 변경.
server.port=8082

작동확인

설정 후 아래 클래스 진입하여 실행. (http://localhost:8080)

SpringmvcApplication

Welcome 페이지 생성.

  • 스프링 부트에 Jar 사용 시 /resources/static/index.hml 위치에 index.html 파일을 두게되면 Welcome 페이지로 처리. (스프링 부트가 지원하는 정적 컨텐츠 위치에 /index.html 이 있으면 된다.)

## 로깅 간단히 알아보기 (최소한의 기능)

  • 운영 시스템에서는 System.out.println() 같은 시스템 콘솔을 사용해서 필요한 정보를 출력하지 않고, 별도의 로깅 라이브러리를 사용해 로그를 출력.
  • 로그 관련 라이브러리도 많고, 깊게 들어가면 끝이 없음. (Logback, Log4J, Log4J2 등 수 많은 라이브러리 존재)

로깅 라이브러리

  • 스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리( spring-boot-starter-logging )가 함께 포함.
  • 스프링 부트 로깅 라이브러리는 기본으로 아래의 로깅 라이브러리 사용.
  • SLF4J (인터페이스) - http://www.slf4j.org
 

SLF4J

Simple Logging Facade for Java (SLF4J) The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framewor

www.slf4j.org

 

Logback Home

Logback Project Logback is intended as a successor to the popular log4j project, picking up where log4j 1.x leaves off. Logback's architecture is quite generic so as to apply under different circumstances. At present time, logback is divided into three mod

logback.qos.ch

  • 로그 라이브러리는 Logback, Log4J, Log4J2 등 많은 라이브러리가 있는데, 그것을 통합해서 인터페이스로 제공하는 것이 바로 SLF4J 라이브러리.
  • SLF4J는 인터페이스이고, 그 구현체로 Logback 같은 로그 라이브러리를 선택.
  • 실무에서는 스프링 부트가 기본 제공하는 Logback을 대부분 사용. (성능, 기능 괜찮음)

로그 선언

  • private Logger log = LoggerFactory.getLogger(getClass());
  • private static final Logger log = LoggerFactory.getLogger(Xxx.class)
  • @Slf4j : 롬복 사용 가능

로그 호출

  • log.info("hello")
  • System.out.println("hello") 시스템 콘솔로 직접 출력하는 것 보다 로그를 사용하면 다음과 같은 장점이 있다.
  • 실무에서는 항상 로그를 사용해야 한다.

LogTestController.

package hello.springmvc.basic;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogTestController {
    private final Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/log-test")
    public String logTest() {
        String name = "Spring";

        log.trace("trace log = {} ", name);
        log.debug("debug log = {} ", name);
        log.info("info log = {} ", name);
        log.warn("warn log = {} ", name);
        log.error("error log = {} ", name)

        return "OK";
    }
}
  • System.out.println("hello") 의 경우 운영/개발 할것없이 항상 출력되므로 사용 하지 않는다. (로그 레벨 설정하여 로그 사용!!!!)

매핑 정보

  • @RestController @Controller 는 반환 값이 String 이면 뷰 이름으로 인식. 그래서 뷰를 찾고 뷰가 랜더링 된다.
  • @RestController 는 반환 값으로 뷰를 찾는 것이 아닌, HTTP 메시지 바디에 바로 입력. 따라서 실행 결과로 ok 메세지를 받을 수 있다.

로그 레벨 설정 (application.properties 에 입력) -> 기본 : info

  • 모든 로그를 출력 할 경우 trace
# hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=trace
  • trace 제외 debug 하위 레벨 출력 할 경우 debug
# hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug
  • info 하위 레벨 출력 할 경우 info (기본)
# hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=info

 

올바른 로그 사용법

  • log.debug("data="+data) 로그 출력 레벨을 info로 설정해도 해당 코드에 있는 "data="+data가 실제 실행이 되어 버린다. 결과적으로 문자 더하기 연산이 발생.
  • log.debug("data={}", data) 로그 출력 레벨을 info로 설정하면 아무일도 발생하지 않는다. 따라서 앞과 같은 의미없는 연산이 발생하지 않는다.
// 자바언어는 아래의 경우 "trace my log = " + "Spring"로 치환 후 더함(연산 발생)
// 로그 레벨을 debug로 할 경우 trace 사용하지 않아도 연산 일어남으로써 
// -> 메모리, cpu 사용. (쓸모없는 리소스 사용 / 의미없는 연산 발생.)
log.trace("trace my log = " + name);

로그가 출력되는 포멧 확인

  • 시간, 로그 레벨, 프로세스 ID, 쓰레드 명, 클래스명, 로그 메시지

로그 레벨 설정을 변경에 따른 출력 결과

  • LEVEL : TRACE > DEBUG > INFO > WARN > ERROR
  • 개발 서버 : debug 출력
  • 운영 서버 : info 출력

@Slf4j

  • lombok이 제공하는 것.
  • @Slf4j를 넣을 경우 아래 코드를 자동으로 해줌.
private final Logger log = LoggerFactory.getLogger(getClass());

로그 사용 장점

  • 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
  • 특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
  • 성능도 System.out보다 좋음. (내부 버퍼링, 멀티 쓰레드 등)
  • 실무에서는 꼭 로그 사용.

로그 추가 학습 시 참고.

로그에 대해서 더 자세한 내용은

스프링 부트가 제공하는 로그 기능

## 요청 매핑

MappingController.

package hello.springmvc.basic.requestmapping;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MappingController {

    private Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/hello-basic")
    public String helloBasic() {
        log.info("basic");

        return "OK";
    }
}

@RestController

  • @Controller 는 반환 값이 String 이면 뷰 이름으로 인식. 그래서 뷰를 찾고 뷰가 랜더링 된다.
  • @RestController 는 반환 값으로 뷰를 찾는 것이 아난, HTTP 메시지 바디에 바로 입력. 따라서 실행 결과로 ok 메세지를 받을 수 있다.

@RequestMapping("/hello-basic")

  • /hello-basic URL 호출이 오면 이 메서드가 실행되도록 매핑.
  • 대부분의 속성을 배열[] 로 제공하므로 다중 설정이 가능하다. 
@RequestMapping({"/hello-basic", "/hello-go"})

두가지 모두 허용.

  • 다음 두가지 요청은 다른 URL이지만, 스프링은 다음 URL 요청들을 같은 요청으로 매핑.
  • 매핑 : /hello-basic
  • URL 요청 : /hello-basic , /hello-basic/

HTTP 메서드

  • @RequestMapping 에 method 속성으로 HTTP 메서드를 지정하지 않으면, HTTP 메서드와 무관하게 호출.
  • 모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE
  • 아래와 같이 설정해줘야 함.
@RequestMapping(value = "/hello-basic", method = RequestMethod.GET)
  • 아래와 같이 축약 가능.
@GetMapping("/hello-basic")
package hello.springmvc.basic.requestmapping;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MappingController {

    private Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping(value = "/hello-basic")
    public String helloBasic() {
        log.info("basic");

        return "OK";
    }

    @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
    public String mappingGetV1() {
        log.info("mappingGetV1");

        return "OK";
    }

    /**
     * 편리한 축약 애노테이션
     * @GetMapping
     * @PostMapping
     * @PutMapping
     * @DeleteMapping
     * @PatchMapping
     * */
    @GetMapping(value = "/mapping-get-v2")
    public String mappingGetV2() {
        log.info("mappingGetV2");

        return "OK";
    }
}

PathVariable(경로 변수) 사용 (자주 사용!!!!!)

/**
 * PathVariable 사용
 * 변수명이 같으면 생략 가능.
 * @PathVariable("userId") String userId -> @PathVariable userId
 * url 자체에 /mapping/userA 이런식으로 값이 들어감.
 */
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
    log.info("mappingPath userId = {} ", data);

    return "OK";
}
  • 최근 HTTP API는 아래와 같이 리소스 경로에 식별자를 넣는 스타일을 선호.
/mapping/userA

/users/1
  • @RequestMapping 은 URL 경로를 템플릿화 할 수 있는데, @PathVariable 을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.
  • @PathVariable 의 이름과 파라미터 이름이 같으면 생략 가능 (아래 코드 참고) (단, @PathVariable 자체를 생략하는건 안됨.)

 

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {
    log.info("mappingPath userId = {} ", userId);

    return "OK";
}

PathVariable 사용 - 다중

/**
 * PathVariable 사용 - 다중
 */
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
    log.info("mappingPath userId={}, orderId={}", userId, orderId);

    return "ok";
}

특정 파라미터 조건 매핑 (자주 사용하진 않음)

  • 특정 파라미터가 있거나 없는 조건을 추가할 수 있다.
/**
 * 파라미터로 추가 매핑
 * params="mode",
 * params="!mode"
 * params="mode=debug"
 * params="mode!=debug" (! = )
 * params = {"mode=debug","data=good"}
 */
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
    log.info("mappingParam");
    
    return "ok";
}

특정 헤더 조건 매핑

/**
 * 특정 헤더로 추가 매핑
 * headers="mode",
 * headers="!mode"
 * headers="mode=debug"
 * headers="mode!=debug" (! = )
 */
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
    log.info("mappingHeader");

    return "ok";
}

미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume

/**
 * Content-Type 헤더 기반 추가 매핑 Media Type
 * consumes="application/json"
 * consumes="!application/json"
 * consumes="application/*"
 * consumes="*\/*"
 * MediaType.APPLICATION_JSON_VALUE
 */
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
    log.info("mappingConsumes");

    return "ok";
}
  • HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑.
  • 만약 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환.
  • consume 예시.
consumes = "text/plain"
consumes = {"text/plain", "application/*"}
consumes = MediaType.TEXT_PLAIN_VALUE

미디어 타입 조건 매핑 - HTTP 요청 Accept, produce

/**
 * Accept 헤더 기반 Media Type
 * produces = "text/html"
 * produces = "!text/html"
 * produces = "text/*"
 * produces = "*\/*"
 */
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    log.info("mappingProduces");

    return "ok";
}
  • HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑.
  • 만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환.
produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"

## 요청 매핑 - API 예시.

  • 회원 관리를 HTTP API로 만든다 생각하고 매핑을 어떻게 하는지 확인. (실제 데이터 넘어가는 부분은 생략)

회원 관리

  • API 회원 목록 조회 : GET /users
  • 회원 등록 : POST /users
  • 회원 조회 : GET /users/{userId}
  • 회원 수정 : PATCH /users/{userId}
  • 회원 삭제 : DELETE /users/{userId}
package hello.springmvc.basic.requestmapping;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {

    /**
     *  회원 목록 조회 : GET '/users'
     *  회원 등록   : POST '/users'
     *  회원 조회   : GET '/users/{userId}'
     *  회원 수정   : PATCH '/users/{userId}'
     *  회원 삭제   : DELETE '/users/{userId}'
     * */
    @GetMapping
    public String user() {
        return "get users";
    }

    @PostMapping
    public String addUser() {
        return "post user";
    }

    @GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
        return "get userId";
    }

    @PatchMapping("/{userId}")
    public String updateUser(@PathVariable String userId) {
        return "update userId=" + userId;
    }

    @DeleteMapping("/{userId}")
    public String delete(@PathVariable String userId) {
        return "update userId=" + userId;
    }
}
  • @RequestMapping("/mapping/users") 클래스 레벨에 매핑 정보를 두면 메서드 레벨에서 해당 정보를 조합해서 사용.
반응형

+ Recent posts