[Spring] IOC (제어의 역전)
객체의 의존성
A 클래스가 B 클래스의 객체를 생성해 사용하는 것을 'A는 B에 의존한다'고 말한다
public class A{
public static void main(String[] args){
B b = new B();
b.action();
}
}
class B {
public void action(){
System.out.println("A는 B에 의존합니다");
}
}
제어의 역전(Inversion Of Control)
스프링 컨테이너는 사용자가 new 생성자를 사용해서 객체를 생성하지 않고도 객체가 다른 객체를 사용할 수 있도록(의존할 수 있도록) 한다. 스프링은 객체를 빈으로 생성하고 관리한다. 이때 제어권이 사용자에게서 프레임워크로 이동되는 것을 제어의 역전(IoC)이라고 한다
빈(Bean)
스프링이 제어권을 가지고 생성하며 의존관계를 부여하는 객체
스프링 컨테이너
- BeanFactory와 ApplicationContext가 있다
- 클래스들이 서로 의존할 수 있도록 의존성 주입(Dependency Injection, DI)을 한다
- 빈객체를 생성하고 생명 주기를 관리한다
- 이때 생성되는 빈은 하나만 생성되는 싱글톤 방식이다
스프링 Bean 등록 방법
컴포넌트 스캔은 @Component가 붙은 클래스를 빈으로 등록한다
@Component
// Bean 이름은 dao로 자동 지정(lowerCamelCase)
@Component
class Dao{
}
// Bean 이름을 dao2로 지정
@Component("dao2")
class Dao3{
}
@Component("Bean 이름")
bean의 이름을 지정하지 않으면 클래스명을 lowerCamelCase로 변환한 것이 이름이 된다
bean의 이름을 지정해주고 싶다면 @Component("Bean 이름") 처럼 괄호 안에 빈의 이름을 적어주면 된다
@Configuration
: @Component를 붙일 수 없는 클래스의 빈을 생성할 때 사용한다
컴포넌트 스캔은 @Bean 어노테이션이 붙은 메소드가 반환하는 객체를 빈으로 등록한다
빈의 이름은 메소드명으로 결정된다
@Configuration
class MyConfiguration{
@Bean
public Myclass MyBean(){
return new MyClass();
}
}
@Controller
@Controller는 @Component를 포함하고 있다
의존성 주입하는 방법
컴포넌트 스캔으로 생성한 스프링 빈을 @AutoWired를 사용해 의존성 주입(DI)하여 사용한다
@AutoWired
1. 필드 주입
필드를 사용해 의존성 주입을 하는 방법이다
외부에서 변경이 어렵기 때문에 추천하지 않는 방법이다
@Component
class MyClass91 {
}
@Component
class MyClass92 {
@Autowired
private MyClass91 myClass91;
}
2. 생성자 주입
생성자를 통해 의존성 주입을 하는 방법이다
@Component
@Getter
class MyClass112 {
private MyClass111 field; // dependency
// 생성자 주입
@Autowired // 생성자가 하나일 때 생략 가능
public MyClass112(MyClass111 field) {
this.field = field;
}
}
🌟 @AutoWired 생략 가능
생성자가 하나일 때, @AutoWired 어노테이션을 생략할 수 있다.
또 Lombok의 @RequiredArgsConstructor는 final 키워드가 붙은 필드를 파라미터로 받는 생성자를 자동 생성하므로 다음과 같이 간략하게 쓸 수 있다
@Component
@RequiredArgsConstructor // final인 필드를 파라미터로 받는 생성자 자동 생성
class MyClass122 {
private final MyClass121 field;
// // 필드가 final이고 생성자로 할당하면 자동으로 주입됨
// MyClass122(MyClass121 field) {
// this.field = field;
// }
}
// 생략된 코드
@Component
@RequiredArgsConstructor
class MyClass131 {
private final MyClass132 field;
}
생성자 주입을 사용할 경우 다음과 같은 장점이 있다
- 순환 참조 방지
- 객체의 불변성
- 테스트에 용이
3. setter 주입
setter 메소드가 public으로 열려있기 때문에 변경이 쉬워 추천하지 않는 방법이다
@Component
class MyClass142 {
private MyClass141 field;
@Autowired
public void setField(MyClass141 field) {
this.field = field;
}
}
스프링 빈을 사용하는 이유?
객체의 의존성 주입을 받을 타입을 인터페이스로 설정하면 여러 인터페이스를 구현한 클래스를 주입받을 수 있습니다. 이러한 방법으로 다형성을 구현할 수 있습니다. 또한 코드의 결합도를 낮추고 관리가 용이해지는 등의 장점이 있습니다.
public class Application16 {
public static void main(String[] args) {
BeanFactory factory = SpringApplication.run(Application16.class);
MyClass161 bean = factory.getBean(MyClass161.class);
bean.action();
}
}
@Component
@RequiredArgsConstructor
class MyClass161 {
private final MyInterface161 field;
public void action() {
field.someMethod();
}
}
// @Component
interface MyInterface161 {
public void someMethod();
}