Java/Spring Boot
[Java] Spring Web Annotation 이해하고 사용하기 -3 : 예외 처리 및 주입
adjh54
2023. 11. 12. 16:33
반응형
반응형
해당 글에서는 Spring Web에서 사용되는 주요 어노테이션 중 '예외처리'와 '주입'과 관련된 어노테이션의 종류에 대해 상세히 알아봅니다.
💡 [참고] 이전에 작성한 글(환경구성 및 요청/응답 어노테이션)을 참고하셔도 좋을 것 같습니다.
💡 [참고] 또한 Exception 구성방법(Global Exception, Business Exception)과 관련하여 아래의 글을 참고하셔도 좋을 것 같습니다.
1) Spring Boot Web
💡 Spring Boot Web
- Spring Boot 프레임워크의 일부로 웹 애플리케이션을 빠르고 쉽게 구축할 수 있도록 도와주는 도구입니다.
- 내장된 웹 서버를 제공하여 웹 애플리케이션을 실행하고 관리하는 데 필요한 모든 설정을 자동으로 처리합니다. (내장된 웹 서버로 Tomcat, Jetty, Undertow와 같은 서버를 사용할 수 있습니다.)
- 이를 사용하면 간단한 설정으로 HTTP 엔드포인트를 생성하고 관리할 수 있습니다. 또한, Spring MVC와 같은 웹 프레임워크와 통합되어 효율적인 웹 애플리케이션 개발을 지원합니다.
- RESTful API, 웹 서비스, 단일 페이지 애플리케이션 등 다양한 유형의 웹 애플리케이션 개발에 적합합니다.
💡 [참고] Spring Boot Web을 사용하기 위한 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' // Spring Boot Web
}
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
1. 주요 어노테이션
어노테이션 | 분류 | 설명 |
@ControllerAdvice | 예외처리 | 예외 처리를 위한 어노테이션입니다. |
@RestControllerAdvice | 예외처리 | RESTful API에서 사용되는 예외 처리를 위한 어노테이션입니다. |
@ExceptionHandler | 예외처리 | 예외 처리를 위한 어노테이션입니다. |
@Transactional | 트랜잭션 | 해당 메서드 또는 클래스에 트랜잭션 기능을 적용하는 어노테이션입니다. 메서드 내에서 실행되는 모든 데이터베이스 조작은 하나의 트랜잭션으로 처리됩니다. |
@Autowired | 주입 및 의존성 | 필드나 메서드에 자동으로 의존성을 주입하는 어노테이션입니다. |
@Value | 프로퍼티 값 주입 | 필드에 프로퍼티 값을 주입하는 어노테이션입니다. 스프링의 환경 설정 파일에서 지정한 값을 가져와서 필드에 할당합니다. |
@Qualifier | 빈 주입 | 같은 타입의 여러 빈이 존재할 때, 명시적으로 주입할 빈을 지정하는 어노테이션입니다. |
@Primary | 빈 주입 | 여러 빈 중에서 기본적으로 주입할 빈을 지정하는 어노테이션입니다. @Qualifier와 함께 사용되며, 명시적인 지정이 없을 경우에 기본 빈으로 사용됩니다. |
@Profile | 빈 주입 | 특정 프로파일에 대해 빈을 등록하거나 제외하기 위해 사용되는 어노테이션입니다. 대상 프로파일이 활성화되어 있을 때만 해당 빈이 사용됩니다. |
2) Spring Web Annotation 주요 어노테이션 : 예외처리
1. @ControllerAdvice
💡 @ControllerAdvice
- Java 스프링 프레임워크에서 ‘전역 예외 처리’와 관련된 작업을 수행하는 어노테이션입니다.
- @Controller로 선언한 클래스에서 발생하는 예외 처리에 대해 해당 선언한 클래스에서 이를 캐치하여 중앙에서 처리할 수 있습니다.
- 이를 통해 중복된 예외 처리 코드를 제거하고, 일관된 에러 응답을 제공할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
annotations | 예외를 스캔할 주석을 지정합니다. |
assignableTypes | 어드바이스를 적용할 컨트롤러의 타입을 지정합니다. |
basePackages | 컨트롤러를 스캔할 기본 패키지를 지정합니다. |
basePackageClasses | 컨트롤러를 스캔할 기본 클래스를 지정합니다. |
value | 어드바이스를 적용할 특정 컨트롤러 클래스를 지정합니다. |
parameters | 컨트롤러의 메소드에 대해 일치하는 매개변수를 지정합니다. |
annotations | 컨트롤러의 메소드에 대해 일치하는 주석을 지정합니다. |
before | 일치하는 메소드 이전에 실행할 어드바이스를 지정합니다. |
afterReturning | 일치하는 메소드가 성공적으로 리턴된 후 실행할 어드바이스를 지정합니다. |
afterThrowing | 일치하는 메소드에서 예외가 발생한 후 실행할 어드바이스를 지정합니다. |
after | 일치하는 메소드 이후에 실행할 어드바이스를 지정합니다. |
around | 일치하는 메소드를 둘러싸고 실행할 어드바이스를 지정합니다. |
order | 여러 어드바이스가 적용되는 순서를 지정합니다. |
proxyTargetClass | 클래스 기반 (CGLIB) 프록시 또는 인터페이스 기반 (JDK 동적) 프록시를 사용할지 여부를 지정합니다. |
scope | 어드바이스 빈의 범위를 지정합니다. (싱글톤, 프로토타입 등) |
name | 어드바이스 빈의 이름을 지정합니다. |
annotations | 컨트롤러 어드바이스를 스캔할 주석을 지정합니다. |
basePackages | 컨트롤러 어드바이스를 스캔할 기본 패키지를 지정합니다. |
basePackageClasses | 컨트롤러 어드바이스를 스캔할 기본 클래스를 지정합니다. |
💡 @ControllerAdvice 사용 예제 : Global Exception을 구성하는 Handler 입니다.
- @ControllerAdvice를 통해서 해당 클래스가 전역 예외처리를 담당하는 클래스로 지정하였습니다.
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
System.out.println("MethodArgumentNotValidException 에러를 캐치");
}
}
💡 [참고] 해당 예시에 대해 상세히 알고 싶으시면 아래의 글을 참고하시면 도움이 됩니다.
2. @RestControllerAdvice
💡 @RestControllerAdvice
- Java 스프링 프레임워크에서 RESTful API 개발 시 예외 처리와 관련된 작업을 효과적으로 수행할 수 있도록 도와주는 어노테이션입니다.
- @RestController로 선언한 전역 예외 처리를 담당하는 클래스를 정의할 수 있습니다. 이 클래스는 @ExceptionHandler 어노테이션과 함께 사용되어 특정 예외 타입에 대한 처리 로직을 구현할 수 있습니다.
- 예를 들어, 특정 예외가 발생할 경우 클라이언트에게 적절한 에러 응답을 반환하거나 로깅하는 등의 작업을 수행할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
value | 적용할 범위를 지정합니다. 특정 패키지나 클래스를 대상으로 할 수 있습니다. 기본값은 모든 패키지가 대상입니다. |
annotations | 특정 어노테이션이 적용된 예외에 대해서만 처리할 수 있도록 지정합니다. |
basePackages | 적용할 패키지를 지정합니다. value와 동일한 역할을 합니다. |
basePackageClasses | 적용할 클래스를 지정합니다. value와 동일한 역할을 합니다. |
assignableTypes | 특정 타입의 예외에 대해서만 처리할 수 있도록 지정합니다. |
useDefaultFilters | 기본 필터를 사용할지 여부를 지정합니다. 기본값은 true입니다. |
includeFilters | 특정 필터를 추가합니다. |
excludeFilters | 특정 필터를 제외합니다. |
💡 @RestControllerAdvice 사용 예제 : Global Exception을 구성하는 Handler입니다.
- @RestControllerAdvice를 통해서 해당 클래스가 전역 예외처리를 담당하는 클래스로 지정하였습니다.
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
System.out.println("MethodArgumentNotValidException 에러를 캐치");
}
}
[ 더 알아보기 ]
💡 @ControllerAdvice와 @RestControllerAdvice 어노테이션 차이
- @ControllerAdvice는 일반적인 컨트롤러 설정과 예외 처리에 사용되고, @RestControllerAdvice는 RESTful API에서 예외 처리와 오류 응답을 사용자 정의하는 데 사용됩니다.
💡 [참고] 해당 예시에 대해 상세히 알고 싶으시면 아래의 글을 참고하시면 도움이 됩니다.
3. @ExceptionHandler
💡 @ExceptionHandler
- Java 스프링 프레임워크에서 요청 핸들러 메소드메서드 실행 중 예외가 발생할 때, @ExceptionHandler 어노테이션을 특정 예외를 처리하는 메서드에 적용할 수 있습니다.
- 이 메소드는 예외를 처리하기 위해 사용자 정의 로직을 제공할 수 있으며, 특정한 에러 응답을 반환하거나 에러 페이지로 리디렉션 하는 등의 작업을 수행할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
value (또는 exception) | 처리하고자 하는 특정 예외 타입을 지정합니다. |
produces | 응답으로 반환될 미디어 타입을 지정합니다. |
responseStatus | 예외 처리 후 반환될 HTTP 응답 상태 코드를 지정합니다. |
reason | 예외 처리 후 반환될 HTTP 응답 메시지를 지정합니다. |
💡 @ExceptionHandler 사용 예시
- @RestControllerAdvice 환경에서 @ExceptionHandler 어노테이션의 속성으로 MethodArgumentNotValidException.class울 정의하였습니다.
- 해당 MethodArgumentNotValidException이 발생하였을 경우 이를 캐치하여 해당 메서드가 수행하도록 하여 “MethodArgumentNotValidException 에러를 캐치” 콘솔을 출력합니다.
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
System.out.println("MethodArgumentNotValidException 에러를 캐치");
}
}
💡 [참고] 해당 예시에 대해 상세히 알고 싶으시면 아래의 글을 참고하시면 도움이 됩니다.
3) Spring Web Annotation 주요 어노테이션: 기타
1. @Transactional
💡 @Transactional
- 스프링 프레임워크에서 특정 메소드 또는 클래스에서 수행되는 ‘트랜잭션’과 관련되어 관리를 위해서 사용되는 어노테이션입니다.
- 메서드 또는 클래스에 해당 어노테이션을 선언하면 코드 실행 중에 트랜잭션에 오류가 발생되면 트랜잭션이 롤백되고 변경 사항이 모두 취소됩니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 속성값 | 설명 |
propagation | REQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED | 트랜잭션 전파 동작을 지정 |
isolation | DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE | 트랜잭션 격리 수준을 설정 |
readOnly | true, false | 읽기 전용 트랜잭션으로 설정 |
timeout | 초 단위의 정수 값 | 트랜잭션 타임아웃 시간을 설정 |
rollbackFor | 예외 클래스 | 롤백을 수행할 예외 클래스를 지정 |
noRollbackFor | 예외 클래스 | 롤백을 수행하지 않을 예외 클래스를 지정 |
💡 [참고] propagation 속성 값에 대한 설명
propagation 속성 값 | 설명 |
REQUIRED | 이미 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 시작합니다. |
SUPPORTS | 이미 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 트랜잭션 없이 진행합니다. |
MANDATORY | 이미 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 예외를 발생시킵니다. |
REQUIRES_NEW | 항상 새로운 트랜잭션을 시작합니다. |
NOT_SUPPORTED | 트랜잭션 없이 진행합니다. 이미 진행 중인 트랜잭션을 일시 중지시킵니다. |
NEVER | 트랜잭션 없이 진행합니다. 이미 진행 중인 트랜잭션이 있으면 예외를 발생시킵니다. |
NESTED | 이미 진행 중인 트랜잭션이 있으면 중첩 트랜잭션을 시작합니다. |
💡 [참고] isolation 속성 값에 대한 설명
isolation 속성 값 | 설명 |
DEFAULT | 기본 격리 수준으로 데이터베이스의 기본 설정을 따릅니다. |
READ_UNCOMMITTED | 트랜잭션에서 수정 중인 데이터를 다른 트랜잭션에서 읽을 수 있습니다. |
READ_COMMITTED | 트랜잭션이 커밋된 데이터만 읽을 수 있습니다. |
REPEATABLE_READ | 트랜잭션 내에서 조회한 데이터는 다른 트랜잭션에서 수정할 수 없습니다. |
SERIALIZABLE | 트랜잭션 동안 다른 트랜잭션에서 데이터를 수정할 수 없습니다. |
💡 @Transactional 사용 예시
- 해당 TemplateServiceImpl 클래스는 TemplateService의 인터페이스의 구현체 파일입니다.
- @Transactional의 어노테이션은 메소드 단위로 적용을 하였습니다.
- 트랜잭션이 조회 처리를 하는 경우 @Transactional(readOnly = true) 속성을 적용하였고 이외 실제 트렌잭션이 발생하는 곳에는 @Transactional을 선언하였습니다.
- 이러한 선언을 통해 잘못된 데이터가 발생하는 경우 처리에 대한 롤백이 수행되도록 구성하였습니다.
/**
* [ 템플릿 설명 ]
* - 해당 파일은 서비스의 비즈니스 로직을 구현하는 곳입니다.
* - 해당 *ServiceImpl 에서는 @Service 어노테이션을 필수적으로 사용합니다.
*/
@Slf4j
@Service
public class TemplateServiceImpl implements TemplateService {
/**
* '생성자 주입 형태'로 사용합니다.
* - Autowired 는 권장되지 않기에 생성자 주입 형태로 구성합니다.
*/
private final SqlSession sqlSession;
public TemplateServiceImpl(SqlSession ss) {
this.sqlSession = ss;
}
/**
* Template 리스트 값 조회
*
* @return List<TemplateVO> 반환값
*/
@Transactional(readOnly = true)
public List<TemplateVO> selectTempList() {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.selectTempList();
}
/**
* 키 값을 기반으로 Template 리스트 조회
*
* @param templateId 조회 키 값
* @return TemplateVO 반환 값
*/
@Transactional(readOnly = true)
public TemplateVO selectTempById(Integer templateId) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.selectTempById(templateId);
}
/**
* Template INSERT
*
* @param templateVO 저장 할 값
* @return TemplateVO 결과값 반환
*/
@Transactional
public int insertTemp(TemplateVO templateVO) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.insertTemp(templateVO);
}
/**
* Template Update
*
* @param templateVO Update Value
* @return TemplateVO 결과값 반환
*/
@Transactional
public int updateTemp(TemplateVO templateVO) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.updateTemp(templateVO);
}
/**
* Template Delete
*
* @param tempId 삭제 아이디
* @return TemplateVO 결과값 반환
*/
@Transactional
public int deleteTempById(Integer templateId) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.deleteTempById(templateId);
}
}
💡 [참고] 해당 예시는 이전에 작성한 글에서 가져왔습니다.
4) Spring Web Annotation 주요 어노테이션 : 주입
1. @Autowired
💡 @Autowired
- Java 스프링 프레임워크에서 사용되는 ‘자동 의존성 주입’을 위한 어노테이션입니다. 주로 컴포넌트 간의 의존성을 자동으로 연결하기 위해 사용하는 어노테이션입니다.
- @Autowired를 필드, 생성자 또는 설정 메서드에 어노테이션으로 달면, Spring은 해당 컴포넌트에 적절한 빈을 자동으로 해결하고 주입합니다. 이를 통해 컴포넌트 간의 느슨한 결합이 가능하며, 애플리케이션의 의존성 관리 과정을 간소화할 수 있습니다.
[ 더 알아보기 ]
💡 의존성 주입을 하지 않으면 어떻게 될까? 예를 들어 Controller에서 Service의 Interface 메서드를 가져오려고 해
- Controller에서 Service 인터페이스를 호출할 때 의존성을 주입하지 않으면 예외나 오류가 발생할 수 있습니다. Controller는 Service 인터페이스가 제공하는 메서드와 기능에 접근할 수 없게 됩니다.
💡 클래스에서 클래스를 호출하는 경우도 있는데 그건 잘 되는데? 예를 들어 Service에서 공통 유틸 클래스를 호출해서 사용하는 것 같이 수행하는것은 잘 되는데?
- 클래스에서 클래스를 호출하여 가져올 수 있는 경우에는 의존성 주입을 사용하지 않을 수 있습니다. 이는 간단한 의존성 관리나 작은 규모의 애플리케이션에서 흔히 사용되는 방식입니다. 그러나 보다 복잡한 애플리케이션 개발이나 테스트 용이성 등을 위해 의존성 주입을 사용하는 것이 좋습니다.
💡 의존성 주입을 하는 이유는 뭘까?
1. 코드의 유지 보수성과 확장성을 향상합니다.
- 의존성 주입을 통해 컴포넌트 간의 결합도를 낮출 수 있고, 의존하는 객체를 쉽게 교체하거나 확장할 수 있습니다.
2. 테스트 용이성을 제공합니다.
- 의존하는 객체를 모의(mock) 객체로 대체하여 단위 테스트를 수행할 수 있으며, 테스트할 때 원하는 동작을 구현한 모의 객체를 주입할 수 있습니다.
3. 코드의 가독성과 이해도를 높여줍니다.
- 클래스가 직접 의존하는 객체를 생성하거나 가져오는 것보다, 외부에서 의존 객체를 주입받는 방식은 코드의 의도를 명확하게 표현할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
required | 의존성이 autowired 되어야 하는지를 지정합니다. 기본값은 true입니다. |
value | autowiring에 사용될 빈 이름 또는 빈 클래스입니다. |
qualifier | 이름에 의한 autowiring을 위한 한정자 값입니다. |
annotationType | autowiring에 사용될 주석 유형입니다. |
name | autowiring에 사용될 빈 이름입니다. |
defaultValue | 요청된 유형의 빈이 존재하지 않을 경우 사용할 기본값입니다. |
💡 해당 예시에서는 아래와 같은 아키텍처 구조를 가지고 있습니다.
- 아래의 예시에서는 Controller 내에서 @Autowired 어노테이션을 통해서 Service(Interface)의 의존성을 주입받아올 예정입니다.
💡 @Autowired 사용 예시
- CodeController는 코드 정보를 관리하는 API입니다.
- @Autowired 어노테이션을 통해서 서비스의 의존성을 주입하여서 Controller 내에서 사용함을 확인할 수 있습니다.
package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.common.codes.SuccessCode;
import com.adjh.multiflexapi.common.response.ApiResponse;
import com.adjh.multiflexapi.model.CodeDto;
import com.adjh.multiflexapi.service.CodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 코드 정보를 관리하는 Controller
*/
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/code")
@Tag(name = "Code", description = "코드 API")
public class CodeController {
@Autowired
private CodeService codeService;
/**
* [API] 코드 리스트 출력
*
* @param codeDto codeDto
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@PostMapping("/codes")
@Operation(summary = "코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCodeList(@RequestBody @Validated CodeDto codeDto) {
log.debug("코드의 모든 리스트를 조회합니다.");
CodeDto resultDto = codeService.selectCode(codeDto);
ApiResponse<Object> ar = ApiResponse.builder()
.result(resultDto)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* [API] 코드 단건 조회 : 코드 키 값을 기반으로 조회합니다.
*
* @param cd String
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@GetMapping("/code")
@Operation(summary = "코드 값 별 코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCodeByCd(@RequestParam String cd) {
CodeDto codeItem = codeService.selectCodeByCd(cd);
ApiResponse<Object> ar = ApiResponse.builder()
.result(codeItem)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* [API] 코드 단건 조회 : 코드 키 값을 기반으로 조회합니다.
*
* @param codeDto
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@PostMapping("/codeItem")
@Operation(summary = "코드 값 별 코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCode(@RequestBody CodeDto codeDto) {
CodeDto codeItem = codeService.selectCode(codeDto);
ApiResponse<Object> ar = ApiResponse.builder()
.result(codeItem)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
}
💡 [참고] @Autowired 방식은 권장하지 않습니다 : IntelliJ - Field Injection is not recommended
- 해당 필드 주입 방식은 아래와 같은 이유로 추천을 하지 않고 있습니다.
1. 테스트 용이성 감소
- 필드 주입은 주입된 의존성을 가진 클래스에 대한 단위 테스트 작성을 어렵게 만듭니다. 테스트 중에 주입된 의존성을 목 객체로 대체하거나 모의 객체로 대체하기 어려울 수 있습니다.
2. 숨겨진 의존성
- 필드 주입을 사용하면 의존성이 생성자나 메서드 매개변수에 명시적으로 선언되지 않습니다. 이로 인해 클래스의 의존성을 이해하기 어려워지며, 숨겨진 의존성이 발생할 수 있습니다.
3. 강한 결합
- 필드 주입은 클래스와 해당 의존성 간에 강한 결합을 만듭니다. 이로 인해 코드베이스가 유연성이 떨어지고 유지보수가 어려워질 수 있습니다. 의존성에 변경이 발생하면 해당 의존성을 사용하는 클래스를 수정해야 할 수도 있습니다.
4. 제어 부족
- 필드 주입은 의존성의 수명 주기를 제어하는 데 제한적인 제어를 제공합니다. 의존성의 생성과 소멸을 관리하기 어려워지며, 이로 인해 메모리 누수나 리소스 관리 문제가 발생할 수 있습니다.
https://www.baeldung.com/java-spring-field-injection-cons
💡 그럼 어떤 방식으로 의존성 주입을 하는 것이 좋을까?
- IntelliJ 추천으로는 필드 의존성 주입보다는 ‘생성자 의존성 주입’ 방식을 추천해주고 있습니다.
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/code")
public class CodeController {
// 적용 이전
@Autowired
private CodeService codeService;
// 적용 이후
private final CodeService codeService;
public CodeController(CodeService codeService) {
this.codeService = codeService;
}
}
2. @Value
💡 @Value
- Java 스프링 프레임워크에서 필드, 메서드 또는 생성자 인수에 값을 주입하기 위해 사용됩니다. 일반적으로 ‘구성 파일이나 환경 변수에서 속성 값을 주입’하는 데 사용됩니다.
- 이를 사용하려면 속성에 대한 값을 정의해야 합니다. 기본적으로 Spring Boot 프로젝트를 사용 중이라면 기본은 application.properties 파일을 가리키고 있습니다.
💡 [참고] application.properties 파일에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
${app.name} | app.name 속성의 값을 주입합니다. |
${app.name:default} | app.name 속성의 값을 주입하되, 속성이 정의되지 않은 경우 default 값을 사용합니다. |
${app.name:} | app.name 속성의 값을 주입하되, 속성이 정의되지 않은 경우 빈 문자열을 사용합니다. |
${app.count:#{10}} | app.count 속성의 값을 주입하되, 속성이 정의되지 않은 경우 기본값으로 10을 사용합니다. |
${app.names:#{'Alice','Bob'}} | app.names 속성의 값을 주입하되, 속성이 정의되지 않은 경우 기본값으로 'Alice'와 'Bob'을 사용합니다. |
💡 @Value 사용 예시 : application.properties
app.name=MyApp
app.description=Normal Appliction
💡 @Value 사용 예시 : CodeController
- @Value("${app.name}")를 통해 application.properties 파일의 app.name을 찾아서 값을 주입합니다.
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/code")
@Tag(name = "Code", description = "코드 API")
public class CodeController {
@Autowired
private CodeService codeService;
@Value("${app.name}")
private String appName; // MyApp
}
💡 [참고] yml파일로 관리하고 있는데 그럼 어떻게 불러와야 하는가?
- spring.profiles.active 속성을 통해서 yml 파일을 불러오고 있습니다.
- application.properties와 동일하게 불러오면 됩니다.
spring.profiles.active=multiflex-local, multiflex-oauth
server.tomcat.basedir=.
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%{yyyy-MM-dd HH:mm:ss}t\\t%s\\t%r\\t%{User-Agent}i\\t%{Referer}i\\t%a\\t%b
💡 [참고] application-multiflex-local.yml
# Custom Key
custom:
app:
name: MyApp
💡 @Value 사용 예시 : CodeController
- @Value("${custom.app.name}")를 통해 application-multiflex-local.yml 파일의 custom.app.name을 찾아서 값을 주입합니다.
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/code")
@Tag(name = "Code", description = "코드 API")
public class CodeController {
@Autowired
private CodeService codeService;
@Value("${custom.app.name}")
private String appName; // MyApp
}
3. @Qualifier
💡 @Qualifier
- Java 스프링 프레임워크에서 동일한 타입의 빈이 여러 개 존재할 때 어떤 빈을 주입해야 하는지 명시적으로 지정할 수 있는 어노테이션입니다.
- 이를 통해 스프링은 어떤 빈을 주입해야 하는지 충돌이나 모호함 없이 알 수 있습니다.
- 주로 의존성 주입(Dependency Injection) 시 사용되며, 빈의 이름이나 특정한 속성 값을 기준으로 빈을 선택할 때 유용합니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
value | 빈의 이름을 기반으로 빈을 선택합니다. |
qualifier | 지정된 한정자(qualifier)를 사용하여 빈을 선택합니다. |
type | 빈의 타입을 기반으로 빈을 선택합니다. |
attribute | 특정한 속성 값을 기준으로 빈을 선택합니다. |
💡 @Qualifier 사용 예시
- 해당 예시에서는 ExampleBean 클래스와 ExampleService 클래스가 존재합니다.
1. ExampleBean 클래스에서 @Qualifier("exampleQualifier")로 한정자를 지정하였습니다.
2. ExampleService 클래스에서는 ExampleBean을 주입받을 때 @Qualifier("exampleQualifier")를 지정하여 어떤 빈을 주입받는지 명시를 합니다.
@Component
@Qualifier("exampleQualifier")
public class ExampleBean {
// ...
}
@Component
public class ExampleService {
private ExampleBean exampleBean;
@Autowired
public ExampleService(@Qualifier("exampleQualifier") ExampleBean exampleBean) {
this.exampleBean = exampleBean;
}
// ...
}
4. @Primary
💡 @Primary
- Java 스프링 프레임워크에서 여러 개의 동일한 타입 빈 중에서 ‘기본적으로 주입할 빈’을 지정할 수 있습니다.
- 이를 통해 스프링은 @Autowired나 @Inject 등의 어노테이션을 사용하여 해당 타입의 빈을 주입받을 때 기본적으로 @Primary로 지정된 빈을 주입합니다
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
없음 | 해당 빈을 기본적으로 주입할 빈으로 지정합니다. |
💡 @Primary 사용 예시
- 해당 예시에서는 PrimaryExampleBean 클래스와 SecondaryExampleBean 클래스, ExampleService 클래스가 존재합니다.
1. PrimaryExampleBean 클래스에서 @Primary를 추가하여 해당 빈을 기본적으로 주입할 빈으로 지정합니다. 그리고 ExampleService 클래스에서 ExampleBean을 주입받을 때, @Autowired 어노테이션만 사용하여 주입받습니다.
2. 이를 통해 스프링은 기본적으로 PrimaryExampleBean을 주입할 빈으로 선택하며, @Primary로 지정된 빈이 없을 경우에는 동일한 타입의 다른 빈을 주입합니다.
@Component
@Primary
public class PrimaryExampleBean implements ExampleBean {
// ...
}
@Component
public class SecondaryExampleBean implements ExampleBean {
// ...
}
@Component
public class ExampleService {
private ExampleBean exampleBean;
@Autowired
public ExampleService(ExampleBean exampleBean) {
this.exampleBean = exampleBean;
}
// ...
}
5. @Profile
💡 @Profile
- Java 스프링 프레임워크에서 ‘특정 환경이나 조건에서만 동작하는 빈을 지정’할 수 있습니다. 이를 통해 스프링 애플리케이션을 다양한 환경에서 유연하게 구성하고 관리할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성 | 설명 |
없음 | 해당 빈은 모든 환경에서 사용됩니다. |
"profile" | 지정된 프로파일에 해당하는 환경에서만 해당 빈이 사용됩니다. 여러 개의 프로파일을 지정할 수 있으며, 쉼표로 구분합니다. |
"!profile" | 지정된 프로파일에 해당하지 않는 환경에서 해당 빈이 사용됩니다. |
{"profile1", "profile2"} | 지정된 여러 개의 프로파일 중 하나에 해당하는 환경에서 해당 빈이 사용됩니다. |
{"!profile1", "!profile2"} | 지정된 여러 개의 프로파일에 해당하지 않는 환경에서 해당 빈이 사용됩니다. |
💡 @Profile 사용 예시
- 해당 예시에서는 DevelopmentConfiguration 클래스와 ProductionConfiguration 클래스가 존재합니다. 두 개의 클래스는 @Configuration를 통해 설정 클래스임을 지정하였습니다.
1. DevelopmentConfiguration 클래스에 @Profile("development")를 추가하여 해당 빈들이 개발 환경에서만 사용되도록 설정합니다.
2. 마찬가지로 ProductionConfiguration 클래스에 @Profile("production")을 추가하여 해당 빈들이 운영 환경에서만 사용되도록 설정합니다.
3. 이렇게 설정된 빈들은 각각의 프로파일에 해당하는 환경에서만 사용됩니다. 스프링 애플리케이션을 실행할 때, spring.profiles.active 프로퍼티를 설정하여 원하는 프로파일을 활성화할 수 있습니다.
@Configuration
@Profile("development")
public class DevelopmentConfiguration {
@Bean
public LoggingService loggingService() {
return new LoggingService("DEBUG");
}
}
@Configuration
@Profile("production")
public class ProductionConfiguration {
@Bean
public LoggingService loggingService() {
return new LoggingService("INFO");
}
}
💡 @Profile 사용 예시
- 위에서 구성한 내용을 기반으로 Applcation이 시작될 때 setAdditionalProfiles("development"); 지정을 통해서 @Profile("development")로 지정한 Configuration 파일을 로드합니다.
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setAdditionalProfiles("development"); // 개발 환경 프로파일 활성화
app.run(args);
}
}
💡 [참고] Spring Web Annotation에 대해 더 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
구분 | 링크 |
Spring Web Annotation : 환경 구성 | https://adjh54.tistory.com/311 |
Spring Web Annotation : 요청 및 응답 | https://adjh54.tistory.com/312 |
Spring Web Annotation : 예외처리 및 주입 | https://adjh54.tistory.com/313 |
오늘도 감사합니다. 😀
반응형