@Bean & @Configuration
@Bean
과 @Configuration
은 자바 기반 설정의 가장 중요한 애너테이션이다.
메서드가 Spring 컨테이너에서 관리할 새로운 객체를 인스턴스화 시키고, 구성 및 초기화한다는 것을 나타내는 데 사용한다.
@Configuration
public class DependencyConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
AnnotationConfigApplicationContext 로 스프링 컨테이너를 인스턴스화 하는 방법
AnnotationConfigApplicationContext
구현은 아래와 같은 애너테이션이 달린 클래스로 피라미터를 전달받고 있다.
@Configuration
클래스가 입력으로 제공되면, @Configuration
클래스 자체가 Bean 정의로 등록되고 클래스 내에서 선언된 모든 @Bean
메서드도 Bean 정의로 등록된다.
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(DependencyConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
@Component
클래스와 JSR-330 클래스가 제공되면, Bean 정의로 등록되며 필요한 경우 해당 클래스 내에서 @Autowired
또는 @Inject
와 같은 DI 메타데이터가 사용되는 것으로 가정한다.
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
@Component
또는 JSR-330 주석이 달린 클래스는 위와 같이 생성자에 입력으로 사용한다.
@Bean
은 메서드 레벨 애너테이션이다. <bean />
에서 제공하는 일부 속성을 지원한다.
@Bean
애너테이션은 @Configuration-annoted
또는 @Component-annoted
클래스에서 사용할 수 있다.
Bean 선언하기
@Bean
애너테이션을 메서드에 추가해서 Bean으로 정의(선언)할 수 있다.
@Configuration
public class DependencyConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
Bean 정의가 있는 인터페이스를 구현해 bean configuration을 설정할 수 있다.
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class DependencyConfig implements BaseConfig {
}
Bean 의존성
@Bean
애너테이션이 추가된(@Bean-annotated
) 메서드는 Bean을 구축하는데 필요한 의존성을 나타내는데 매개 변수를 사용할 수 있다.
@Configuration
public class DependencyConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
는 해당 객체가 bean definitions의 소스임을 나타내는 애너테이션이다. @Bean-annoted
메서드를 통해 bean을 선언한다.
@Configuration
클래스의 @Bean
메서드에 대한 호출을 사용하여 bean 사이의 의존성을 정의할 수도 있다.
Bean 사이에 의존성 주입하기
Bean이 서로 의존성을 가질 때, 의존성을 표현하는 것은 다른 bean 메서드를 호출하는 것과 같이 간단하다.
@Configuration
public class DependencyConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
beanOne
은 생성자 주입을 통해 beenTwo
에 대한 참조를 받는다.
스프링은 설정 정보 없이 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이란 기능을 제공한다.
@ComponentScan
은 @Component
가 붙은 모든 클래스를 스프링 빈으로 등록해주기 때문에 설정 정보에 붙여주면 된다. 의존관계도 자동으로 주입하는 @Autowired
기능도 제공한다.
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan
public class AutoDependencyConfig {
}
컴포넌트 스캔 기본 대상
@Component : 컴포넌트 스캔에서 사용된다.
@Controller & @RestController : 스프링 MVC 및 REST 전용 컨트롤러에서 사용된다.
@Service : 스프링 비즈니스 로직에서 사용된다.
특별한 처리를 하지 않으며, 개발자들이 핵심 비즈니스 로직이 여기에 있다는비즈니스 계층을 인식하는데 도움이 된다.
@Repository : 스프링 데이터 접근 계층에서 사용된다.
스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.
@Configuration : 스프링 설정 정보에서 사용된다.
스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
Filter
includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
FilterType 옵션
ANNOTATION: 기본값, 애너테이션으로 인식해서 동작한다.
ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.
ASPECTJ: AspectJ 패턴을 사용한다.
REGEX: 정규 표현식을 나타낸다.
CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리한다.
생성자를 통해 의존 관계를 주입받는 방법이다.
생성자에 @Autowired
를 하면 스프링 컨테이너에 @Component
로 등록된 bean에서 생성자에 필요한 bean들을 주입한다.
특징
생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.
불변과 필수 의존 관계에 사용된다.
생성자가 1개만 존재하는 경우에는 @Autowired
를 생략해도 자동 주입된다.
NullPointerException
을 방지할 수 있다.
주입받을 필드를 final
로 선언 가능하다.
@Component
public class CoffeeService {
private final MemberRepository memberRepository;
private final CoffeeRepository coffeeRepository;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, CoffeeRepository coffeeRepository) {
this.memberRepository = memberRepository;
this.coffeeRepository = coffeeRepository;
}
}
setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존 관계를 주입하는 방법이다.
특징
선택과 변경 가능성이 있는 의존 관계에 사용된다.
자바 bean 프로퍼티 규약의 수정자 메서드 방식을 사용한다.
@Component
public class CoffeeService {
private final MemberRepository memberRepository;
private final CoffeeRepository coffeeRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setCoffeeRepository(CoffeeRepository coffeeRepository) {
this.coffeeRepository = coffeeRepository;
}
}
생성자 주입과의 차이점은 생성자 대신 set 필드명 메서드를 생성해 의존 관계를 주입한다.
수정자 경우 @Autowired
를 입력하지 않으면 실행되지 않는다.
@Component
가 실행하는 클래스를 스프링 빈으로 등록한다.
스프링 빈으로 등록하고 의존 관계를 주입하는데, @Autowired
에 있는 것들을 자동으로 주입한다.
생성자는 1개일 때, @Autowired가 없어도 작동되는 이유는 무엇일까?
스프링이 해당 클래스 객체를 생성해 bean에 넣어야하는데, 생성할 때 생성자를 부를 수 밖에 없다. 그러므로 bean을 등록하면서 의존 관계 주입도 같이 발생하게 된다.
필드에 @Autowired
를 붙여서 바로 주입하는 방법이다.
특징
코드가 간결해서 예전에 많이 사용된 방식이지만, 외부에서 변경이 불가능하여 테스트하기 힘들다는 단점이 있다.
DI 프레임워크가 없으면 아무것도 할 수 없다.
실제 코드와 상관 없는 특정 테스트를 하고 싶을 때 사용할 수 있다.
정상적으로 작동되게 하려면 결국 setter가 필요하므로 수정자 주입을 사용하는게 더 편리하다.
@Component
public class CoffeeService {
@Autowired
private final MemberRepository memberRepository;
@Autowired
private final CoffeeRepository coffeeRepository;
}
일반 메서드를 사용해 주입하는 방법이다.
특징
주입할 스프링 빈이 없을 때 동작해야하는 경우가 있다.
`@Autowire만 사용하는 경우 required 옵션 기본값인 true가 사용되어 자동 주입 대상이 없으면 오류가 발생하는 경우가 있다.
자동 주입 대상 옵션 처리 방법
불변
의존 관계 주입은 처음 애플리케이션이 실행될 때 대부분 정해지고 종료 전까지 변경되지 않고 변경되면 안 된다.
수정자 주입 같은 경우에는 이름 메서드를 public으로 열어두어 변경이 가능하기 때문에 적합하지 않다.
생성자 주입은 객체를 생성할 때 최초로 1번만 호출되고 그 이후에는 다시는 호출되는 일이 없기 때문에 불변하게 설계할 수 있다.
누락
호출했을 때는 NPE(Null Point Exception)이 발생하는데 의존관계 주입이 누락되었기 때문에 발생한다.
생성자 주입을 사용하면 주입 데이터 누락 시 컴파일 오류가 발생한다.
final 키워드 사용 가능
생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
생성자에서 값이 설정되지 않으면 컴파일 시점에서 오류를 확인할 수 있다.
java: variable (데이터 이름) might not have been initialized
생성자 주입을 제외한 나머지 주입 방식은 생성자 이후에 호출되는 형태이므로 final 키워드를 사용할 수 없다.
순환 참조
순환 참조를 방지할 수 있다.
개발하다보면 여러 컴포넌트 간에 의존성이 생기게 된다. (A → B를 참조하고, B → A를 참조)
필드 주입과 수정자 주입은 bean이 생성된 후에 참조를 하기 때문에 애플리케이션이 어떠한 오류와 경고 없이 구동됩니다.
실제 코드가 호출될 때까지 문제를 알 수 없다.
생성자를 통해 주입하게되면 BeanCurrentlyInCreationException이 발생하게 된다.