Java/Spring Boot

[Java] Spring Web Annotation 이해하고 사용하기 -3 : 예외 처리 및 주입

adjh54 2023. 11. 12. 16:33
반응형
반응형

 
 

해당 글에서는 Spring Web에서 사용되는 주요 어노테이션 중 '예외처리'와 '주입'과 관련된 어노테이션의 종류에 대해 상세히 알아봅니다.





 

💡 [참고] 이전에 작성한 글(환경구성 및 요청/응답 어노테이션)을 참고하셔도 좋을 것 같습니다.

[Java] Spring Web Annotation 이해하고 사용하기 -1 : 환경 구성

해당 글에서는 Spring Web에서 사용되는 주요 어노테이션 중 환경구성과 관련된 어노테이션의 종류에 대해 상세히 알아봅니다. 1) Spring Boot Web 💡 Spring Boot Web - Spring Boot 프레임워크의 일부로 웹 애

adjh54.tistory.com

 

[Java] Spring Web Annotation 이해하고 사용하기 -2 : 요청 및 응답

해당 글에서는 Spring Web에서 사용되는 주요 어노테이션 중 요청/응답과 관련된 어노테이션의 종류에 대해 상세히 알아봅니다. 💡 [참고] 이전에 작성한 Spring Web Annotation '환경구성' 글에서 이어

adjh54.tistory.com

 
 

💡 [참고] 또한 Exception 구성방법(Global Exception, Business Exception)과 관련하여 아래의 글을 참고하셔도 좋을 것 같습니다. 

[Java] Global Exception 이해하고 구성하기 : Controller Exception

해당 글에서는 Controller에서 발생하는 Exception을 Global Exception을 구성하여서 처리하는 방법에 대해서 공유합니다. 1) 개발 환경 💡 Global Exception을 적용하는데 활용한 개발환경입니다. 개발환경 버

adjh54.tistory.com

 

[Java] Business Exception 이해하고 구성하기 : Service Exception

해당 글에서는 business Layer에서 발생하는 오류에 대해서 공통 처리를 위한 Business Exception 대한 구성 방법에 대해 이해하고 구성하는 방법에 대해서 공유합니다. [참고] 이전에 작성한 Global Exception

adjh54.tistory.com

 
 
 

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 (Spring Framework 6.0.13 API)

Specialization of @Component for classes that declare @ExceptionHandler, @InitBinder, or @ModelAttribute methods to be shared across multiple @Controller classes. Classes annotated with @ControllerAdvice can be declared explicitly as Spring beans or auto-d

docs.spring.io

 
 

💡 @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 에러를 캐치");
    }
}

 
 

💡 [참고] 해당 예시에 대해 상세히 알고 싶으시면 아래의 글을 참고하시면 도움이 됩니다.

[Java] Global Exception 이해하고 구성하기 : Controller Exception

해당 글에서는 Controller에서 발생하는 Exception을 Global Exception을 구성하여서 처리하는 방법에 대해서 공유합니다. 1) 개발 환경 💡 Global Exception을 적용하는데 활용한 개발환경입니다. 개발환경 버

adjh54.tistory.com

 
 
 

2. @RestControllerAdvice


💡 @RestControllerAdvice

- Java 스프링 프레임워크에서 RESTful API 개발 시 예외 처리와 관련된 작업을 효과적으로 수행할 수 있도록 도와주는 어노테이션입니다.

- @RestController로 선언한 전역 예외 처리를 담당하는 클래스를 정의할 수 있습니다. 이 클래스는 @ExceptionHandler 어노테이션과 함께 사용되어 특정 예외 타입에 대한 처리 로직을 구현할 수 있습니다.
- 예를 들어, 특정 예외가 발생할 경우 클라이언트에게 적절한 에러 응답을 반환하거나 로깅하는 등의 작업을 수행할 수 있습니다.

 

💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성설명
value적용할 범위를 지정합니다. 특정 패키지나 클래스를 대상으로 할 수 있습니다. 기본값은 모든 패키지가 대상입니다.
annotations특정 어노테이션이 적용된 예외에 대해서만 처리할 수 있도록 지정합니다.
basePackages적용할 패키지를 지정합니다. value와 동일한 역할을 합니다.
basePackageClasses적용할 클래스를 지정합니다. value와 동일한 역할을 합니다.
assignableTypes특정 타입의 예외에 대해서만 처리할 수 있도록 지정합니다.
useDefaultFilters기본 필터를 사용할지 여부를 지정합니다. 기본값은 true입니다.
includeFilters특정 필터를 추가합니다.
excludeFilters특정 필터를 제외합니다.

