[Spring] 기본편 07. 의존관계 자동 주입1

33425 단어 SpringSpring

이 글은 스프링 [핵심원리 - 기본편]을 듣고 정리한 내용입니다

📌 다양한 의존관계 주입 방법

  • 의존관계 주입 방법
  1. 생성자 주입
  2. 수정자 주입(setter주입)
  3. 필드 주입
  4. 일반 메서드 주입

🌱 생성자 주입

  • 생성자를 통해 의존관계를 주입받는 방법
  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다
    -> 불편, 필수 의존관계에 사용
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

}
  • *중요: 생성자가 딱 1개있으면 @Autowired 생략해도 자동 주입된다.
    (당연히 스프링 빈 일때만 해당)
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    //    @Autowired - 생성자 딱 1개니까 생략해도 자동주입 됨.
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

}

🌱 수정자 주입(setter 주입)

  • setter라 불리는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
  • 선택, 변경의 가능성이 있는 의존관계에 사용
@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired //수정자 주입
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Autowired //수정자 주입
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

}
  • *참고 : @Autowired 의 기본동작은 주입할 대상이 없으면 오류남.
  • @Autowired(required = false)로 지정하면 주입할 대상없어도 동작한다.

자바빈 프로퍼티 규약이란?
-> 자바에서는 과거부터 필드의 값을 직접 변경하지 않고 (Data.aa=aa), setXxx, getXxx 메서드를 통해서 값을 읽거나 수정하는 규칙을 만들었다.

  • 자바빈 프로퍼티 규약 예시
class Data {
	private int age;
    public void setAge(int age){
    	this.age=age;
    }

	public int getAge(){
    	return age;
      }
   }

🌱 필드 주입

  • 필드에 바로 주입하는 방식이다.
  • 코드가 간결하지만, 외부에서 변경이 불가능하므로 테스트 하기에 힘들다 (치명적인 단점)
  • DI 프레임워크가 없으면 아무것도 할 수 없다.
  • 테스트코드나 @Configurtaion(설정 관련) 같은곳에서만 특별한 용도로 사용할 수 있다.
  • 사용하지 말자!!!
@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private MemberRepository memberRepository;
    @Autowired
    private DiscountPolicy discountPolicy;
}

* 참고

  • 순수한 자바코드에서는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능!
  • 예를 들어, 스프링 빈이 아닌(@Component가 붙지 않은 혹은 @bean 수동등록도 되어있지 않은) Member 같은 클래스에서 @Autowired를 적용해도 아무런 기능도 동작하지 않는다.

🌱 일반 메서드 주입

  • 일반 메서드를 통해서도 주입 가능하다
  • 한번에 여러 필드를 주입 받을 수 있다.
  • 잘 사용하지 않는다.
@Component
public class OrderServiceImpl implements OrderService {
    
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
    
    //일반 메서드를 통해 주입받는 예시
    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository=memberRepository;
        this.discountPolicy=discountPolicy;
    }
 }

📌 옵션 처리

  • 주입할 빈이 없어도 동작해야 할 때가 있다.
  • @Autowiredrequired 옵션의 기본값이 true이므로, 자동 주입 대상이 없으면 오류가 발생한다.
  • 자동 주입 대상을 옵션으로 처리하는 방법은 아래와같이 세가지가 있다.
package hello.core.autowired;
 
public class AutowiredTest {
    
    @Test
    void AutowiredOption() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
    }
    
    static class TestBean {

        //호출 자체가 안됨.
        //Member는 스프링 빈이 아니다.
        @Autowired(required=false)
        public void setNoBean1(Member noBean1){
            System.out.println("noBean1 = " + noBean1);
        }

        //null 호출
        @Autowired
        public void setNoBean2(@Nullable Member noBean2){
            System.out.println("noBean2 = " + noBean2);
        }

        //Optional.empty 호출
        @Autowired
       public void setNoBean3(Option al<Member> noBean3){
            System.out.println("noBean3 = " + noBean3);
        }
    }
}
  • @Autowired(required=false): 자동 주입할 대상이 없으면 메서드 자체가 호출이 안된다.
  • org.springframework.lang.@Nullable: 자동 주입할 대상이 없으면 Null이 입력된다.
  • Optional<>: 자동 주입할 대상이 없으면 Optional.empty가 입력된다.
  • 출력 결과
  • 참고
    @Nullable, Optional은 스프링 전반에 걸쳐서 지원된다.
    예를들어, 생성자 자동 주입에서 특정 필드에만 사용해도 된다.

📌 생성자 주입을 선택하라!

  • 다양한 의존관계 주입 방법중에 생성자 주입이 권장된다.

