2020-3-26 다시 시작하는 스프링(2) - 빈주입

수동으로 빈등록하고 주입하기

XML방식

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookService"
          class="me.jimmy.springmvc.book.BookService">
        <property name="bootRepository" ref="bootRepository"></property>
    </bean>
    <bean id="bootRepository" class="me.jimmy.springmvc.book.BookRepository"/>
</beans>

// 다른방법
<context:component-scan base-package="me.jimmy.springmvc"></context:comp> // anto
  • component-scan을 이용해서 base-package이하의 컴포넌트들을 스캔해서 빈으로 등록한다.
  • 어노테이션기반으로 빈을 등록하고 설정하는 부분은 스프링2.5부터 가능.

Java annotation방식

@Configuration
public class ApplicationConfig {

    @Bean
    public BookRepository bookRepository() {
        return new BookRepository();
    }

    @Bean
    public BookService bookService() {
        BookService bookService = new BookService();
        bookService.setBookRepository(bookRepository());
        return bookService;
    }
}

public class SpringmvcApplication {
	public static void main(String[] args) {
		ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		String [] beans = context.getBeanDefinitionNames();
		System.out.println(Arrays.toString(beans));
	}
}

//Another
@Configuration
@ComponentScan(basePackages = "me.") // 방법1
@ComponentScan(basePackageClasses = SpringmvcApplication.class) // 방법2
public class ApplicationConfig {
}
  • BookService에서 setter를 썼기때문에 Autowired가 가능했다.
  • 자바 어노테이션 방식 역시 xml component-scan처럼 @ComponentScan을 활용할 수 있다. @ComponentScan의 속성에는 basePackage와 basePacakgeClasses가 있지만 basePacakgeClasses가 더 type-safe하다.
  • basepackageClasses를 활용하면 클래스에 해당하는 주변 패키지들을 다 스캔한다.
  • 위의 방법이 현재 스프링부트에서 사용하는 방법에 가장 근접하다.

현재의방식

@SpringBootApplication
public class SpringmvcApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringmvcApplication.class, args);
	}
}
  • xml, java 어노테이션 방식으로 ApplicationContext를 만들고 주입을 하다가 현재는 @SpringBootApplication만 붙이면 알아서 다 된다.
  • @SpringBootApplication 내부를 보면 @ComponentsScan, @SpringBootConfiguration 등이 이미 붙어있다.

@Autowired

  • @Autowired는 생성자를 통해 주입받는 방법과 필드 주입, 세터에서 주입받는 방식이 있다.
@Service
public class BookService {
    @Autowired // 필드주입
    private BookRepository bookRepository;

    @Autowired // 생성자 주입
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Autowired(required = false) // setter주입
    public BookService setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
        return this;
    }
}
  • required는 기본적으로 true이다. 안필요하면 false로 둔다.
  • 생성자를 통해 주입할때는 주입되는 빈이 개입이 되서 반드시 있어야한다. setter나 필드 주입을 사용할때는 required를 통해 해당 빈이 없이도 빈이 생성될 수 있게해줄 수 있다.

경우의수

  • 해당 타입의 빈이 없는 경우

  • 해당 타입의 빈이 한 개인 경우

  • 해당 타입의 빈이 여러개인 경우

    • 빈 이름으로 시도
      • 같은 이름의 빈 찾으면 해당 빈 사용
      • 같은 이름으로 못찾으면 실패
@Repository
public class JimmyBookRepository implements BookRepository {
}

@Repository
@Primary // sol1
public class MyBookRepository implements BookRepository {
}

@Service
public class BookService {
    @Autowired
  	@Qualifier("jimmyBookRepository") // sol2
    private BookRepository bookRepository;
}

// Field bookRepository in me.jimmy.springmvc.book.BookService required a single bean, but 2 were found => error message

// Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed => solution

@Component
public class BookRunner implements ApplicationRunner {
    @Autowired
    private BookService bookService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        bookService.printBook();
    }
}
// solution1 : class me.jimmy.springmvc.book.MyBookRepository$$EnhancerBySpringCGLIB$$593a097f

// solution2 : class me.jimmy.springmvc.book.JimmyBookRepository$$EnhancerBySpringCGLIB$$bc20518d
  • BookService에서는 BookRepository를 주입못해준다. 이유는 BookRepository에 해당하는 bean이 1개 이상이기 때문이다.
  • 방법1 : @Primary를 붙인다. 붙이고나서 ApplicationRunner를 구현해서 돌려보면 @Primay를 붙인 MyBookRepository 클래스가 주입된것을 확인할 수 있다.
  • 방법2 : @Qualifier(“className”) 소문자로 시작되는 className을 붙여서 한다. 이때는 @Primary보다 우선순위가 높다.
  • 방법3 : service에서 List types를 받는다..
  • @Autowired는 타입과 이름을 같이 본다.
  • @Primary를 가장 추천.

동작원리

  • BeanPostProcessor참조 : 새로 만든 빈 인스턴스를 수정할 수 있는 라이프 사이클 인터페이스
Written on March 26, 2020