1. 의존성 주입
Dependency Injection
- IoC (Inversion of Control) 이라고도 함
- 어떤 객체가 사용하는 의존 객체를 직접 만드는 것이 아니라 주입 받아 사용하는 방법
1.1 예시
1.1.1 의존성 주입 없이 직접 사용할 객체를 만드는 예시
public class BookService {
private BookRepository bookRepository;
//constructor
public BookService() {
bookRepository = new BookRepository();
}
}
1.1.2 사용할 객체를 주입 받는 예시
public class BookService
private BookRespository bookRepository;
public void setBookRepository(BookRespository bookRepository) {
this.bookRepository = bookRepository;
}
}
1.1.3 설명
- BookService 클래스는 관련 객체들을 저장해 둔 BookRepository 클래스가 필요하다
- 즉, BookService 클래스는 BookRepository 클래스의 의존성을 가진다.
- BookService와 BookRepository가 둘 다 Bean으로 등록되어 있을 때 BookService 생성자만 만들어주면 스프링 IoC 컨테이너가 BookRepository에 의존성을 자동으로 주입한다.
- 스프링 4.3 이후부터 생성자가 하나인 경우 @Autowired 사용하지 않아도 된다.
결론은 의존성을 주입받아서 객체를 사용하라는 것
2. IoC 컨테이너
애플리케이션 컴포넌트의 중앙 저장소
- Bean configuration 소스로부터 Bean의 정의를 읽고, 구성하고, 제공한다.
- Bean간 의존 관계를 설정해준다.
- 즉, 객체 생성을 담당하고, 의존성을 관리한다.
- 중요한 인터페이스 BeanFactory, ApplicationContext
2.1 POJO
Plain Old Java Object
- 오래된 방식의 자바 객체 , 즉 평범한 자바 클래스
- 과거에 자바 웹 애플리케이션 개발을 위해서는 Servlet 클래스를 상속받아야 했는데 이는 POJO가 아니다.
- 즉, 특정 기술, 프레임워크에 종속되지 않고 동작하는 순수한 자바 객체
- 객체 지향적 원리에 충실하면서 환경과 기술에 종속되지 않고 필요에 따라 재활용할 수 있는 방식의 오브젝트
2.2 IoC 이외의 다른 호출 방식
2.2.1 클래스 호출 방식
- 클래스 내에 선언과 구현이 함께 존재
- 다양한 형태로 변화 불가능
- 클래스 -사용, 생성, 호출 → 클래스
2.2.2 인터페이스 호출 방식
- 인터페이스를 구현한 구현 클래스
- 클래스는 인터페이스를 사용해서 해당 인터페이스를 구현한 클래스를 생성하고 호출해서 사용함
- 구현 클래스를 교체해서 변화가 가능하지만 이때 이를 호출해서 사용하는 클래스도 코드 수정이 필요함
2.2.3 팩토리 호출 방식
- 호출 클래스는 팩토리 클래스만 호출하면 됨
- 팩토리가 인터페이스를 구현한 클래스를 생성함
- 구현 클래스 변경시 호출 클래스에는 영향이 없으나 팩토리 클래스에 수정이 필요
2.3 IoC 호출 방식
- IoC에서 인터페이스를 구현한 클래스를 생성해서 호출하던 클래스에 의존성을 주입
- 호출 클래스는 의존성을 주입 받아 인터페이스를 사용
- 서로의 의존성이 없음
- 실행 시점에 클래스간 관계 형성
3. 스프링 컨테이너
프로그래머가 작성한 코드의 처리 과정으 위임 받아 독립적으로 처리하는 존재
- 의존성 제어
- 객체간 의존성을 낮추기 위해 IoC 컨테이너 사용
3.1 BeanFactory
- 스프링 빈 컨테이너에 접근하기 위한 최상위 인터페이스
- 빈 객체를 생성, 관리하는 기본적인 기능을 가진 인터페이스
- 빈팩토리는 클라이언트의 요청이 있을 때 getBean()을 통해 빈 객체 (클래스 인스턴스) 를 생성 (lazy-loading)
- 일반적으로 스프링 프로젝트에서 사용될 일 드물다.
3.1.1 Bean
- 스프링 IoC 컨테이너가 관리하는 객체
- IoC에 등록됨 → 등록된 빈에 대해 의존성 관리가 쉬워지고, 싱글톤 형태로 관리됨 (선언된 하나의 빈에 대해 컨테이너에 단 하나의 객체만이 존재함 → getBean 요청이 들어올 때마다 새 인스턴스 생성)
3.1.2 Bean 등록하기
- 과거에는 직접 xml에 설정을 추가해줘야 했다. (무시무시.. )
- ApplicationConfiguration 클래스 생성 후 어노테이션으로 빈 등록하는 방법
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
public BookRepository bookRepository() {
return new BookRepository();
}
@Bean
public BookService bookService() {
BookService bookService = new BookService();
bookService.setBookRepository(bookRepository());
return bookService;
}
}
- 스프링 부트에서 지원하는 표준 방식 : 어노테이션으로 빈 설정하고 주입 받음
@Bean : 외부 라이브러리를 빈으로 등록할 경우
@Component : 개발자가 직접 만든 클래스를 빈으로 등록하고 싶은 경우
@Controller @Service @Repository @Congifuration @RestController: 위 @Component (빈)을 역할에 따라 명칭을 달리 지정하는 것
** @Configuration : IoC 컨테이너에게 해당 클래스가 빈 설정 파일임을 알림 -> 이 파일을 applicationContext가 읽어서 실행
@Autowired : 컨테이너에 등록된 스프링 빈을 찾아 주입시켜줌 ** 3.2에서 추가 설명
- @ComponentScan
- 프로젝트 생성시 등록한 최초 패키지 아래에 있는 파일 경로를 적어주면 해당 패키지 아래 있는 모든 빈 관련 어노테이션들을 찾아 빈으로 등록해줌
- 이 어노테이션이 설정된 클래스가 포함된 패키지 하위의 클래스에서만 대상을 찾음
- 그런데 스프링부트 프로젝트 생성시 메인 클래스가 있는 최상위 애플리케이션 클래스에 있는 @SpringBootApplication에 이 선언이 포함되어 있는 것 + @Configuration 포함
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackageClasses = DemoApplication.class)
public class ApplicationConfig {
}
3.2 @Autowired
- 필요한 의존 객체의 타입에 해당하는 빈을 찾아 주입
- 타입 - 생성자, setter, 필드
- 디폴트가 true → 의존성 주입할 대상을 찾지 못하면 애플리케이션 구동 실패
- @Autowired(required=false) 로 설정하면 빈으로 등록 X
- 같은 타입 ex)Repository 클래스가 여러개 인 경우 둘 다 빈으로 등록되어 있다면 주입 불가
- 빈으로 등록한 클래스하나에 @Primary를 등록 ex) @Repository @Primary
- 또는 @Autowired @Qualifier("등록할 빈의 이름")
- 또는 List<..>으로 해당 타입의 모든 빈을 주입받는 방식
- primary가 더 안정적인 방법
3.2.1 Constructor DI
- 생성자 주입
- 권고되는 방법
- 생성자에 의존성 주입을 받고자 하는 필드를 나열하는 방식
- 필수적으로 사용해야 하는 레퍼런스 없이는 인스턴스를 만들 수 없도록 강제함
3.2.2 Field DI
- 객체의 멤버 필드에 @Autowired 어노테이션을 선언하여 주입 받는 방법
- 이 필드는 final** 선언 불가
- ** final : 여러 컨텍스트에서 단 한 번만 할당될 수 있는 객체를 정의할 때 사용하는 키워드, 한번 초기화하면 변경할 수 없는 상수값, 객체 자체 변경 불가 (객체 속성 변경은 setter 함수로 가능), 메소드 내부 변수값 변경 불가
3.2.3 Setter DI
- setter 메소드에 Autowired 어노테이션을 달아 주입 받는 방법
- 의존성이 선택적으로 필요한 경우 사용
3.3 ApplicationContext
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
- 빈 팩토리를 상속받은 인터페이스
- 스프링의 핵심 인터페이스
- 빈 객체를 생성하고 관리하는 기능을 가짐
-
- 트랜잭션 관리, 메시지 기반 다국어 처리, AOP처리, DI 등 지원
- pre-loading 방식 : 컨테이너가 구동되는 시점에 객체들을 생성
3.3.1 EnvironmentCapable 인터페이스
- 프로파일과 프로퍼티를 다루는 인터페이스
- 프로파일 profile
- 빈들의 묶음
- 어떤 환경에서는 어떤 빈들만 사용하겠다는 것을 의미
- 등록한 프로파일 실행 → Edit Configurations → VM options에 -Dsping.profiles.active="test"
- 프로파일 표현식에 !, | , & 사용 가능
@Configration // bean 설정 타입 중 하나
@Profile("test") // test라는 프로파일로 애플리케이션을 실행하기 전까지 아래 빈 설정이 적용되지 않음
public class TestConfiguration{
@Bean
public BookRepository bookRepository() {
return new TestBookRepository();
}
}
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext ctx;
@Autowired // test 프로파일로 실행하지 않은 경우 에러 발생 -> 빈으로 등록되지 않았기 때문!
BookRepository bookRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
Environment environment = ctx.getEnvironment();
System.out.println(Arrays.toString(environment.getActiveProfiles()));
System.out.println(Arrays.toString(environment.getDefaultProfiles()));
}
}
- 프로퍼티 properties
- 다양한 방법으로 정의할 수 있는 설정값
- environment 역할 : 프로퍼티 소스 설정 및 값 가져오는 것
- 같은 프로퍼티가 있을 경우 우선순위대로 높은 것을 설정값으로 가져옴
3.3.2 MessageSource 인터페이스
- 메시지를 다국화하는 인터페이스
- 메시지 설정 파일을 모아놓고 각 국가마다 로컬라이징을 하여 각 지역에 맞는 메시지를 제공할 수 있음
- [file_name][language][country].properties 파일 생성
- ex) messages_kr_KR.properties >> greeting=한국어, {0}
- 메시지 소스 리로딩하는 방법 : ReloadableResourceBundleMessageSource 객체를 통해 메시지 프로퍼티를 갱신하면서 읽음 ⇒ 스프링부트에서는 메시지를 직접 빈으로 등록하지 않아도 messages.properties 사용 가능
import ...
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(10);
return messageSource;
}
}