Develop/Spring+JPA

[Spring 5 프로그래밍 입문] chapter 3, 4 - 의존 주입

연로그 2022. 5. 21. 15:01
반응형

목차

  • chapter 3: 스프링 DI
    • 의존 주입; DI
    • 객체 조립기
    • Spring과 DI
  • chapter 4: 의존 자동 주입
    • @Autowired 어노테이션
    • @Qualifier 어노테이션
    • @Autowired 어노테이션 필수 여부 정하기
    • 자동 주입 vs 수동 주입

 


Chapter 3 - 스프링 DI

 

💥 의존 주입; DI; Dependency Injection

  • 여기서 의미하는 의존은 객체 간의 의존을 의미
  • 의존: 변경에 의해 영향받는 관계
  • 한 클래스가 다른 클래스의 메서드를 실행할 때 이를 의존한다고 표현

 

example1 - 의존 객체 직접 생성하기

  • 의존 객체 직접 생성
  • 의존 객체인 UserDao를 변경하고 싶은 경우 UserService에서 직접 수정
public class UserService {
    private UserDao userDao = new UserDao();
}

 

example2 - 의존 객체 주입받기

  • 생성자를 이용하면 의존 객체 변경을 유연하게 만들 수 있음
public class UserService {
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}

 

🧩 객체 조립기

  • 객체 생성하고 의존 대상이 되는 객체를 주입하는 역할
  • Service 또는 Service 를 호출하는 곳에서 객체를 주입시키지 않아도 됨

 

example - 객체 조립기를 이용한 의존 주입

public class Assembler {

    private UserDao userDao;
    private UserService userService;

    public Assembler() {
        userDao = new UserDao(); // userService의 의존 객체를 수정하고 싶다면 요 부분만 수정하면 된다.
        userService = new UserService(userDao);
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public UserService getUserService() {
        return userService;
    }
}

 

🍃 Spring & DI

  • 스프링은 위의 조립기와 유사한 기능을 이미 제공함
  • 객체 생성 및 생성 객체에 의존 주입

 

example - @Configuration과 @Bean 활용

// 설정 클래스
@Configuration
public class ApplicationContext {

    @Bean
    public UserDao userDao() {
        return new UserDao();
    }

    @Bean
    public UserService userService() {
        return new UserService(userDao());
    }
}

 

📑 컨테이너 생성

// 1개
ApplicationContext context = new AnnotationConfigApplicaionContext(ApplicaionContext.class);

// 여러개
ApplicationContext context = new AnnotationConfigApplicaionContext(ApplicaionContext1.class, ApplicationContext2.class);

 

 

컨테이너를 직접 선언하지 않은 사람들은 여태 이런거 안 만들었는데 왜 되지? 의아할 수 있다. 그 이유는 SpringApplication.run()에서 자동으로 생성해주었기 때문이다.  (참고: Spring 공식 문서)

 

📑 getBean()

  • 컨테이너를 생성 후 해당 메서드를 호출해 bean 객체를 가져올 수 있음
  • BeanFactory 인터페이스에서 정의
  • AbstractApplicationContext 클래스에서 구현

  • context.getBean("bean 이름", bean타입.class)
    • 이름 불일치하는 경우: NoSuchBeanDefinitionException
    • 타입 불일치하는 경우: BeanNotOfRequiredTypeException

  • context.getBean(bean타입.class)
    • 존재하지 않는 빈인 경우: NoSuchBeanDefinitionException
    • 타입 같은 빈이 여러개인 경우: NoUniqueBeanDefinitionException

 

📑 @Import 어노테이션

  • 함께 사용할 설정 클래스 지정

 

example - @Import 어노테이션

@Configuration
@Import(ApplicationContext2.class)
// @Import({ApplicationContext2.class, ApplicationContext2.class})
public class ApplicationContext {

    @Bean
    public UserDao userDao() {
        return new UserDao();
    }

