Spring @Annotation(어노테이션)

1. Spring @Annotation

1.1. @RestController

@RestController 동작에 앞서, 먼저 알아보아야할것이 @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 "";

}

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

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

이제 @Controller 다음으로 @RestController 의미에 대해 알아보겠습니다. 간단히 @RestController는 @Controller가 포함되어있고 @ReponseBody형식을 함께 사용할때 사용합니다.

@RestController 내부

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
58
59
60
61
/*
* 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.web.bind.annotation;

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;
import org.springframework.stereotype.Controller;

/**
* A convenience annotation that is itself annotated with
* {@link Controller @Controller} and {@link ResponseBody @ResponseBody}.
* <p>
* Types that carry this annotation are treated as controllers where
* {@link RequestMapping @RequestMapping} methods assume
* {@link ResponseBody @ResponseBody} semantics by default.
*
* <p><b>NOTE:</b> {@code @RestController} is processed if an appropriate
* {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
* {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter}
* pair which are the default in the MVC Java config and the MVC namespace.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

/**
* 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)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";

}

내부 스펙을 확인해보면 @ResponseBody Annotation, @Controller Annotation이 함께 선언되어 있습니다.

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

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

1.2. @RequestMapping

@RequestMapping Annotation은 Spring에서 제공하는 HandlerMapping Class가 가지고 있습니다.
두 가지의 경우로 나눌 수 있는데, 이것은 스프링 HTTP 내부 Note:를 참고하면 아래와 같은 사항을 확인할 수 있습니다.

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
/**
* Annotation for mapping web requests onto methods in request-handling classes
* with flexible method signatures.
*
* <p>Both Spring MVC and Spring WebFlux support this annotation through a
* {@code RequestMappingHandlerMapping} and {@code RequestMappingHandlerAdapter}
* in their respective modules and package structure. For the exact list of
* supported handler method arguments and return types in each, please use the
* reference documentation links below:
* <ul>
* <li>Spring MVC
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-arguments">Method Arguments</a>
* and
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-return-types">Return Values</a>
* </li>
* <li>Spring WebFlux
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-ann-arguments">Method Arguments</a>
* and
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-ann-return-types">Return Values</a>
* </li>
* </ul>
*
* <p><strong>Note:</strong> This annotation can be used both at the class and
* at the method level. In most cases, at the method level applications will
* prefer to use one of the HTTP method specific variants
* {@link GetMapping @GetMapping}, {@link PostMapping @PostMapping},
* {@link PutMapping @PutMapping}, {@link DeleteMapping @DeleteMapping}, or
* {@link PatchMapping @PatchMapping}.</p>
*
* <p><b>NOTE:</b> When using controller interfaces (e.g. for AOP proxying),
* make sure to consistently put <i>all</i> your mapping annotations - such as
* {@code @RequestMapping} and {@code @SessionAttributes} - on
* the controller <i>interface</i> rather than on the implementation class.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Sam Brannen
* @since 2.5
* @see GetMapping
* @see PostMapping
* @see PutMapping
* @see DeleteMapping
* @see PatchMapping
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
}

위의 내용을 요약 하면

@RequestMapping Annotation은 클래스 및 메서드 수준에서 모두 사용할 수 있습니다. 대부분의 경우 방법 수준에서 응용 프로그램은 HTTP 메서드별 변형된 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 또는 @PatchMapping 중 하나를 사용하는 것을 권장합니다.
참고: 컨트롤러 인터페이스(예: AOP 프록시)를 사용할 때 구현 클래스가 아닌 컨트롤러 인터페이스에 @RequestMapping 및 @SessionAttributes와 같은 모든 매핑 Annotation을 일관되게 배치해야 합니다.

즉, 클래스 및 메서드 수준을 모두 사용할 수 있으며, 최근에는 HTTP 메서드별 변형된 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 또는 @PatchMapping 중 하나를 사용하는 것을 권장한다고 합니다.

  1. Class Level Mapping
    Class Level Mapping은 모든 메서드에 적용하는 경우 사용합니다.
1
2
3
4
@RequestMapping("/")
public class MainController {
}

HTTP Request 요청경로가 “/” 일 경우 해당 클래스에서 처리를 진행합니다.

  1. Handler Level Mapping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RequestMapping("/")
public class MainController {
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getItemA(Model model){
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String getItemB(Model model){
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String getItemC(Model model){
}
@RequestMapping(value = "/user",method = RequestMethod.PATCH)
public String getItemD(Model model){
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String getItemE(Model model){
}
}

요청 URL에 대하여 해당 메서드를 처리해야하는 경우 사용합니다. 특정 메서드에서 HTTP요청을 처리한다고 생각하면 됩니다.

RequestMapping 옵션

  • value는 해당 URL 경로를 지정합니다.
  • HTTP Request Method를 지정하여 HTTP 형식에 맞추어 수행됩니다.

1.3. @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping

@RequestMapping에서 조금더 세분화된 Method path를 지정하여 명시적이고 효율적인 사용을 위하여 Annotation이 Spring 4.3.xx 버전 이후로 추가되었습니다.
현재 @RequestMapping보다는 4.3버전이후로 나온 Annotation을 권장하고 있습니다.

종류
@GetMapping, @PostMapping, DeleteMapping, @PutMapping, @PatchMapping

Annotation 사용법

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

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

@RequestMapping("/")
public class MainController {
@GetMapping("/user")
//@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getItemA(Model model){

}
@PostMapping("/user")
//@RequestMapping(value = "/user", method = RequestMethod.POST)
public String getItemA(Model model){

}
@DeleteMapping("/user")
//@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String getItemA(Model model){

}
@PatchMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PATCH)
public String getItemA(Model model){

}
@PutMapping("/user")
//@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String getItemA(Model model){

}
}

기존에 RequestMapping을 통하여 가독성이 떨어지는 코드를 작성하였는데 스프링4.3버전이후로 부터 세부적인 HTTP Method를 지정할 수 있게 되어 명시적이고 효율성이 올라갔다고 생각합니다.

1.4. @Compoment

스프링빈을 등록하는 방법은 2가지가 존재합니다.

  • 컴포넌트 스캔과 자동의존관계 설정
  • 코드로 직접 스프링빈을 등록하는 방식

따라서, 컴포넌트스캔원리를 이용 할때 @Component Annotation을 사용하게 됩니다. @Component의 의미는 해당 Annotation이 있으면 Spring bean으로 자동으로 등록이 됩니다.
즉, component-scan을 통하여 특정 패키지 안의 클래스들을 스캔하고 @Component Annotation이 있는 클래스에 대하여 bean인스턴스를 생성시킵니다. bean인스턴스로 생성되면 스프링 컨테이너에서 관리를 진행하게 되고 DI를 주입받을 수 있습니다.

DI주입은 필드주입, setter주입, 생성자 주입 총 3가지 방법이 있습니다. 의존관계가 실행중 동적으로 변하는 경우는 거의 없기때문에 생성자 주입을 통한 DI주입을 권장합니다.

DI주입시 주의사항은 @Autowired를 통한 DI는 Controller, Service등과 같이 스프링이 관리하는 객체에서만 동작하게 됩니다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작되지 않습니다. 생성자에 @Autowired를 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입해줍니다. 생성자가 1개만 있으면 @Autowired는 생략이 가능합니다.

1.5. @Controller, @Service, @Repository

1.5.1. @Controller

@Controller가 스프링빈으로 자동등록되는 이유는 @Controller내부에 보면 @Component Annotation에 의해서 컴포넌트스캔이 진행되기 때문입니다.

@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

/**
* 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 "";

}

1.5.2. @Service

@Service Annotation또한 스프링빈으로 자동등록되는 이유는 @Service내부에 보면 @Component Annotation에 의해서 컴포넌트스캔이 진행되기 떄문입니다.
요약하면 @Component 어노테이션에 의해서 @Controller, @Service, @Repository와 같은 어노테이션을 구체화하여 스프링 컨테이너에 @Service bean으로 등록을 하게 됩니다.

@Service 내부

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
/**
* Indicates that an annotated class is a "Service", originally defined by Domain-Driven
* Design (Evans, 2003) as "an operation offered as an interface that stands alone in the
* model, with no encapsulated state."
*
* <p>May also indicate that a class is a "Business Service Facade" (in the Core J2EE
* patterns sense), or something similar. This annotation is a general-purpose stereotype
* and individual teams may narrow their semantics and use as appropriate.
*
* <p>This annotation serves as a specialization of {@link Component @Component},
* allowing for implementation classes to be autodetected through classpath scanning.
*
* @author Juergen Hoeller
* @since 2.5
* @see Component
* @see Repository
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

/**
* 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 "";

}

1.5.3. @Repository

@Repository Annotation또한 스프링빈으로 자동등록되는 이유는 @Repository내부에 보면 @Component Annotation에 의해서 컴포넌트스캔이 진행되기 떄문입니다. 즉, @Component 어노테이션에 의해서 @Controller, @Service, @Repository와 같은 어노테이션을 구체화하여 스프링 컨테이너에 @Service bean으로 등록을 하게 됩니다.

@Repository 내부

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
/**
* Indicates that an annotated class is a "Repository", originally defined by
* Domain-Driven Design (Evans, 2003) as "a mechanism for encapsulating storage,
* retrieval, and search behavior which emulates a collection of objects".
*
* <p>Teams implementing traditional Java EE patterns such as "Data Access Object"
* may also apply this stereotype to DAO classes, though care should be taken to
* understand the distinction between Data Access Object and DDD-style repositories
* before doing so. This annotation is a general-purpose stereotype and individual teams
* may narrow their semantics and use as appropriate.
*
* <p>A class thus annotated is eligible for Spring
* {@link org.springframework.dao.DataAccessException DataAccessException} translation
* when used in conjunction with a {@link
* org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
* PersistenceExceptionTranslationPostProcessor}. The annotated class is also clarified as
* to its role in the overall application architecture for the purpose of tooling,
* aspects, etc.
*
* <p>As of Spring 2.5, this annotation also serves as a specialization of
* {@link Component @Component}, allowing for implementation classes to be autodetected
* through classpath scanning.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 2.0
* @see Component
* @see Service
* @see org.springframework.dao.DataAccessException
* @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

/**
* 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 "";

}

1.6. @ComponentScan

@ComponentScan이란 컴포넌트 검색기능을 가진 어노테이션입니다.
스프링은 기본적으로 객체를 자동으로 생성하여 관리하기 때문에 @Component 어노테이션이 있는 클래스들을 스프링 컨테이너가 생성해줍니다.
따라서, @ComponentScan는 특별히 basePackage를 지정하지 않아도 main class가 위치한 패키지를 Root package로 하여 그 이하의 모든 컴포넌트 클래스들을 검색하여 빈으로 등록할 수 있습니다.

1.7. @Configuration

  • 클래스에 이 어노테이션이 붙어 있으면 스프링은 해당 클래스를 Java config로 간주합니다.
  • 1개 이상의 @Bean을 제공하는 클래스의 경우 반드시 @Configuration을 명시해 주어야 합니다.

1.8. @Bean

  • 개발자가 직접 제어가 불가능한 외부 라이브러리 또는 설정을 위한 클래스를 Bean으로 등록할 때 @Bean 어노테이션을 활용합니다.

1.9. @SpringBootApplication

@SpringBootApplication 어노테이션은 스프링 부트의 가장 기본적인 설정을 선언이 되어있습니다. 내부로직을 살펴보도록 하겠습니다.

@SpringBootApplication

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
/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
* annotation that is equivalent to declaring {@code @Configuration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @since 1.2.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@AliasFor(annotation = EnableAutoConfiguration.class)
public Class<?>[] exclude() default {};

@AliasFor(annotation = EnableAutoConfiguration.class)
public String[] excludeName() default {};

@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
public String[] scanBasePackages() default {};

@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
public Class<?>[] scanBasePackageClasses() default {};

@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
public Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

@AliasFor(annotation = Configuration.class)
public boolean proxyBeanMethods() default true;
}

여기 스펙부분을 자세히보시면 중요하게 생각해야할 부분이 두가지 있습니다.

  • @ComponentScan
  • @EnableAutoConfiguration

이 두가지가 왜 중요할까요?

@ComponentScan

@ComponentScan은 @component 어노테이션 및 @Service, @Repository, @Controller 등의 어노테이션을 스캔하여 Bean으로 등록해주는 어노테이션입니다.
즉, 어플리케이션 실행시에 @Service, @Repository, @Repositroy, @Controller에 컴포넌트스캔을 통하여 스프링 컨테이너에 Bean으로 등록합니다.

@EnableAutoConfiguration

@EnableAutoConfiguration은 사전에 정의한 라이브러리들을 Bean으로 등록해 주는 어노테이션입니다. 사전에 정의한 라이브러리들 모두가 등록되는 것은 아니고 특정조건이 만족될 경우에 Bean으로 등록됩니다.

저장위치

사전 정의 파일 위치: Dependencies > spring-boot-autoconfigure > META-INF > spring.factories

References

https://projectlombok.org/features/
https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch04s11.html
https://velog.io/@gillog/Spring-Annotation-정리
https://gmlwjd9405.github.io/2018/12/02/spring-annotation-types.html
https://bamdule.tistory.com/31
https://imucoding.tistory.com/217
https://toma0912.tistory.com/86
https://tomining.tistory.com/180