4. 스프링 애너테이션 - @ComponentScan과 @Component
- @ComponentScan으로 @Component를 자동 검색해서 빈으로 등록
- @Controller, @Service, @Repository, @ControllerAdvice의 메타 애너테이션
@Configuration
// @ComponentScan("com.fastcampus.ch3") - basePackages 생략
// @ComponentScan(basePackages = {"com.fastcampus.ch3"}) - 패키지 지정
// @ComponentScan(basePackageClasses = AppConfig.class) - 클래스 지정
@ComponentScan // 이 애너테이션이 붙은 클래스의 패키지를 자동 스캔
public class AppConfig {
// Bean(name = "superEngine") Engine superEngine() {return new SuperEngine();}
// @Component("superEngine")
@Component
class SuperEngine extends Engine {}
프로젝트를 진행할 때 여러 사람이 하다보면, 하나의 파일을 같이 변경해야 하니 불편한 게 있을 수 있다.
그럴 때 각자 개발한 클래스에 @Component만 붙여줘도 된다.
이 때 @ComponentScan을 붙여서 빈으로 등록해놓아야 한다. 이렇게 써놓으면, AppConfig가 있는 클래스를 다
찾는다.
@Component("superEngine")의 생략 버전이 @Component이기 때문에 앞글자를 소문자로 한 "superEngine"객체가
저장소에 등록되게 된다.
| Key | Value |
| "superEngine" | 0x100 |
| ... | ... |
@Controller, @Service, @Repository, @ControllerAdvice, @Configuration들도 @ComponentScan에 의해서 자동 검색이 된다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
이런식으로 애너테이션을 정의할 때 @Component가 들어있는 애너테이션들이 존재한다.
<실습>
AppConfig.java의 내부 코드와 Main의 실행문에서 아래 두 줄을 제외한 출력문들을 주석처리한 후 결과를 실행하면
System.out.println("ac.getBeanDefinitionCount() = " + ac.getBeanDefinitionCount());
System.out.println("ac.getBeanDefinitionNames() = " + ac.getBeanDefinitionNames());

다음과 같이 appConfig만 빈으로 등록된 것을 확인할 수 있다.
[Main.java]
@Component
class Car {
Engine engine;
Door door;
@Override
public String toString() {
return "Car{" +
"engine=" + engine +
", door=" + door +
'}';
}
}
@Component
class Engine {}
@Component
class Door {}
[AppConfig.java]
@Configuration
@ComponentScan
public class AppConfig {
// @Bean
// Car car() {return new Car();}
//
// @Bean
// @Scope("prototype")
// Engine engine() {return new Engine();}
//
// @Bean
// Door door() {return new Door();}
}
AppConfig에 @ComponentScan을 붙여주고, 빈으로 등록할 클래스의 앞에 @Component를 붙여준다면

car, engine, door도 빈으로 등록되는 것을 볼 수 있다.
여러 사람이 개발할 때는 자신이 개발한 클래스에 @Component를 붙여서 @ComponentScan이 자동적으로 수행되도록
하는 것도 좋은 방법이다.
기본적인 것은 @Bean 애너테이션을 이용하고, 이후 추가적으로 개발되는 것들은 @Component, @ComponentScan을
이용하면 좋다.
5. 스프링 애너테이션 - @Value와 @PropertySource
@Component
@PropertySource("setting.properties")
class SysInfo {
@Value("#{systemProperties['user.timezone']}")
String timeZone;
@Value("#{systemEnvironment['PWD']}")
String currDir;
@Value("${autosaveDir}")
String autosaveDir;
@Value("${autosaveInterval}")
int autosaveInterval;
@Value("${autosave}")
boolean autosave;
}
@Value애너테이션은 String, int와 같은 값을 하드코딩하지 않고, 동적으로 다른 저장된 값을 읽어와서
timeZone과 같은 변수를 초기화해주는 역할을 한다.
그걸 읽어올 파일 이름을 @PropertySource로 지정한다. 파일을 읽어오거나 sysProperties, systemEnvironment와 같은
시스템 환경 변수들을 읽어서 사용할 수 있다. (하드코딩보다 변경에 유리!)
Ex)
[src/main/resource/setting.properties]라는 파일을 만들었다고 가정한다.
autosaveDir=/autosave
autosave=true
autosaveInterval=30
왼쪽이 key, 오른쪽이 value
파일을 만들어놓고 파일에 있는 내용을 읽으려면 위 예제 코드와 같이 파일을 지정해주고, @Value 애너테이션을
이용해서 파일의 내용을 읽어와 그 값을 변수에 저장하도록 하면 된다.
그러면 값을 직접 작성할 필요가 없고, 변경 사항이 발생했을 때 파일만 변경하면 되므로 변경에 유리한 코드가 된다.
Map<String, String> env = System.getenv();
System.out.println("System.getenv() = " + env);
Properties prop = System.getProperties();
System.out.println("System.getProperties = " + prop);
System.out.println("ac.getBean(SysInfo.class) = " + ac.getBean(SysInfo.class));
Map<String, String> env = System.getenv();
은 맵형태로 데이터를 주는데, 여기 들어있는 값들 중 'PWD'라는 값을 읽어서 거기 저장되어 있는 값을 저장.
(cf. PWD는 현재 작업 폴더를 의미)
Properties prop = System.getProperties();
이 안에 어떤 key, value값이 있는지 확인할 수 있다.
<실습>
[SysInfo.java]
package com.fastcampus.ch3.di3;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("setting.properties")
public class SysInfo {
@Value("#{systemProperties['user.timezone']}")
String timeZone;
@Value("#{systemEnvironment['PWD']}")
String currDir;
@Value("${autosaveDir}")
String autosaveDir;
@Value("${autosaveInterval}")
int autosaveInterval;
@Value("${autosave}")
boolean autosave;
@Override
public String toString() {
return "SysInfo{" +
"timeZone='" + timeZone + '\'' +
", currDir='" + currDir + '\'' +
", autosaveDir='" + autosaveDir + '\'' +
", autosaveInterval=" + autosaveInterval +
", autosave=" + autosave +
'}';
}
}
[setting.properties]
autosaveDir=/autosave
autosave=true
autosaveInterval=30
[Main.java]
SysInfo info = ac.getBean(SysInfo.class);
System.out.println("info = " + info);

