시작하기에 앞서,
앞서 설명한 것처럼 스프링은 스프링 컨테이너가 자바 객체를 빈(Bean)으로 만들어 관리해준다.
이전과 같은 예제를 통해 스프링 컨테이너와, 빈에 대해 간단하게 리뷰 해보자.
public interface EmployeeRepository {
void save(Employee employee);
Employee findById(Long employeeId);
List<Employee> findAll();
}
public class EmployeeRepositoryImplement implements EmployeeRepository {
private static Long sequence = 0L;
private Map<Long, Employee> store = new HashMap<>();
@Override
public void save(Employee employee) {
employee.setId(++sequence);
store.put(employee.getId(), employee);
}
@Override
public Employee findById(Long employeeId) {
return store.get(employeeId);
}
@Override
public List<Employee> findAll() {
return new ArrayList<>(store.values());
}
}
public interface EmployeeService {
void join(Employee employee);
Employee findEmployee(Long employeeId);
List<Employee> findAllEmployee();
}
public class EmployeeServiceImplement implements EmployeeService {
EmployeeRepository employeeRepository;
@Override
public void join(Employee employee) {
employeeRepository.save(employee);
}
@Override
public Employee findEmployee(Long employeeId) {
return employeeRepository.findById(employeeId);
}
@Override
public List<Employee> findAllEmployee() {
return employeeRepository.findAll();
}
}
이렇게
Employee
를 저장해주는
Repository
인터페이스와 이것의 구현체가 있으며,
Repository
를 적극적으로 활용해 비지니스 로직을 구현하는
Service
인터페이스와 이것의 구현체가 존재한다.
지금은 각각의 인터페이스 구현체가 하나이지만 추후에 새로운 구현체가 생겼을 때, 어떤 구현체를 이용할지 주입해줘야한다.
지금 같은 구조는 문제가 생길 여지가 크기 때문에 이전에 언급했듯이 스프링 프레임워크는 스프링 컨테이너에서 이 자바 객체를 빈이라는 것으로 관리해주고 이를 통해 초래할 다양한 문제들을 해결했다.
하지만 우리가 이렇게 스프링 컨테이너가 자바 객체를 관리할 수 있게 빈을 등록해야하는데 이를 빈 등록이라고 한다.
스프링 컨테이너와 빈이라는 것은 알겠고. 우리에게 이점을 가져다주는 것은 알겠다.
하지만, 빈은 어떻게 등록하는 것일까?
이 방법에는 수동 빈 등록, 자동 빈 등록 두 가지의 방법이 있다. 이에 대해 알아보자.
수동 빈 등록
수동 빈 등록은
@Configuration
과
@Bean
을 활용하여 빈으로 등록할 수 있도록 한다.
@Configuration
어노테이션은 자바 설정정보를 설정할 때 사용되는 어노테이션으로
@Bean
과 함께 사용되어 빈 등록을 해준다.
위의 예제에서의 빈 등록을 예시로 들겠다.
@Configuration
public class Config {
@Bean
public EmployeeRepository employeeRepository() {
return new EmployeeRepositoryImplement();
}
@Bean
public EmployeeService employeeService() {
return new EmployeeServiceImplement(employeeRepository());
}
}
다음과 같이 설정정보를 적용해주는
config.class
에
@Configuration
어노테이션을 적용해주고 빈으로 등록해줘야 하는 객체들에 대해 @Bean 어노테이션을 적용하고 필요한 객체들을 주입시킨다.
public class EmployeeServiceImplement implements EmployeeService {
public final EmployeeRepository employeeRepository;
public EmployeeServiceImplement(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Override
public void join(Employee employee) {
employeeRepository.save(employee);
}
@Override
public Employee findEmployee(Long employeeId) {
return employeeRepository.findById(employeeId);
}
@Override
public List<Employee> findAllEmployee() {
return employeeRepository.findAll();
}
}
이제 실제로 어떻게 적용되는지 알아보자.
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
@Test
public void Config() throws Exception {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
System.out.println("===========================");
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if (beanDefinition.getRole() == beanDefinition.ROLE_APPLICATION) {
System.out.println("beanDefinition = " + beanDefinitionName);
}
}
System.out.println("===========================");
}
먼저,
AnnotationConfigApplicationContext
에 대해서 알아보자.
우리는 스프링 컨테이너에 등록하기 위해서는
Config.class
파일도 빈에 등록되어야 한다.
그렇다면 스프링 컨테이너를 만들고
Config.class
를 등록시켜야 할 것이다.
스프링 컨테이너는
BeanFactory
를 최상위 인터페이스로 하여
ApplicationContext
,
AnnotationConfigApplicationContext
를 자식으로 가져 더욱 많은 메소드들을 구현하게 된다.
우리는 그 중
AnnotationConfigApplicationContext
를 활용하여
Config.class
를 등록한 뒤, 해당 클래스 안에 있는 객체들을 빈으로 등록하였다.
결과를 확인하면 알 수 있듯이 빈들이 잘 등록되어 사용되어지는 것을 볼 수 있다.
자동 빈 등록
자동 빈 등록은
@Componentscan
을 활용하여 빈으로 등록할 수 있도록 한다.
여기서 말하는
@Componentscan
은
@Component
어노테이션 및
streotype (@Service, @Repository, @Controller)
어노테이션이 부여된 Class들을 자동으로 스캔하여
Bean
으로 등록해는 어노테이션이다.
이를 활용하기 위해 다음과 같이
@Componentscan
을 구성하였다.
@Configuration
@ComponentScan
public class AutoConfig {
}
다음과 같이
@Componentscan
>을 적용해주었는데 여기서 탐색의 위치는 default로 해당 파일(
AutoConfig.class
)가 있는 패키지와 그 하위를 기본으로 한다.
임의로 패키지의 경로를 설정하는 것은
basePackages
를 통해 설정이 가능하다. (
basePackages = "com.GilSSang"
)
또한,
basePackageClasses
를 통해 해당 클래스에 있는 위치로 부터의 패키지와 그 하위를 탐색하게 할 수 있다. (
basePackages = AutoConfig.class
)
이에 따라, 다음 그림과 같이
AutoConfig.class
가 있는 곳(
'mainpoint.mainpoint.Beann'
)부터 그 아래를 scan할 것이다.
그리고 각각
Repository
,
Service
인터페이스에 사용될 구현체가 scan의 대상이 될 수 있도록 각각에
@Component
를 붙여준다.
그리고 각각의 객체에 의존성 주입을 위해 생성자 주입, Setter 주입, 필드 주입, 일반 메서드 주입 중 하나를 선택하여 적용해주어야 한다.
이 4가지에 대해서 알아보자.
(1) 생성자 주입
public EmployeeRepository employeeRepository;@Autowiredpublic EmployeeServiceImplement(EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository;}
- 가장 좋은 방법이다.
- @Autowired를 통해 연결하여 생성자를 통해 주입받는다. (생성자가 한개인 경우 @Autowired 생략 가능)
- 빈을 등록하면서 바로 의존성 주입이 발생한다.
(2) Setter 주입
public EmployeeRepository employeeRepository;@Autowiredpublic void setEmployeeRepository(EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository;}
- @Autowired를 통해 연결하여 Setter를 통해 주입받는다.
- 빈을 모두 등록한 뒤, 의존성 주입이 발생한다.
- required파라미터를 통해 선택적으로 주입받을 수 있으며 변경이 필요할 경우 이를 사용한다.
(3) 필드 주입
@Autowiredpublic EmployeeRepository employeeRepository;
- 단순히, @Autowired 를 필드에 적용해주면 적용이 된다.
- 개발자가 직접 이 값에 주입할 수 없기 때문에 테스트에 비효율적이다.
- 되도록이면 지양하자.
(4) 일반 메서드 주입
public EmployeeRepository employeeRepository;@Autowiredpublic void injection(EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository;}
- @Autowired 를 메소드에 적용해주면 적용이 된다.
- 거의 사용하지 않는다.
이제 예제를 통해 직접 적용한 예제를 살펴보자.
일단, 이 예제에서는 필드 주입을 선택하여 간단하게 이를 처리할 것이다.
@Componentpublic class EmployeeRepositoryImplement implements EmployeeRepository { private static Long sequence = 0L; private Map<Long, Employee> store = new HashMap<>(); @Override public void save(Employee employee) { employee.setId(++sequence); store.put(employee.getId(), employee); } @Override public Employee findById(Long employeeId) { return store.get(employeeId); } @Override public List<Employee> findAll() { return new ArrayList<>(store.values()); }}
@Componentpublic class EmployeeServiceImplement implements EmployeeService { @Autowired EmployeeRepository employeeRepository; @Override public void join(Employee employee) { employeeRepository.save(employee); } @Override public Employee findEmployee(Long employeeId) { return employeeRepository.findById(employeeId); } @Override public List<Employee> findAllEmployee() { return employeeRepository.findAll(); }}
위에 설명과 같이
@Component
와
@Autowired
를 통해 이를 처리하였다.
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoConfig.class);@Testpublic void Config() throws Exception { String[] beanDefinitionNames = ac.getBeanDefinitionNames(); System.out.println("==========================="); for (String beanDefinitionName : beanDefinitionNames) { BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName); if (beanDefinition.getRole() == beanDefinition.ROLE_APPLICATION) { System.out.println("beanDefinition = " + beanDefinitionName); } } System.out.println("===========================");}
위와 같이 마찬가지로
AnnotationConfigApplicationContext
를 활용하여 이를 처리한 모습을 확인할 수 있다.
그리고 결과를 확인하면 알 수 있듯이 빈들이 잘 등록되어 사용되어지는 것을 볼 수 있다.
수동 빈 등록 vs 자동 빈 등록
다음과 같이 수동 빈 등록과 자동 빈 등록에 대해서 알아보았는데, 과연 한 빈이 수동과 자동으로 둘 다 등록이 되어있다면 어떻게 될까?
기본적으로는 수동이 자동보다 우선권을 가지지만, 이러한 상황 자체가 프로젝트가 큰 에러를 초래할 수 있다.
그에 따라, 스프링 부트에서는 이 같은 일이 발생하면 에러 처리를 해준다.
(만약, 정 쓰고 싶다면
application.properties
에 <
spring.main.allow-bean-definition-overriding=true
를 해주면 된다.)
하지만 이런 상황을 만들지 말자!
그러면 언제 수동 빈 등록을 사용해야되고 언제 자동 빈 등록을 사용해야 될까?
수동 빈 등록
- 공통 업무
- 직접 등록하는 기술 지원 객체
- 다형성을 적극 지원하는 비지니스 로직
- 라이브러리 활용(개발자가 제어 불가)
자동 빈 등록
- 기본적으로 자동 빈 등록을 사용
- 비지니스 업무 로직
- 직접 개발한 클래스
'스프링' 카테고리의 다른 글
MVC, Spring MVC (0) | 2021.09.18 |
---|---|
빈 초기화 콜백, 소멸전 콜백 (0) | 2021.09.16 |
같은 타입의 빈 등록(중복) (0) | 2021.09.16 |
스프링 컨테이너, 빈 (0) | 2021.09.15 |
스프링의 핵심 (0) | 2021.09.14 |