어노테이션(Annotation) [Spring]

조민·2022년 4월 8일
0
post-thumbnail

1. Annotation 이란?

 ㄴ 사전적 의미로는 "주석" 이라는 뜻.
 ㄴ 자바에서는 코드 사이에 주석처럼 쓰이며, 특별한 의미, 기능을 수해앟도록 하는 기술.
 ㄴ 프로그램에게 추가적인 정보를 제공해주는 "메타 데이터"라고 볼 수도 있다.

2. Spring Boot 관련 Annotation

2-1) @SpringBootApplication

 ㄴ 해당 클래스가 스프링 부트를 설정하는 클래스임을 의미.
 ㄴ 스프링은 @SpringBootApplication 어노테이션이 달린 클래스가 있는 패키지를 베이스 패키지로 간주한다.


 스프링의 중요 기능 중 하나가 "의존성 주입" 컨테이너로서의 기능이다. 
 스프링은 베이스 패키지와 그 하위 패키지에서 자바 빈을 찾아 스프링의 의존성 주입 컨테이너 오브젝트, 즉 ApplicationContext에 등록한다. 
 그리고 애플리케이션 실행 중 어떤 오브젝트가 필요한 경우 의존하는 다른 오브젝트를 찾아 연결해 준다.

2-2) @Autowired

 ㄴ 자동으로 다른 오브젝트를 찾아 연결해주는 어노테이션
 ㄴ 생성자나 Setter와 같은 메소드 없이 의존성을 주입해서 자동으로 객체를 생성해주는 어노테이션

이 어노테이션을 사용할 시 스프링이 자동적으로 값을 할당해주며 Controller 클래스에서 DAO나 Service에 관한 객체들을 주입시킬 때 많이 사용한다.

Type을 먼저 확인한 후, 못 찾으면 Name에 따라서 주입한다.

<Bean을 주입 받는 방식>
1. @Autowired
2. Setter
3. 생성자(@AllArgsConstructor 사용) = 권장 방식

그렇다면, 스프링은 어떻게 애플리케이션 컨텍스트에 등록할 자바 빈을 찾는 것일까? 그 해답은 @Component 에 있다.

2-3) @Component

 ㄴ 스프링에게 이 클래스를 자바 빈으로 등록시키라고 알려주는 어노테이션

@component를 클래스에 달기만 하면 무조건 스프링이 검색해서 등록해 준다는 말인가?
그건 아니다. @componentScan 어노테이션이 어떤 클래스에 있어야지만 컴포넌트 스캐닝할 수 있다.
그런데, @componentScan을 보통 사용하지 않는다. 그 이유는 뭘까?
@ComponentScan을 프로젝트 내부에서 사용하지는 않았지만, @SpringBootApplication이 이미 @ComponentScan을 포함하고 있어서 굳이 추가해 주지 않아도 됐던 것이다.

2-4) @ComponentScan

 ㄴ @Component와 @Service, @Repository, @Controller, @Configuration 이 붙은 클래스 Bean들을 찾아서 Context에 bean등록을 해주는 어노테이션

Spring에서 @Component로 전부 사용하지 않고 @Repository, @Service, @Controller 등을 사용하는 이유는
->@Repository DAO의 메소드 등에서 발생할 수 있는 unchecked exception 들을 스프링의 DataAccessException으로 처리할 수 있기 때문이다.

가독성에서도 해당 어노테이션을 갖는 클래스가 무엇을 의미하는지 단 번에 알 수 있는 장점이 있다.

====================================

@Component를 추가하여 자동으로 오브젝트를 스프링에 빈으로 등록하고
@Autowired를 사용하여 스프링이 필요할 때 알아서 오브젝트를 생성해준다.

그러나, 여기서 자동으로 오브젝트를 찾아 생성하는 것이 아니라면 어떻게 해야 할까?
다시 말해, @Component를 추가하지 않고 스프링을 통해 빈을 관리하고 싶을 경우는 어떻게 해야 할까?

====================================

2.4) @Bean

 ㄴ @Bean은 **개발자가 직접 제어 불가능한 외부 라이브러리 등을 Bean 으로 생성해야 할 때** 사용되는 어노테이션이다.

개발자가 자동으로 연결되는 빈이 아닌 다른 빈을 사용하고 싶은 경우가 있다. 또는 라이브러리를 사용할 때 이 라이브러리 클래스가 스프링 기반이 아니라서 @Component를 추가하지 못하는 경우도 있다.
이러한 경우에 스프링으로 빈을 관리하려면 직접적으로 "Bean을 생성해라."라고 말해줄 필요가 있다. 그 작업을 위한 어노테이션이 바로 @Bean 이다.