스프링이 알아서 타입에 맞게 변환을 해준 후 값들을 출력해준다.
6. 스프링 애너테이션 - @Autowired
- Spring container(AC)에서 타입으로 빈을 검색(by Type)해서 참조 변수에 자동 주입(DI)
- 검색된 빈이 n개이면, 그 중에 참조변수와 이름이 일치하는 것을 주입
- 주입 대상이 변수일 때, 검색된 빈이 1개 아니면 예외 발생
- 주입 대상이 배열일 때, 검색된 빈이 n개라도 예외 발생X
[참고] @Autowired(required=false)일 때, 주입할 빈을 못찾아도 예외 발생X.
@Autowired Engine engine;
@Autowired Engine[] engines;
@Autowired(required = fasle)
SuperEngine superEngine;
@Component
class TurboEngine extends Engine {}
@Component("engine")
class SuperEngine extends Engine {}
TurboEngine과 SuperEngine이 둘 다 engine의 자손이니 2개가 등록이 되어있다.
그럴 때는 참조 변수와 이름이 일치하는 @Component("engine")을 찾아 주입하게 된다.
하지만 여기서 SuperEngine은 superEngine으로 이름이 일치하지 않는다.
이렇게 불일치 하는 경우 TurboEngine, SuperEngine 둘 중 무엇을 넣어야 할 지 판단할 수 없어 에러가 난다.
이 때 required = false로 두면 에러가 발생하지 않는다.(true가 디폴트)
주입할 빈이 없으면 에러가 나지않고 주입을 하지 않는다. 꼭 필수로 주입할 빈이 아닌 경우 required = false로 두면 된다.
But) 보통은 타입이 1개니 이런 경우는 많이 없다!
아니면, 배열로 두 개의 빈을 다 넣을 수도 있다.
<실습>
[Main.java]