    @Bean
    public UserService userService() {
        return new UserService(userDao());
    }
}

 

 


Chapter 4 - 의존 자동 주입

 

🚀 @Autowired 어노테이션

  • @Autowired를 이용한 다양한 주입 방식은 블로그에 기술
  • 발생 가능한 Exception
    • 일치하는 빈이 없는 경우: UnsatisfiedDependencyException
    • 타입이 같은 빈이 여러개인 경우: UnsatisfiedDependencyException

 

💐 @Qualifier 어노테이션

  • 자동 주입 가능한 빈이 여러개인 경우 자동 주입할 빈을 지정할 수 있음
  • 어노테이션 생략 시 빈의 이름을 메서드명으로 지정

 

example

public class UserService {

    private UserDao userDao;

    @Autowired
    @Qualifier("dao1")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
@Configuration
public class ApplicationContext {

    @Bean
    @Qualifier("dao1") // <- 해당 bean이 사용된다!
    public UserDao userDao1() {
        return new UserDaoImpl1();
    }

    @Bean
//    @Qualifier("dao2")
    public UserDao dao2() { // @Qualifier 어노테이션 생략 시 빈의 이름을 메서드명으로 지정
        return new UserDaoImpl2();
    }
}

 

📚 @Autowired 어노테이션 필수 여부 정하기

  • 일반적으로 매칭되지 않는 bean 이 존재하면 Exception 발생
  • 자동 주입할 대상이 필수가 아닌 경우는 어떻게 할까?

 

📕 1. @Autowired(required = false)

  • Spring 5 버전부터 지원
  • 빈이 존재하지 않으면 자동 주입 대상에 할당 안함
public class UserService {

    @Autowired(required = false)
    private UserDao userDao;
}

 

📒 2. Optional

  • Java 8부터 지원
  • 빈이 존재하지 않으면 자동 주입 대상에 빈 Optional 할당
public class UserService {

    @Autowired
    private Optional<UserDao> userDao;
}

 

📗 3. @Nullable

  • 빈이 존재하지 않으면 자동 주입 대상에 null 할당
public class UserService {

    @Autowired
    @Nullable
    private UserDao userDao;
}

 

required=false vs @Nullable
언뜻 보면 @Autowired(required=false)와 @Nullable이 동일해보일 수 있다. 하지만 전자는 bean이 존재하지 않으면 그냥 내버려두고 후자는 bean이 존재하지 않으면 무조건 null이 할당된다는 점이 다르다. 기존에 이미 값을 할당한 상태로 @Nullable을 함께 이용하면 기존 값이 없어지고 null 값이 할당 된다.

 

📰 자동 주입 vs 수동 주입

 자동 주입과 수동 주입 코드가 섞이면 주입이 되지 않아 NullPointerException 발생하는 경우 원인 찾는게 오래 걸린다. 일관되게 사용하는 것이 좋다. 관리할 빈이 너무 많으면 설정 정보가 커지고 이를 관리하는 것이 매우 부담스러워진다. 가능하면 스프링이 잘 지원해주는 자동 주입을 사용하자. 다만 수동 주입이 더 좋은 경우도 분명 존재는 한다.

 

어플리케이션 기능은 크게 핵심 기능과 부가 기능으로 나뉜다.

  • 핵심 기능: 해당 객체가 제공하는 고유한 기능 (ex: CustomerService - 구매 로직)
  • 부가 기능: 핵심 기능을 보조하는 기능 (ex: 로그 추적, 트랜잭션 처리 등)

 

부가 기능은 핵심과 비교해 수가 적고 어플리케이션에 광범위하게 영향을 미친다. 이런 부분에 대해서는 수동 주입을 통해 명확하게 드러내는 것이 좋을 때도 있다.

 


이 글은 최범균 님의 '스프링5 프로그래밍 입문'을 보고 정리하는 글 입니다.

반응형