SpringBoot

  • Spring Boot는 프로덕션 수준의 스프링 기반 어플리케이션을 쉽고 빠르게 만들 수 있다.
  • Spring Boot는 Spring구성이 거의 필요하지 않다.
  • Spring Boot는 java -jar로 실행하는 Java 어플리케이션을 만들 수 있다.

주요 목표

  • Spring 개발에 대해 빠르고 광범위하게 적용할 수 있는 환경
  • 기본값 설정이 있지만 바꿀 수 있다.
  • 대규모 프로젝트에 공통적인 비 기능 제공(보안, 모니터링 등)
  • XML 구성 요구사항이 전혀 없다.

  • Build Tool : Maven, Gradle
  • Servlet Container : Tomcat, Jetty, Undertow, Netty, …

Hello Spring Boot REST Api

REST Api를 처리하는 컨트롤러를 만드려면 다음과 같이 한다.

@RestController //스프링부트에게 이 클래스는 REST를 처리하는 곳이라는 것을 알려준다.
@RequestMapping("/api") //어떤 URI를 매핑할지 알려준다.
public class ApiController {

    @GetMapping("/hello") //GET방식으로 hello라는 url로 요청이 들어올시 처리하는 곳.
    public String hello(){
        return "hello spring boot";
    }
}

GET API

위의 Hello Spring Boot에서 사용한 @GetMapping 어노테이션으로 들어가보면 다음과 같다.


/**
 * Annotation for mapping HTTP {@code GET} requests onto specific handler
 * methods.
 *
 * <p>Specifically, {@code @GetMapping} is a <em>composed annotation</em> that
 * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.GET)}.
 *
 * @author Sam Brannen
 * @since 4.3
 * @see PostMapping
 * @see PutMapping
 * @see DeleteMapping
 * @see PatchMapping
 * @see RequestMapping
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {

	/**
	 * Alias for {@link RequestMapping#name}.
	 */
	@AliasFor(annotation = RequestMapping.class)
	String name() default "";

	/**
	 * Alias for {@link RequestMapping#value}.
	 */
	@AliasFor(annotation = RequestMapping.class)
	String[] value() default {};

	/**
	 * Alias for {@link RequestMapping#path}.
	 */
	@AliasFor(annotation = RequestMapping.class)
	String[] path() default {};

	/**
	 * Alias for {@link RequestMapping#params}.
	 */
	@AliasFor(annotation = RequestMapping.class)
	String[] params() default {};

	/**
	 * Alias for {@link RequestMapping#headers}.
	 */
	@AliasFor(annotation = RequestMapping.class)
	String[] headers() default {};

	/**
	 * Alias for {@link RequestMapping#consumes}.
	 * @since 4.3.5
	 */
	@AliasFor(annotation = RequestMapping.class)
	String[] consumes() default {};

	/**
	 * Alias for {@link RequestMapping#produces}.
	 */
	@AliasFor(annotation = RequestMapping.class)
	String[] produces() default {};

}

위에서 보이듯이, 여러가지 방법으로 GET요청방식을 지정할 수 있는데, 아무런 지정없이 문자열을 넣어주면 value로 설정되는것이 기본값이다.

보통 다음과 같이 GetMapping을 사용한다.

@RestController
@RequestMapping("/api/get")
public class GetApiController {

    @GetMapping(path = "/hello") // http://localhost:8080/api/get/hello
    public String hello(){
        return "get hello";
    }

    //에전방식, 위와 동일한 동작
//    @RequestMapping("/hi") //모든 메소드가 동작한다. (GET, POST, DELTE, PUT ...)
    @RequestMapping(path="/hi", method = RequestMethod.GET) // /api/get/hi
    public String hi(){
        return "get hi";
    }

}

Path Variable

요청하는 URL 패스를 변수로 사용하고 싶으면 다음과 같이한다.

// http://localhost:8080/api/get/path-variable/{name}
@GetMapping("/path-variable/{name}") //주소에는 대문자 대신 -로 구분하는것이 좋다.
public String pathVariable(@PathVariable String name){ //pathVariable을 받도록한다.
    System.out.println("PathVariable : " + name);
    return name;
}

위는 PathVariable로 받으려고 하는 중괄호 안의 변수명과 파라미터로 받는 변수명이 일치하지만, 일치하지 않을 경우는 다음과 명시해준다.