@Autowired를 넣기 전 null 값이 나온다.
@Component
class Car {
@Autowired
Engine engine;
@Autowired
Door door;

@Autowired를 넣고 실행하면 engine과 door가 잘 들어간 것을 볼 수 있다.
- Bean을 더 추가해보자

Engine이 들어간 것을 볼 수 있는데, 보면 같은 타입의 빈이 3개이다.(정확히는 다름)
engine타입으로 instanceof를 검사하니 3개가 다 주입 대상이 되는 것이다.
이렇게 여러 개일 땐 3개의 후보 중에서 engine 이름과 일치하는 것을 찾아서 넣게 되니 Engine이 들어가게 되는 것이다.

Engine에 있는 @Component를 주석처리 하니 에러가 발생한다.
하나를 넣어야하는데 superEngine과 turboEngine 두 개가 발견된 것이다.
이럴 땐 배열로 처리해주면 빈이 여러개가 검색되도 가능해진다.

engine이 배열로 들어간 것을 볼 수 있다.
- @Autowired를 생성자에 붙일 수도 있다.
그러면 빈이 만들어질 때 생성자를 이용해서 빈을 주입할 수 있다.(객체 주소 대입)
참조 변수에 붙어있는 @Autowired를 주석처리 해준 후 코드를 실행해도 빈이 주입되는 것을 볼 수 있다.


생성자에 붙은 @Autowired를 생략해도 생성자가 있는 경우에 자동으로 빈을 주입해준다.
- Car라는 기본 생성자가 존재할경우

null이 나오는 것을 볼 수 있는데, 기본 생성자를 이용해서 Car를 생성한 것이다.
=> 생성자가 여러 개라면 @Autowired를 생략할 수 없다!! (기본 생성자를 이용)

7. 스프링 애너테이션 - @Resource
- Spring container(AC)에서 이름으로 빈을 검색(by Name)해서 참조 변수에 자동 주입(DI)
- 일치하는 이름의 빈이 없으면, 예외 발생
class Car {
@Resource(name="superEngine")
Engine engine;
}
리소스를 생략하면 name이 이름으로 지정한 것과 같다. 같은 경우 생략 가능
class Car {
// @Resource(name="engine")
@Resource
Engine engine;
}
이름으로 찾는 경우 @Qulifier를 사용할수도 있다.
@Autowired로 검색하는 경우는 by Type으로 검색하는데, 2개 이상이라면 이름으로 찾는다.
그 이름을 지정해줘서 이름과 일치하는걸 찾아서 참조변수에 주입해준다.
class Car {
@Autowired
@Qualifier("superEngine")
Engine engine;
}
@Resource를 사용해도 되고, @Autowired, @Qualifier를 사용해도 된다.
<실습>

@Resource(name="superEngine")을 실행한 결과 SuperEngine이 들어간 것을 볼 수 있다.

@Autowired와 @Qulifier를 이용한 경우에도 superEngine을 잘 주입한 것을 볼 수 있다.
8. 스프링 애너테이션 vs 표준 애너테이션(JSR-330)
- javax.inject-1.jar - @Inject, @Named, @Qualifier, @Scoper, @Singleton
- annotations-api.jar - @Resource, @ManagedBean, @PreDestory, @PostContruct
| 스프링 애너테이션 | 표준 애너테이션 | 비고 |
| @Autowired | @Inject | @Inject에는 required속성이 없음 |
| @Qualifier | @Qualifier, @Named | 스프링의 @Qualifier는 @Named와 유사 |
| - | @Resource | 스프링에는 이름 검색이 없음 |
| @Scope("singleton") | @Singleton | 표준에서는 prototype이 디폴트 |
| @Component | @Named, @ManagedBean | 표준에서는 반드시 이름이 있어야 함 |
'SpringBoot' 카테고리의 다른 글
| ch3 07. 의존성 관리와 설정의 자동화(2) (0) | 2023.07.31 |
|---|---|
| ch3 06. 의존성 관리와 설정의 자동화(1) (0) | 2023.07.31 |
| ch3 04. Bean과 ApplicationContext (0) | 2023.07.30 |
| ch3 03. Spring DI의 원리(2) (0) | 2023.07.29 |
| ch3 02. Java Reflection API (0) | 2023.07.19 |