@Bean을 이용해 스프링에게 이 오브젝트를 정확히 어떻게 생성해야 하는지, 매개변수를 어떻게 넣어줘야 하는지 알려줄 수 있다.

=====================================
정리

1. 스프링 부트 애플리케이션이 시작한다.
2. @ComponentScan 어노테이션이 있는 경우 베이스 패키지와 그 하위 패키지에서 @Component 가 달린 클래스를 찾는다.
3. 필요한 경우 @Component가 달린 클래스의 오브젝트를 생성한다. 이 때, 생성하려는 오브젝트가 다른 오브젝트에 의존한다면, 즉 멤버 변수로 다른 캘르스를 갖고 있다면 그 멤버 변수 오브젝트를 찾아 넣어줘야 한다.
   @Autoworied를 사용하는 경우 스프링이 그 오브젝트를 찾아 생성해 넣어준다.
  a. 이 때 @Autowired에 연결된 변수의 클래스가 @Component가 달린 클래스인 경우 스프링이 오브젝트를 생성해 넘겨준다.
  b. 만약 @Bean 어노테이션으로 생성하는 오브젝트인 경우 @Bean이 달린 메서드를 불러 생성해 넘겨준다.

=====================================

2.5) @Builder

 ㄴ lombok에서 사용하는 어노테이션
 ㄴ 오브젝트를 생성을 위한 디자인 패턴 중 하나
 ㄴ @Builder 어노테이션을 사용하면 Builder 클래스를 따로 개발하지 않고도 Builder 패턴을 사용해 오브젝트를 생성할 수 있다.
TodoEntity todo = TodoEntity.builder()
				  .id("t-10328373")
                  .userId("developer")
                  .title("Implement Model")
                  .build();

이렇게 Builder 패턴을 사용하는 것은 생성자를 이용해 오브젝트를 생성하는 것과 비슷하다. 생성자를 이용하는 것과 비교해 장점이 있다면 생성자 매개변수의 순서를 기억할 필요가 없다는 점이다.


2.6) @NoArgsConstructor

 ㄴ 매개변수가 없는 생성자를 구현

@NoArgsConstructor를 사용하는 것은 아래와 같은 생성자를 구현하는 것과 같다.

public TodoEntity() {

}

2.7) @AllArgsConstructor

 ㄴ @AllArgsConstructor 어노테이션은 클래스의 모든 멤버 변수를 매개변수로 받는 생성자를 구현해 준다.

@AllArgsConstructor를 사용하는 것은 아래와 같은 생성자를 구현하는 것과 같다.

public TodoEntity(String id, String userId, String title, boolean done)
{
	super();
    this.id = id;
    this.userId = userId;
    this.title = title;
    this.done = done;
}

2.8) @Data

 ㄴ @data 어노테이션은 클래스 멤버 변수의 Getter/Setter 메서드를 구현해준다.
public String getId() {
	return id;
}
public void setId(String id) {
	this.id = id;
}
public String getUserId() {
	return userId;
}
public void setUserId(String userId) {
	this.userId = userId;
}
public String getTitle() {
	return title;
}
public String setTitle(String title) {
	this.title = title;
}
public boolean isDone() {
	return done;
}
public void setDone(boolean done) {
	this.done = done;
}




DTO (Data Transfer Object)

계층간 데이터 교환을 위한 JAVA bins

(Controller, View, Business Layer, Persistent Layer 계층 간 데이터 교환을 위한 객체를 DTO 또는 VO 라고 부른다.)

서비스가 요청을 처리하고 클라이언트로 반환할 때 모델 자체를 그대로 리턴하는 경우는 별로 없다.
보통은 데이터를 전달하는 데 사용하는 오브젝트인 Data Transfer Object (DTO) 로 변환해 리턴한다.
왜 그냥 모델을 리턴하지 않고 DTO로 변환하는 것인가?
-> 첫 번째 이유는 비즈니스 로직을 캡슐화 하기 위함이다. 모델은 데이터베이스 테이블 구조와 매우 유사하다. 모델이 갖고 있는 필드들은 테이블의 스키마와 비슷할 확률이 높다. 대부분의 비즈니스는 외부인이 자사의 데이터베이스의 스키마를 아는 것을 원치 않는다. 이 때, DTO처럼 다른 오브젝트로 바꿔 반환하면 외부 사용자에게 서비스 내부의 로직, 데이터베이스의 구조 등을 숨길 수 있다.
-> 두 번째 이유는 클라이언트가 필요한 정보를 모델이 전부 포함하지 않는 경우가 많기 때문이다. 가장 대표적인 예로 에러 메시지가 있다.