public String pathVariable(@PathVariable(name="id") String pathName){ //pathVariable을 받도록한다.

Query Parameter

요청하는 URL뒤에 ~~~?variable1=somethig1&variable2=somthing2와 같이 &key=value형식 으로 어떤 정보를 전달하는데 이것이 쿼리 파라미터이다. 이것을 처리하려면 다음과 같이 한다.

//목표 : http://localhost:8080/api/get/query-param?user=dboo&email=~~.com&age=30
@GetMapping(path = "query-param")
public String queryParam(@RequestParam Map<String, String> queryParam){ //RequestParam으로 쿼리파라미터를 받는다.
    StringBuilder sb = new StringBuilder();
    queryParam.entrySet().forEach(
            entry ->{
                System.out.println(entry.getKey());
                System.out.println(entry.getValue());
                System.out.println("\n");

                sb.append(entry.getKey() + " = " + entry.getValue() + "\n");
            });
    return sb.toString();
}

그런데, 이 경우 어떤 키값으로 파라미터가 들어올 것인지 전혀 예측하거나 제한할 수 없기 때문에 이를 지정하 는 방법을 알아보자.

@GetMapping(path="query-param02")
public String queryParam02(
        @RequestParam String name, //파라미터를 지정한다.
        @RequestParam String email,
        @RequestParam int age
){
    System.out.println(name);
    System.out.println(email);
    System.out.println(age);
    return name + " " + email + " " + age;
}

그런데, 이 경우에 쿼리파라미터의 갯수가 늘어나면 늘어날수록 지정해야하는 파라미터도 많아지기 때문에 여기에 DTO를 연계할 수 있도록 스프링이 제공을 한다. 현업에서 가장 많이 사용하는 방식이기도 하다.

받을 쿼리파라미터를 DTO로 만들어보자.

public class UserRequest {

    private String name;
    private String email;
    private int age;

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "UserRequest{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
}

위 DTO를 활용해서 쿼리 파라미터를 받으려면 다음과 같이 한다.
queryParam02와 같이 어노테이션은 붙이지 않는다. 스프링부트가 알아서 매칭시켜준다.

@GetMapping(path="query-param03")
    public String queryParam03(UserRequest userRequest){ //어노테이션 붙이지 않는다.
        System.out.println(userRequest.getName());
        System.out.println(userRequest.getEmail());
        System.out.println(userRequest.getAge());
        return userRequest.toString();
    }

POST API

  • 보통 쿼리 파라미터를 사용하지 않고, 데이터 바디에 정보를 실어서 전달한다.
  • 데이터 형테는 XML이나 JSON으로 전달하고 보통 JSON을 사용한다.

짤막하게 JSON데이터 형식에 대해 알아보자.

JSON

“key” : “value” 형식으로 나열하며, 키-값쌍들은 쉼표(,)로 구분한다.
사용할 수 있는 변수는 다음과 같다.

  1. string : value
  2. number : value
  3. boolean : true, false
  4. object : {}
  5. array : []

snake case : “phone_number”
camel case : “phoneNumber”

{
  "string" : "value",
  "number" : 10,
  "boolean" : false,
  "object" : {
    "var1" : "variable1",
    "var2" : 2,
  },
  "array" : [
    {
      "array1" : "1"
    },
    {
      "array2" : "2"
    }, ...
  ]
}

POST Controller

기본적인 PostMapping은 다음과 같이한다.

@PostMapping("/post")
public void post(@RequestBody Map<String, Object> requestData){
    requestData.forEach((key, value)->{
        System.out.println("key : " + key);
        System.out.println("value : " + value);
    });
}

GET방식과는 다르게 @RequestBody를 붙여준다.
이번에도 DTO를 만들어서 데이터바디를 객체로 받아보자.

public class PostRequestDto {
    private String account;
    private String email;
    private String address;
    private String password;

    //getter, setter, toString 생략
}

그러면 PostController에서

@PostMapping("/post")
public void post(@RequestBody PostRequestDto postRequestDto){
    System.out.println(postRequestDto.toString());
}

와 같이 줄일 수 있다. GET에서와 다른 점은 DTO를 쓰더라도 @RequestBody를 생략하지 않는다.

그런데, DTO에서 받아야 하는 변수명이 snake-case로 오게 될 시, 자바에서의 camel-case와 어떻게 매핑 할것인지 문제가 있다.

public class PostRequestDto {
    private String account;
    private String email;
    private String address;
    private String password;
    private String phoneNumber;

    //getter, setter, toString 생략
}

camel-case형식인 변수명 phoneNumber가 추가되었다.

기본적으로 JSON으로 넘어온 변수명들이 ObjectMapper를 통해서 매핑이 되는데, snake-case인 변수명을 따로 매핑정보를 줘야 한다.

  • @JsonProperty 어노테이션을 이용한 변수명매핑
public class PostRequestDto {
    private String account;
    private String email;
    private String address;
    private String password;

    @JsonProperty("phone_number")
    private String phoneNumber;

    //getter, setter, toString 생략
}

위 방법은 굳이 snake-case가 아니더라도, JSON 변수명을 매핑할때 사용할 수 있다.

이 방법 말고도 @JsonNaming 이라는 어노테이션으로도 해결할 수 있는데 이는 나중에 다시 알아보자.