자바 기반 설정
자바 코드로 직접 컨테이너를 설정하는 데 사용되는 어노테이션 2가지가 있다.
@Configuration
@Bean
위 어노테이션은 메서드가 스프링 컨테이너에서 관리할 새 객체를 인스턴스화, 구성 및 초기화한다는 것을 나타내는데 사용한다.
@Configuration
public class AppConfig {
@Bean
public AppService appService() {
return new AppServiceImpl();
}
}
XML 설정 방식
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
스프링 컨테이너를 인스턴스화 하는 방법
스프링 컨테이너를 인스턴스화 하는 방법에는 어노테이션을 통해 Config 클래스를 설정하는 방법이 있다.
스프링 3.0 버전부터는 AnnotationConfigApplicationContext를 도입하여 ApplicationContext(스프링 컨테이너)를 구현하는데 어노테이션을 사용하여 클래스로 파라미터를 전달받는다.
- @Configuration 클래스
- @Component 클래스
- JSR-330 메타데이터
@Configuration 어노테이션
@Configuration 클래스가 입력으로 제공되면 해당 클래스 자체가 Bean 정의로 등록된다.
따라서 클래스 내에 선언된 모든 @Bean 메서드도 빈 정의로 등록된다.
@Configuration 클래스를 입력으로 받는 경우 다음과 같이 인스턴스화하여 사용할 수 있다.
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AppService appService = context.getBean(AppService.class);
appService.run();
}
@Component 어노테이션
@Component 클래스가 입력으로 제공되면 빈 정의로 등록된다.
필요한 경우 @Autowired 또는 @Inject와 같은 DI 메타데이터가 사용되는 것으로 가정한다.
@Component 클래스를 입력으로 받는 경우 다음과 같이 인스턴스화하여 사용할 수 있다.
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(
AppServiceImpl.class, Dependency1.class, Dependency2.class);
AppService appService = context.getBean(MyService.class);
appService.run();
}
위 코드는 예를 들기 위해 AppServiceImpl, Dependency1, Dependency2 클래스에서 스프링 의존성 주입(DI) 어노테이션을 사용한 경우의 예제이다.
@Bean 어노테이션
@Bean은 메서드-레벨 어노테이션으로, <bean />에서 제공하는 일부 속성을 지원한다.
- init-method
- destory-method
- autowiring
@Bean 어노테이션은 @Configuration-annoted 또는 @Component-annoted 클래스에서 사용할 수 있으며, 주로 개발자가 직접 제어 불가능한 외부 라이브러리 등을 Bean으로 만들려고 할 때 사용된다.
@Configuration
public class AppConfig {
@Bean
public AppServiceImpl appService() {
return new AppServiceImpl();
}
}
빈 정의가 있는 인터페이스를 구현하여 bean configuration을 설정할 수도 있다.
public interface BaseConfig {
@Bean
default AppService appService() {
return new AppServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
}
XML 방식의 configuration
<beans>
<bean id="appService" class="com.example.AppServiceImpl"/>
</beans>
빈 의존성
@Bean 어노테이션이 추가된 메서드(@Bean-annotated)는 빈을 구축하는 데 필요한 의존성을 나타내기 위해 매개 변수를 사용할 수 있다.
@Configuration
public class AppConfig {
@Bean
public AppServiceImpl appService(AppRepository appRepository) {
return new AppServiceImpl(appRepository);
}
}
@Configuration 어노테이션 사용
@Configuration 어노테이션은 해당 객체가 bean definitions의 소스임을 나타내는 어노테이션이다.
@Bean-annoted 메서드를 통해 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의 생성자 주입을 통해 beanTwo에 대한 참조를 받는 경우이다.
Java 기반의 설정 환경의 작동 방식에 대한 정보
@Configuration
public class DependencyConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
위 예제는 clientService1(), clientService2() 메서드에서 clientDao() 메서드를 1번씩 호출하는 코드이다.
clientDao() 메서드는 ClientDaoImpl의 새 인스턴스를 만든다.
즉, 두 개의 인스턴스가 각 clientService1, clientService2에 있어야 한다.
하지만, 스프링에 인스턴스화된 빈은 기본적으로 싱글톤 범위를 갖게 되므로
하위 클래스의 하위 메서드는 상위 메서드를 호출하고, 새 인스턴스를 만들기 전에 먼저 컨테이너에 캐시된 bean이 있는지 확인하게 된다.
동작 방식
- 모든 @Configuration 클래스는 시작 시 CGLIB를 사용하여 하위 클래스를 분류한다.
- 스프링에서 CGLIB라는 바이트코드 조작 라이브러리를 사용하는 것을 뜻한다.
- AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록하는 것이다. (DependencyConfig → DependencyConfig@CGLIB)
- 따라서 같은 메서드를 호출할 때는 이미 스프링 컨테이너에 등록되어 있다면, 기존에 만들어진 것을 반환해 준다.
- 스프링 컨테이너에 등록되어 있지 않을 때에만 필요한 Bean을 생성하고 스프링 컨테이너에 등록한다.
Java 코드에서 어노테이션을 사용해 Spring 컨테이너를 구성하는 방법
어노테이션을 사용하면 구성의 복잡성을 줄일 수 있다.
@Import 어노테이션
XML 파일 내에서 요소가 사용되는 것처럼 구성을 모듈화하는데 사용한다.
다른 구성 클래스에서 @Bean definitions를 가져올 수 있다.
@Configuration
public class DependencyConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(DependencyConfigA.class) // @Import 어노테이션 사용
public class DependencyConfigB {
@Bean
public B b() {
return new B();
}
}
위 경우 컨텍스트를 인스턴스화할 때, DependencyConfigB.class만 제공하면 ㅗ딘다.
public static void main(STring[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(DependencyConfigB.class);
// A와 B 모두 사용할 수 있다.
A a = context.getBean(A.class);
B b = context.getBean(B.class);
}
@import 어노테이션을 활용하면 컨테이너 인스턴스화를 단순화할 수 있다.
많은 @Configuration 클래스를 기억할 필요 없이 하나의 클래스만 처리하면 된다.
문제점
실제로 사용될 때 빈은 1개의 구성 파일만 @Import 받는 것이 아닌 여러 구성 클래스 간에 걸쳐 서로 의존성을 갖게 된다.
XML을 사용할 때에는 컴파일러가 관여하지 않기 때문에 컨테이너 초기화 중에 ref=”some Bean”을 선언하여 스프링으로 해결할 수 있어 문제가 되지 않는다.
하지만, @Configuration 클래스를 사용하면 자바 컴파일러는 구성 모델에 제약을 두게 되며 다른 빈에 대한 참조는 유효한 자바 구문이어야 한다.
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
해결방법
@Bean 메서드는 빈 의존성을 설명하는 임의 개수 파라미터를 가질 수 있다.
@Autowired 또는 @Value 주입 등 bean과 동일한 기능을 사용할 수 있다.
단, @Configuration의 생성자 주입은 스프링 프레임워크 4.3에서만 지원한다.
대상 빈이 하나의 생성자만 정의하는 경우 @Autowired를 지정할 필요가 없다.
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
'프레임워크(Framework) > Spring' 카테고리의 다른 글
[Spring] 스프링 싱글톤 컨테이너 (웹 애플리케이션과 싱글톤) (0) | 2022.11.04 |
---|---|
[Spring] 스프링 의존성 주입(DI : Dependency Injection) 4가지 방법 (의존 관계 자동 주입) (1) | 2022.11.03 |
[Spring] 스프링 빈 생명주기 콜백 (0) | 2022.11.02 |
[Spring] 스프링 빈 스코프(Bean Scope)란 무엇인가? (0) | 2022.11.01 |
[Spring] 스프링 빈(Bean)이란 무엇인가? (2) | 2022.10.29 |