REST API

REST는 Representational State Transfer의 약자로 아키텍처 스타일이다.
아키텍처 스타일은 아키텍처 패턴과는 조금 다른데 아키텍처 패턴은 어떤 반복되는 문제 상황을 해결하는 도구이고, 아키텍처 스타일은 반복되는 아키텍처 디자인을 의미한다. REST 아키텍처 스타일은 6가지 제약조건으로 구성된다. 이 가이드라인을 따르는 API를 RESTful API라고 한다.

* REST 제약조건
  ㄴ 클라이언트-서버 (Client-Server)
  ㄴ 상태가 없는    (Stateless)
  ㄴ 캐시되는 데이터 (Cacheable)
  ㄴ 일관적인 인터페이스 (Uniform Interface)
  ㄴ 레이어 시스템   (Layered System)
  ㄴ 코드-온-디맨드  (Code-On-Demand) (선택사항)
* 클라이언트-서버
  : 클라이언트-서버라는 것은 리소스를 관리하는 서버가 존재하고 다수의 클라이언트가 리소스를 소비하려고 네트워크를 통해 서버에 접근하는 구조를 의미.
  이런 구조 중 우리에게 가장 친숙한 것이 바로 웹 애플리케이션이고 우리가 만들려는 Todo앱도 클라이언트(브라우저)-서버 구조다.
==========================================
Tip
: 리소스란 REST API가 리턴할 수 있는 모든 것을 의미한다. 예를 들어 HTML, JSON, 이미지 등이다.
* 상태가 없음
  :상태가 없다(Stateless)는 것은 클라이언트가 서버에 요청을 보낼 때, 이전 요청의 영향을 받지 않음을 의미한다.
  예를 들어 /login으로 로그인 요청을 보내고 로그인이 돼 다음 페이지인 /page로 넘어갔다고 가정하자. 
/page로 리소스를 불러올 때 이전 요청에서 login한 사실을 서버가 알고있어야 한다면 그것은 상태가 있는 아키텍처다.
서버가 그 사실을 알지 못한다면 상태가 없는 것이다.
그럼 로그인을 어떻게 하란 말인가? 또 부득이한 경우 상태를 어떻게 유지하는가?
  클라이언트는 서버에 요청을 할 때마다 요청에 리소스를 받기 위한 모든 정보를 포함해야 한다.
예를 들어, 로그인의 경우 서버는 로그인 상태를 유지하지 못하므로 요청을 보낼 때마다 로그인 정보를 항상 함꼐 보내야 한다. 리소스를 수정한 후 수정한 상태를 유지해야 하는 경우에는 서버가 아닌 데이터베이스 같은 퍼시스턴스에 상태를 저장해야 한다.

HTTP는 기본적으로 상태가 없는 프로토콜이다.
따라서 HTTP를 사용하는 웹 애플리케이션은 기본적으로 상태가 없는 구조를 따른다.



2.10) RestController

 ㄴ Spring에서 Controller 중 View로 응답하지 않는, Controller를 의미
 ㄴ method의 반환 결과를 JSON 형태로 반환한다.
 ㄴ RestController를 이용하면 http와 관련된 코드 및 요청/응답 매핑을 스프링이 알아서 해준다.

@RestController의 내부를 들여다 보면, 크게 두 어노테이션의 조합으로 이루어져 있다.
@RestController = @Controller + @ResponseBody

@Controller : @Component로 스프링이 이 클래스의 오브젝트를 알아서 생성하고 다른 오브젝트들과의 의존성을 연결한다는 뜻이다.

@ResponseBody : 이 클래스의 메서드가 리턴하는 것은 웹 서비스의 ResponseBody라는 뜻. 다시 말해 메서드가 리턴할 때 스프링은 리턴된 오브젝트를 JSON의 형태로 바꾸고 HttpResponse에 담아 반환한다는 뜻이다.

====================================



2.11) GetMapping

 ㄴ @GetMapping 어노테이션을 이용해 이 메서드의 리소스와 HTTP 메서드를 지정한다. 클라이언트가 이 리소스에 대해 Get 메서드를 요청하면 @GetMapping에 연결된 컨트롤러가 실행된다.

팁
@GetMapping, @PostMapping, @PutMapping, @DeleteMapping은 스프링 4.3부터 지원되기 시작했다.

그 이전에는
 @RequestMapping(value="/경로", method=RequestMethod.GET)
 @RequestMapping(value="/경로", method=RequestMethod.POST)
 @RequestMapping(value="/경로", method=RequestMethod.PUT)
 @RequestMapping(value="/경로", method=RequestMethod.DELETE)
