프로그래밍/기타

1.2 IoC_DI를 위한 빈 설정 메타정보 작성

2jun0 2023. 1. 3. 22:59

@Component

빈 스캐너가 @Component가 붙은 클래스를 빈으로 등록한다.

아래 클래스는 annotatedHello라는 아이디로 빈 등록된다.

@Component
public class AnnotatedHello {
    ...
}

빈의 아이디는 임의로 설정할 수 있다.

@Component("myAnnotationHello")
public class AnnotatedHello {
    ...
}

@Component는 메타 애노테이션으로도 사용가능하다.
즉, 다음과 같이 커스텀 애노테이션을 정의할 수 있다.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Compenent
public @interface MyComponent {
    String value() default "";
}

@Configuration & @Bean

@Configuration애노테이션을 클래스에 달아두면 자바코드로 빈 설정 메타정보를 담을 수 있다.

@Configuration
public class AnnotatedHelloConfig {
    @Bean
    public AnnotatedHello annotatedHello() {
        return new AnnotatedHello();
    }
}

클래스 안의 @Bean이 달려있는 메서드는 빈으로 등록된다.
타입은 반환타입인 AnnotatedHello으로,
아이디는 메서드 이름인 annotatedHello로 등록된다.

또한 AnnotatedHelloConfig 자체도 빈으로 등록된다.


@Configuration에서 등록된 빈은 싱글톤으로 생성된다.

@Configuration
public class AnnotatedHelloConfig {

    @Bean
    public Person person1() {
        Person person = new Person();
        // 자동차는 하나만 만들어진다.
        person.setCar(car());
        return person;
    }

    @Bean
    public Person person2() {
        Person person = new Person();
        // 자동차는 하나만 만들어진다.
        person.setCar(car());
        return person;
    }

    @Bean
    public Car car() {
        return new Car();
    }
}

@Configuration+@Bean으로 등록되는 빈은 싱글톤이기 때문에
car()를 여러번 호출해도 같은 객체가 반환이 된다.

결국 person1빈과 person2빈은 같은 자동차 객체를 가지게 된다.


반면에 일반 클래스에 @Bean만 붙는다면 싱글톤으로 관리되지 않는다.

public class Normal {

    @Bean
    public Person person1() {
        Person person = new Person();
        // 자동차는 여러개 만들어 질 수 있다.
        person.setCar(car());
        return person;
    }

    @Bean
    public Person person2() {
        Person person = new Person();
        // 자동차는 여러개 만들어 질 수 있다.
        person.setCar(car());
        return person;
    }

    @Bean
    public Car car() {
        return new Car();
    }
}

이번에는 car가 싱글톤 빈이 되지 않기 때문에
car()를 여러번 호출하면 각각 다른 객체가 생성된다.

person1빈과 person2빈은 다른 자동차 객체를 가지게 된다.


그렇다면 일반 클래스에서 싱글톤 빈을 사용하고 싶다면 어떻게 해야할까?

public class Normal {

    private Car car;

    public void setCar(Car car) {
        this.car = car;
    }

    @Bean
    public Person person1() {
        Person person = new Person();
        // 자동차는 여러개 만들어 질 수 있다.
        person.setCar(this.car);
        return person;
    }

    @Bean
    public Person person2() {
        Person person = new Person();
        // 자동차는 여러개 만들어 질 수 있다.
        person.setCar(this.car);
        return person;
    }

    @Bean
    public Car car() {
        return new Car();
    }
}

Normal클래스가 Car빈을 주입 받도록 하면 같은 빈 객체를 쓸 수 있을 것이다.
car()메서드는 스프링 컨테이너에 의해 싱글톤 오브젝트를 만들 때 한번만 호출된다.

AnnotationConfigApplicationContext

애노테이션을 사용할 경우 AnnotationConfigApplicationContext를 이용하면 안에 빈 스캐너가 내장되어 있어, @Component가 붙은 클래스를 빈으로 등록해준다.

public void simpleBeanScanning() {
    ApplicationContext ctx = new AnnotationConfigApplicationContext("spring.demo.2jun0.bean");
    // 빈의 아이디로 가져올 수 있다.
    AnnotatedHello h = ctx.getBean("annotatedHello", AnnotatedHello.class);
}

생성자에 @Configuration이 붙은 클래스를 넣어주는 방법도 있다.

public void simpleBeanScanning() {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AnnotatedHelloConfig.class);
    // 특정 타입의 빈이 하나만 존재한다면 이름을 생략할 수 있다.
    AnnotatedHello h = ctx.getBean(AnnotatedHello.class);
    // 설정 클래스도 빈으로 등록된다.
    AnnotatedHelloConfig c = ctx.getBean("annotatedHelloConfig", AnnotatedHelloConfig.class);
}

@Resource 빈 의존관계 설정 방법

수정자 메서드

public class Hello {
    ...

    @Resource(name="printer")
    public void setPrinter(Printer printer) {
        this.printer = printer;
    }
}

필드

이런 방식을 필드 주입이라고 한다.
private여도 스프링이 주입해준다.

public class Hello {
    @Resource(name="printer")
    private Printer printer;
}

@Resource는 name을 생략해도 된다. 다만, 이렇게 하면 메서드나 필드 이름으로 적용된다.
(@Autowired는 타입으로 찾는다.)

@Autowired 빈 의존관계 설정 방법

@Autowired@Resource와 비슷하지만 다른 점은 이름 대신 타입으로 찾는다는 점이다.

수정자 메서드

public class Hello {
    ...

    @Autowired
    public void setPrinter(Printer printer) {
        this.printer = printer;
    }
}

필드 주입

public class Hello {
    @Autowired
    private Printer printer;
}

생성자 주입

생성자로 주입할 수 있다는 것도 @Resource와 다른 점이다.

public class Hello {
    // 혼동을 피하기 위해 하나의 생성자에만 적용할 수 있다.
    @Autowired
    public Hello(Printer printer, Reader reader) {
        this.printer = printer;
        this.reader = reader;
    }
}

일반 메서드 주입

일반 메서드에도 @Autowired를 적용할 수 있다.

public class Hello {
    // 한 개 이상의 메서드에 적용 가능!
    @Autowired
    public void config(Printer printer, Reader reader) {
        this.printer = printer;
        this.reader = reader;
    }
}

동일한 타입의 빈이 여러개 있을 때

동일한 타입의 빈이 여러개 있을땐 필드나, 파라미터를 컬렉션이나 배열로 선언하면 된다.

public class Hello {
    @Autowired
    private Collection<Printer> printers;

    @Autowired
    private Printer[] printers;
}

Map을 이용하면 {이름:객체} 쌍으로 받을 수 있다.

public class Hello {
    @Autowired
    private Map<String, Printer> printerByName;
}

@Qualifier

@Qualifier를 사용하면 @Autowired에도 빈 이름을 지정할 수 있다.

필드에 사용할 수 있고,

public class Hello {
    @Autowired
    @Qualifier("myPrinter")
    private Printer printer;
}

수정자에 사용할 수 있고,

public class Hello {
    ...

    @Autowired
    @Qualifier("myPrinter")
    public void setPrinter(Printer printer) {
        this.printer = printer;
    }
}

파라미터에 사용할 수 있다.

public class Hello {

    @Autowired
    public void config(@Qualifier("myPrinter") Printer printer, Reader reader) {
        this.printer = printer;
        this.reader = reader;
    }
}

@Value으로 디폴트 값 설정하기

빈이 없더라도 @Value를 이용해 디폴트 값을 정할 수 있다.

public class Hello {
    @Value("1.2")
    private double num;
}