필요한 의존 객체의 “타입"에 해당하는 빈을 찾아 주입한다.
@Autowired
사용할 수 있는 위치
생성자나 필드에 의존성을 주입받으려고 하는 경우 객체 자체는 만들 수 있지만 @Autowired라는 애노테이션이 붙으면 빈이 있어야만 의존성을 주입받을 수 있기 때문에 애플리케이션 구동에 실패한다. 이 경우에는 @Autowired(required=false)로 지정하면 된다.
public class BookRepository {
}
@Service
public class BookService {
BookRepository bookRepository;
@Autowired(required = False)
public void setBookRepository(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
경우의 수
같은 타입의 빈이 여러개 일 때
@Repository @Primary
public class MyBookRepository implements BookRepository{
}
@Repository
public class MyBookRepository2 implements BookRepository{
}
@Primary 애노테이션이 붙은 MyBookRepository를 사용하게 된다.
@Service
public class BookService {
@Autowired
List<BookRepository> bookRepositories;
public void printBookRepository(
this.bookRepositories.forEach(System.out::println);
}
}
@Service
public class BookService {
@Autowired @Qualifier("myBookRepository")
BookRepository BookRepository;
public void printBookRepository(){
System.out.println(bookRepository.getClass());
}
}
또는 필드 이름을 보고 필드 이름과 동일한 타입과 이름을 가진 빈을 주입받는다.
@Service
public class BookService {
@Autowired
BookRepository myBookRepository;
public void printBookRepository(){
System.out.println(mybookRepository.getClass());
}
}
동작원리
BeanPostProcessor
라는 빈 라이프 사이클 인터페이스에 의해 처리된다. 빈의 라이프사이클에는 빈의 인스턴스를 만든 다음에 Initialization 과정(빈의 이 있는데 그 전후에 BeanPostProcessor 라이프 사이클 인터페이스를 통해 새로 만든 빈 인스턴스를 수정하는 라이프 사이클 콜백을 처리할 수 있다.
@Service
public class BookService {
@Autowired @Qualifier("myBookRepository")
BookRepository myBookRepository;
@PostConstruct
public void setUp(){
System.out.println(myBookRepository.getClass());
}
}
@Service
public class BookService implements InitializingBean {
@Autowired @Qualifier("myBookRepository")
BookRepository myBookRepository;
@Override
public void afterPropertiesSet() throws Exception {
}
}
빈 팩토리 라이프사이클 인터페이스와 메소드
또한 스프링에서는 AutowiredAnnotationBeanPostProcessor extends BeanPostProcessor
를 사용하여 스프링에 제공하는 @Autowired와 @Value, @Inject 애노테이션을 처리할 수 있다. (BeanPostProcessors의 postProcessBeforeInitialization 메소드 부분에서 처리)
확인하는 방법
@Component
public class MyRunner implements ApplicationRunner {
@Autowired
ApplicationContext applicationContext;
/*
@Autowired
AutowiredAnnotationBeanPostProcessor processor;
*/
@Override
public void run(ApplicationArguments args) throws Exception {
AutowiredAnnotationBeanPostProcessor bean = applicationContext.getBean(AutowiredAnnotationBeanPostProcessor.class);
System.out.println(bean);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* Alias for {@link #basePackages}.
* <p>Allows for more concise annotation declarations if no other attributes
* are needed — for example, {@code @ComponentScan("org.my.pkg")}
* instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>{@link #value} is an alias for (and mutually exclusive with) this
* attribute.
* <p>Use {@link #basePackageClasses} for a type-safe alternative to
* String-based package names.
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages
* to scan for annotated components. The package of each class specified will be scanned.
* <p>Consider creating a special no-op marker class or interface in each package
* that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
컨포넌트 스캔 주요 기능
@Component
@Component 애노테이션이 붙은 빈들은 기본적으로 Singleton 스코프이기 때문에 런타임시에 모두 빈을 생성한다. 따라서 런타임시에 시간이 오래 걸릴 수 있다.
스프링 런타임 시간을 짧게 하고 싶은 경우 -> 스프링 5부터 도입된 펑션을 사용한 빈 등록(reflection이나 cglib같은 프록시를 사용하지 않기 때문)
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(Demospring51Application.class)
.initializers((ApplicationContextInitializer<GenericApplicationContext>) applicationContext -> {
applicationContext.registerBean(MyBean.class);
})
.run(args);
}
@SpringBootApplication
public class Springtest2Application {
public static void main(String[] args) {
// SpringApplication.run(Springtest2Application.class, args);
var app = new SpringApplication(Springtest2Application.class);
app.addInitializers(new ApplicationContextInitializer<GenericApplicationContext>() {
@Override
public void initialize(GenericApplicationContext ctx) {
ctx.registerBean(MyService.class); // 컴포넌트 스캔 밖에 있는 클래스 빈으로 등록
}
});
}
}
@SpringBootApplication
public class Springtest2Application {
@Autowired
MyService myService;
public static void main(String[] args) {
// SpringApplication.run(Springtest2Application.class, args);
var app = new SpringApplication(Springtest2Application.class);
app.addInitializers((ApplicationContextInitializer<GenericApplicationContext>) ctx -> {
ctx.registerBean(MyService.class); // 컴포넌트 스캔 밖에 있는 클래스 빈으로 등록
}
}
또는 Functional Interface를 사용할 수도 있다.
@SpringBootApplication
public class Springtest2Application {
@Autowired
MyService myService;
public static void main(String[] args) {
// SpringApplication.run(Springtest2Application.class, args);
var app = new SpringApplication(Springtest2Application.class);
/*
app.addInitializers(new ApplicationContextInitializer<GenericApplicationContext>() {
@Override
public void initialize(GenericApplicationContext ctx) {
ctx.registerBean(MyService.class);
}
});
*/
app.addInitializers((ApplicationContextInitializer<GenericApplicationContext>) ctx -> {
ctx.registerBean(MyService.class);
ctx.registerBean(ApplicationRunner.class, () -> args1 -> System.out.println("Functional Bean Definition!!"));
// ctx.registerBean(ApplicationRunner.class, new Supplier<ApplicationRunner>() {
// @Override
// public ApplicationRunner get() {
// return args1 -> System.out.println("Functinal Bean Definition!!");
// }
// @Override
// public ApplicationRunner get() {
// return new ApplicationRunner() {
// @Override
// public void run(ApplicationArguments args) throws Exception {
// System.out.println("Functional Bean Definiton!!");
// }
// };
// }
// });
});
app.run(args);
}
}
@ComponentScan을 버리고 펑션를 사용해 일일이 빈을 등록하는 것 보다는 @Configuration + @Bean을 사용하는 방법으로 등록된 @Bean을 등록하기 위한 것 정도는 괜찮다.
동작 원리
BeanFactoryPostProcessor는 다른 모든 빈들을 만들기 이전에 적용된다. 즉, 다른 모든 빈들을 만들기 이전에 컴포넌트 스캔을 해서 적용한다는 것이다.
스코프
프로토타입 빈이 싱글톤 빈을 참조하면?
@Component @Scope(value = "prototype")
public class Proto {
@Autowired
Single single;
}
싱글톤 빈이 프로토타입 빈을 참조하면?
@Component
public class Single {
@Autowired
private Proto proto;
public Proto getProto() {
return proto;
}
}
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {
@Autowired
Single single;
}
cglibrary라는 third-party tool을 사용해서 class 기반의 proxy를 만들도록 한다. 원래 자바에서는 인터페이스 기반의 proxy 밖에 만들 수 없다. proxy 또한 같은 타입이기 때문에 주입받을 수 있다.
또한 proxy를 사용하는 것 말고도 다른 방법이 있다.
@Component
public class Single {
@Autowired
private ObjectProvider<Proto> proto;
public Proto getProto() {
return proto.getIfAvailable();
}
}
싱글톤 객체 사용시 주의할 점
@Component
public class Single {
@Autowired
private Proto proto;
// 쓰레드 세이프하다고 보장받을 수 없음(공유되는 bean)
int value = 0;
public Proto getProto() {
return proto;
}
}
참고