처럼 하나의 어노테이션에 HTTP 메서드를 매개변수로 주는 형태로 컨트롤러 메서드를 연결했다.

매개변수를 넘겨받는 방법

/test가 아닌 /test/{id}로 PathVariable이나 /test?id=123처럼 요청 매개변수를 받아야 한다면 어떻게 해야 할까?

2.12) @PathVariable

@PathVariable을 이용하면 /{id}와 같이 URI의 경로로 넘어오는 값을 변수로 받을 수 있다.


2.13) @RequestParam

@RequestParam을 이용하면 ?id={id}와 같이 요청 매개변수로 넘어오는 값을 변수로 받을 수 있다.


2.14) @ResponseBody

문자열보다 복잡한, 예컨대 오브젝트를 리턴하려면 어떻게 해야 할까? 요청을 통해 오브젝트를 가져올 수 있는데 응답으로 오브젝트를 리턴할 수 있다.
실제로 구현하는 방법은 아주 간단하다. 그냥 오브젝트를 리턴하면 된다. 이런 간단함의 비밀은 @RestController 어노테이션에 있다.

@Controller
@ResponseBody
public @interface RestController {
	...
}
 ㄴ RestController는 @Controller + @ResponseBody 조합으로 이루어져 있다.

@Controller : @Component로 스프링이 이 클래스의 오브젝트를 알아서 생성하고 다른 오브젝트들과의 의존성을 연결한다는 뜻

@ResponseBody : 이 클래스의 메서드가 리턴하는 것은 웹 서비스의 ResponseBody라는 뜻
: 다시 말해, 메서드가 리턴할 때 스프링은 리턴된 오브젝트를 JSON의 형태로 바꾸고 HttpResponse에 담아 반환한다는 뜻이다.

팁
스프링이 오브젝트를 JSON으로 바꾸는 것처럼 오브젝트를 저장하거나 네트워크를 통해 전달할 수 있도록 변환하는 것을 직렬화(serialization)라고 하고, 반대의 작업을 역직렬화(deserialization)라고 한다.

ResponseEntity

우리가 작성할 컨트롤러는 모두 ResponseEntity를 반환할 예정이다. ResponseEntity는 HTTP 응답의 바디뿐만 아니라 여러 다른 매개변수들, 예를 들어 status나 header를 조작하고 싶을 때 사용한다.




빌드 자동화 툴 : Gradle과 라이브러리

Grale은 빌드 자동화 툴이다.
빌드 자동화 툴을 이용하면 컴파일, 라이브러리 다운로드, 패키징, 테스팅 등을 자동화할 수 있다.
빌드 자동화를 사용하는 이유는 모든 자동화의 시작은 반복 작업에서 시작된다.

웹 애플리케이션을 만들려면 여러 가지 라이브러리가 필요하다. 빌드 자동화 툴이 없다면 라이브러리의 사용을 위해 라이브러리 사으테엇 jar 파일을 다운로드 받아야 한다. 그리고 이클립스의 Project Build Path에 이 라이브러리를 추가해야 한다.

라이브러리를 추가하면 코드에서 이 라이브러리를 사용할 수 있다. 우리는 프로젝트 진행을 위해 스프링 스타트 사이트(https://start.spring.io/)에서 6개의 라이브러리를 추가했다.
그런데, 빌드 자동화 툴이 없다면 어떤 사이트를 가서 어떤 jar 파일을 받아야 할 지...
이렇게 작업하는데 시간이 더 필요할 것이다.

대규모 프로젝트에서는 몇 백 개 이상의 라이브러리를 기본으로 사용한다. 오퍼레이터에게 이 작업을 반복적으로 요구하는 것은 인적 자원의 낭비다. 그래서 이 작업을 자동화한다.

빌드 자동화 툴을 사용하면 자동화 할 수 있다. 라이브러리를 다운받는 대신 원하는 라이브러리와 버전을 코드로 작성한다. 오퍼레이터가 직접 컴파일, 빌드, 유닛 테스트를 실행하는 대신 이 과정을 일련의 코드로 적는다. 그러면 빌드 자동화 툴이 이 코드를 해석해 프로젝트 빌드에 필요한 작업을 실행해준다.

자동화 툴 중 하나인 그래들(https://gradle.org/)은 자바, 그루비, 스칼라 등 JVM에서 실행되는 언어의 빌드 자동화를 위해 사용된다. 그래들은 그루비라는 언어로 작성돼있다.

profile
VillainDeveloper

0개의 댓글