RestControllerAdvice (Spring Framework 6.0.13 API)

Array of base packages. Controllers that belong to those base packages or sub-packages thereof will be included — for example, @RestControllerAdvice(basePackages = "org.my.pkg") or @RestControllerAdvice(basePackages = {"org.my.pkg", "org.my.other.pkg"}).

docs.spring.io

 

💡 @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에서 예외 처리와 오류 응답을 사용자 정의하는 데 사용됩니다.

 
 

💡 [참고] 해당 예시에 대해 상세히 알고 싶으시면 아래의 글을 참고하시면 도움이 됩니다.

[Java] Global Exception 이해하고 구성하기 : Controller Exception

해당 글에서는 Controller에서 발생하는 Exception을 Global Exception을 구성하여서 처리하는 방법에 대해서 공유합니다. 1) 개발 환경 💡 Global Exception을 적용하는데 활용한 개발환경입니다. 개발환경 버

adjh54.tistory.com

 
 
 
 

3. @ExceptionHandler


💡 @ExceptionHandler

- Java 스프링 프레임워크에서 요청 핸들러 메소드메서드 실행 중 예외가 발생할 때, @ExceptionHandler 어노테이션을 특정 예외를 처리하는 메서드에 적용할 수 있습니다.

- 이 메소드는 예외를 처리하기 위해 사용자 정의 로직을 제공할 수 있으며, 특정한 에러 응답을 반환하거나 에러 페이지로 리디렉션 하는 등의 작업을 수행할 수 있습니다.

 
 

💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성설명
value (또는 exception)처리하고자 하는 특정 예외 타입을 지정합니다.
produces응답으로 반환될 미디어 타입을 지정합니다.
responseStatus예외 처리 후 반환될 HTTP 응답 상태 코드를 지정합니다.
reason예외 처리 후 반환될 HTTP 응답 메시지를 지정합니다.

ExceptionHandler (Spring Framework 6.0.13 API)

Annotation for handling exceptions in specific handler classes and/or handler methods. Handler methods which are annotated with this annotation are allowed to have very flexible signatures. They may have parameters of the following types, in arbitrary orde

docs.spring.io

 
 

💡 @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 에러를 캐치");
    }
}

 

💡 [참고] 해당 예시에 대해 상세히 알고 싶으시면 아래의 글을 참고하시면 도움이 됩니다.

[Java] Global Exception 이해하고 구성하기 : Controller Exception

해당 글에서는 Controller에서 발생하는 Exception을 Global Exception을 구성하여서 처리하는 방법에 대해서 공유합니다. 1) 개발 환경 💡 Global Exception을 적용하는데 활용한 개발환경입니다. 개발환경 버

adjh54.tistory.com

 
 
 
 

3) Spring Web Annotation 주요 어노테이션: 기타


 

1. @Transactional


 💡 @Transactional

- 스프링 프레임워크에서 특정 메소드 또는 클래스에서 수행되는 ‘트랜잭션’과 관련되어 관리를 위해서 사용되는 어노테이션입니다. 
- 메서드 또는 클래스에 해당 어노테이션을 선언하면 코드 실행 중에 트랜잭션에 오류가 발생되면 트랜잭션이 롤백되고 변경 사항이 모두 취소됩니다.


 

💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성속성값설명
propagationREQUIRED, SUPPORTS, MANDATORY, REQUIRES_NEW, NOT_SUPPORTED, NEVER, NESTED트랜잭션 전파 동작을 지정
isolationDEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE트랜잭션 격리 수준을 설정
readOnlytrue, false읽기 전용 트랜잭션으로 설정
timeout초 단위의 정수 값트랜잭션 타임아웃 시간을 설정
rollbackFor예외 클래스롤백을 수행할 예외 클래스를 지정
noRollbackFor예외 클래스롤백을 수행하지 않을 예외 클래스를 지정

 

Transactional (Spring Framework 6.0.13 API)

Describes a transaction attribute on an individual method or on a class. When this annotation is declared at the class level, it applies as a default to all methods of the declaring class and its subclasses. Note that it does not apply to ancestor classes

docs.spring.io

 

💡 [참고] 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);
    }
}

 

💡 [참고] 해당 예시는 이전에 작성한 글에서 가져왔습니다.

[JAVA] Spring Boot내에서 MyBatis & PostgreSQL 연동하기