생성자 주입이 권장되는 이유

  • 불변
    • 의존 관계 주입이 되면, 대부분 변경할 일이 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다. (불변해야함)
    • 수정자 주입을 사용하면 setXX를 public으로 열어두어야 하는데 누군가 실수로 변경할수도 있다.
    • 생성자 주입은 객체 생성시에 1번만 호출되므로, 더이상 호출될일이 없어서 불변에 기여한다.
  • 누락
    • 생성자 주입을 사용하면 주입 데이터를 누락 했을 때 컴파일 오류가 발생한다.
    	@Test //컴파일 오류 발생
        void createOrder() {  //memberRepository, discountPolicy가 누락됨.
        OrderServiceImpl orderService = new OrderServiceImple();
        orderService.createOrder(1L, "itemA", 10000);
       } 
  • final 키워드
    • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있어서, 생성자에서 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;


    @Autowired// 생성자 딱 1개니까 생략해도 자동주입 됨.
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        //discountPolicy값이 설정되지 않음
        //this.discountPolicy = discountPolicy;
    }

}
  • 필수 필드인 discountPolicy값을 설정하는 부분이 누락 되어서 다음 오류가 발생한다.
  • java: variable discountPolicy might not have been initialized
  • 컴파일 오류는 세상에서 가장 빠르고, 좋은 오류다!!!
  • 컴파일 타임이란?
    • 개발자에 의해 개발 언어로 소스코드가 작성 된 뒤, 컴파일 과정을 통해 컴퓨터가 인식할 수 있는 기계어 코드로 변환되어 실행 가능한 프로그램이 되는 과정
  • 컴파일 에러란?
    • 소스코드가 컴파일 되는 과정 중에 발생하는 Syntax error, 파일 참조 오류 등과 같은 문제들로 인해 컴파일이 방해되어 발생하는 오류.
      • 컴파일 에러 발생 시, 문제가 되는 소스코드를 알려준다. (그래서 좋음)
  • 런타임이란?
    • 컴파일 과정을 마친 응용프로그램이 사용자에 의해서 실행되어지는 때를 의미한다.
  • 런타임 에러란?
    • 이미 컴파일이 완료되어 프로그램일 실행중임에도 불구하고, 의도치 않은 예외 상황으로 인해 프로그램 실행 중에 발생하는 오류.

정리

  • 생성자 주입 방식은 프레임워크에 의존하지 않고, 순수 자버 언어의 특징을 잘 살리는 방법이기도 하다.
  • 항상 생성자 주입을 선택하자! 가끔 옵션을 필요하면 수정자 주입을 선택하자. (필드 주입은 사용하지 않는게 좋다.)

📌 롬복과 최신 트랜드

  • 개발을 하다보면 대부분이 불변으로 해야되어서 final 키워드를 사용한다.

  • 그러나 final방식은 생성자도 만들어야하고, 주입 해주는 코드도 만들어야 하고 귀찮다.

  • final 방식의 기본 코드

@Component
  public class OrderServiceImpl implements OrderService {
      private final MemberRepository memberRepository;
      private final DiscountPolicy discountPolicy;
@Autowired
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
  discountPolicy) {
          this.memberRepository = memberRepository;
          this.discountPolicy = discountPolicy;
      }
}
  • 생성자 1개일때 @Autowired 생략 가능
@Component
  public class OrderServiceImpl implements OrderService {
      private final MemberRepository memberRepository;
      private final DiscountPolicy discountPolicy;
      
      //@Autowired 생략
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
  discountPolicy) {
          this.memberRepository = memberRepository;
          this.discountPolicy = discountPolicy;
      }
}
  • 롬복 적용

  • @RequiredArgConstructor 어노테이션을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.

  • 생성자가 만들어졌는지는 command+f12를 통해 보면 확인 가능하다.

  • 롬복 사용한 코드

  • 롬복이 자바의 어노테이션 프로세서 라는 기능을 이용해서 컴파일 시점에 생성자 코드를 자동으로 생성해주는 것이다.

@Component
  @RequiredArgsConstructor
  public class OrderServiceImpl implements OrderService {
      private final MemberRepository memberRepository;
      private final DiscountPolicy discountPolicy;
}

최신 트랜드

  • 생성자를 딱 1개두고 @Autowired를 생략하거나
  • 여기에, Lombok 라이브러리의 @RequiredArgsConstructor를 사용한다.

롬복 라이브러리 적용 방법

  • build.gradle에 라이브러리 및 환경 설정 코드 추가
plugins {
    id 'org.springframework.boot' version '2.6.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

//lombok 설정 추가 시작
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
//lombok 설정 추가

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    //lombok 라이브러리 추가 시작
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    //lombok 라이브러리 추가 끝
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

tasks.named('test') {
    useJUnitPlatform()
}
  • 코드를 작성하고나서 아래 과정 해줘야한다.
  1. Preferences -> plugin -> lombok 검색해서 설치 + 재시작
  2. Preferences -> Annotaion Processor 검색 -> Enable annotation processing 체크 + 재시작
  3. 임의의 클래스 만들고 ,@Getter, @Setter 확인
package hello.core;


import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.autoconfigure.cache.CacheProperties;

@Getter
@Setter
@ToString
public class HelloLombok {

    private String name;
    private int age;

    public static void main(String[] args) {
        HelloLombok helloLombok = new HelloLombok();
        helloLombok.setName("aaaaaa");
		
        //@ToString 어노테이션을 통해 스트링으로 바로 출력할 수 있다.
        System.out.println("helloLombok = " + helloLombok);

    }
}

참고 사이트
https://velog.io/@yejin20/%EB%9F%B0%ED%83%80%EC%9E%84%EA%B3%BC-%EC%BB%B4%ED%8C%8C%EC%9D%BC%ED%83%80%EC%9E%84-%EC%B0%A8%EC%9D%B4%EC%A0%90

좋은 웹페이지 즐겨찾기