Spring MVC @ResponseBody의 역할

1. @ResponseBody

스프링환경에서 개발을 진행하다보면 컨트롤러에서 @ResponseBody를 사용하는일이 많습니다. @ResponseBody를 사용하는것과 사용하지 않는것은 어떤차이점이 있을까요?

기본적으로 Spring은 MVC 환경에서 동작을 합니다. 이때 MVC의 동작을 간략하게 말씀드리겠습니다.

만약 사용자가 웹브라우저상에서 서버에게 localhost:8080/mvc 라는 경로로 요청을 진행한다고 가정하겠습니다.
이때 Spring 내부의 내장 톰캣 서버가 해당 요청을 받아 들여 스프링 컨테이너는 해당 요청을 가지고 있는 @Controller 어노테이션이 붙여진 컨트롤러를 찾아주게됩니다.

@Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

/**
* Indicates that an annotated class is a "Controller" (e.g. a web controller).
*
* <p>This annotation serves as a specialization of {@link Component @Component},
* allowing for implementation classes to be autodetected through classpath scanning.
* It is typically used in combination with annotated handler methods based on the
* {@link org.springframework.web.bind.annotation.RequestMapping} annotation.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 2.5
* @see Component
* @see org.springframework.web.bind.annotation.RequestMapping
* @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {

/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";

}

@Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that an annotated class is a "component".
* Such classes are considered as candidates for auto-detection
* when using annotation-based configuration and classpath scanning.
*
* <p>Other class-level annotations may be considered as identifying
* a component as well, typically a special kind of component:
* e.g. the {@link Repository @Repository} annotation or AspectJ's
* {@link org.aspectj.lang.annotation.Aspect @Aspect} annotation.
*
* @author Mark Fisher
* @since 2.5
* @see Repository
* @see Service
* @see Controller
* @see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";

}


해당 컨트롤러를 타고 들어가면 다음과 같은 구조를 볼 수 있습니다. 자세히 살펴보시면 @Component 어노테이션을 살펴보실 수 있습니다. 과연 이것의 어노테이션의 역할은 무엇일까요?
즉, 기존의 Spring에서는 @Bean을 붙여 빈을 등록하던 것처럼 빈 클래스에 @Component 애노테이션을 붙여 빈을 등록할 수 있습니다. 따라서 @Controller 어노테이션 내부에는 해당 컨트롤러를 @Bean 으로 등록하는 역할도 하고 있다는 뜻입니다.
@Component를 붙여 빈을 등록하면 클래스 이름의 첫 문자를 소문자로 바꾼 것이 빈의 이름(id)이 됩니다. 그리고 빈 객체가 생성되는 시점은 ApplicationContext 객체가 생성되는 시점이며 기본적으로 singleton scope 입니다.

localhost:8080/mvc의 요청경로에 맞는 컨트롤러를 @Bean 컴포넌트 스캔을 통하여 해당 경로를 가지고 있는 컨트롤러를 찾아주게 됩니다. 만약 @ResponseBody 가 붙여져 있지 않다면 해당 모델에 값을 넘겨줄 수도 있고, String을 반환하게 되면 데이터를 넘겨주는것이 아니라 해당 View의 Path값에 맞게 모델값과 함께 반환시켜주게 됩니다.

간단한 예제로는

1
2
3
4
5
6

@GetMapping("/mvc")
public String hello(Model model) {
model.addAttribute("name", "kgh");
return "root";
}

형식을 들 수 있습니다. 모델값을 가져와 거기에 <Key,value> 형식으로 값을 넣어주고 반환값을 String으로 한 'root' View Path를 반환시켜주게 됩니다. 즉, 컨트롤러에서 리턴 값으로 문자를 반환하면 뷰 리졸버( viewResolver )가 화면을 찾아서 처리를 진행합니다.

@ResponseBody를 붙여주면 무슨일이 발생할까요?

@ResponseBody의 역할은 HTTP BODY문자내용을 반환하는 api라고 할 수 있습니다. 즉, 기존에 @ResponseBody가 없는 경우에 viewResolver 대신에 httpMessageConverter가 동작하여 문자일 경우에는 StringHttpMessageConverter 객체일 경우에는 MappingJackson2HttpMessageConverter로 이루어져있습니다. 클라이언트의 HTTP Accept헤더와 서버 컨트롤러의 반환 타입 정보 둘을 조합해서 HttpMessageConverter가 동작되게 됩니다. 기본으로 동작되는 데이터반환형식은 Default JSON형식으로 동작되게 됩니다.

1
2
3
4
5
@GetMapping("/mvc")
@ResponseBody
public String helloString(@RequestParam("name") String name){
return "kgh" + name;
}