해당 글에서는 Spring Boot내에 Mybatis와 PostgreSQL을 연결하고 로직 처리를 위한 환경 설정 및 파일 구성에 대해서 공유합니다. 💡 최하단에 해당 환경 구성과 관련하여 '로컬 DB 구성 방법', 'HikariCP'

adjh54.tistory.com

 
 
 
 
 

4) Spring Web Annotation 주요 어노테이션 : 주입


 

1. @Autowired


💡 @Autowired

- Java 스프링 프레임워크에서 사용되는 ‘자동 의존성 주입’을 위한 어노테이션입니다. 주로 컴포넌트 간의 의존성을 자동으로 연결하기 위해 사용하는 어노테이션입니다.

- @Autowired를 필드, 생성자 또는 설정 메서드에 어노테이션으로 달면, Spring은 해당 컴포넌트에 적절한 빈을 자동으로 해결하고 주입합니다. 이를 통해 컴포넌트 간의 느슨한 결합이 가능하며, 애플리케이션의 의존성 관리 과정을 간소화할 수 있습니다.

 

https://adjh54.tistory.com/298

 
 

[ 더 알아보기 ]

💡 의존성 주입을 하지 않으면 어떻게 될까? 예를 들어 Controller에서 Service의 Interface 메서드를 가져오려고 해

- Controller에서 Service 인터페이스를 호출할 때 의존성을 주입하지 않으면 예외나 오류가 발생할 수 있습니다. Controller는 Service 인터페이스가 제공하는 메서드와 기능에 접근할 수 없게 됩니다.


💡 클래스에서 클래스를 호출하는 경우도 있는데 그건 잘 되는데? 예를 들어 Service에서 공통 유틸 클래스를 호출해서 사용하는 것 같이 수행하는것은 잘 되는데?

- 클래스에서 클래스를 호출하여 가져올 수 있는 경우에는 의존성 주입을 사용하지 않을 수 있습니다. 이는 간단한 의존성 관리나 작은 규모의 애플리케이션에서 흔히 사용되는 방식입니다. 그러나 보다 복잡한 애플리케이션 개발이나 테스트 용이성 등을 위해 의존성 주입을 사용하는 것이 좋습니다.


💡 의존성 주입을 하는 이유는 뭘까?

1. 코드의 유지 보수성과 확장성을 향상합니다.

- 의존성 주입을 통해 컴포넌트 간의 결합도를 낮출 수 있고, 의존하는 객체를 쉽게 교체하거나 확장할 수 있습니다.

2. 테스트 용이성을 제공합니다.

- 의존하는 객체를 모의(mock) 객체로 대체하여 단위 테스트를 수행할 수 있으며, 테스트할 때 원하는 동작을 구현한 모의 객체를 주입할 수 있습니다.

3. 코드의 가독성과 이해도를 높여줍니다.

- 클래스가 직접 의존하는 객체를 생성하거나 가져오는 것보다, 외부에서 의존 객체를 주입받는 방식은 코드의 의도를 명확하게 표현할 수 있습니다.

 
 

💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성설명
required의존성이 autowired 되어야 하는지를 지정합니다. 기본값은 true입니다.
valueautowiring에 사용될 빈 이름 또는 빈 클래스입니다.
qualifier이름에 의한 autowiring을 위한 한정자 값입니다.
annotationTypeautowiring에 사용될 주석 유형입니다.
nameautowiring에 사용될 빈 이름입니다.
defaultValue요청된 유형의 빈이 존재하지 않을 경우 사용할 기본값입니다.

 
 

💡 해당 예시에서는 아래와 같은 아키텍처 구조를 가지고 있습니다.

- 아래의 예시에서는 Controller 내에서 @Autowired 어노테이션을 통해서 Service(Interface)의 의존성을 주입받아올 예정입니다.
https://adjh54.tistory.com/105

 

 💡 @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 파일에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.

[Java] 개발 환경에 따라 각각 환경 파일 구성 방법: application.properties

해당 글에서는 Spring Boot 기반 로컬, QA, 운영 환경에서 각각 다른 환경파일을 사용하는 방법에 대해서 공유합니다. 1) 개발환경 분류 이름 버전 언어 Java 11 프레임워크 Spring Boot 2.7.12 프레임워크 Sp

adjh54.tistory.com

 
 

💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성설명
${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 (Spring Framework 6.0.13 API)

docs.spring.io

 
 

 💡 @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 (Spring Framework 6.0.13 API)

Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value. This annotation is semantically equiv

docs.spring.io

 
 
 

💡 @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

 

 
 
 
오늘도 감사합니다. 😀
 
 
 

반응형