[Spring] IoC와 DI: Bean, ApplicationContext, @Autowired
정의
**IoC (Inversion of Control)**는 객체 생성/생명주기 제어를 프레임워크가 가져가는 원칙. **DI (Dependency Injection)**는 그 구현 방법. Spring 컨테이너(ApplicationContext)가 Bean을 관리하고 의존성을 주입.
Bean 등록
@Component (그리고 sterotype)
@Component
public class UserService {
...
}
@Service // @Component 의 별칭 (비즈니스 로직)
public class OrderService { ... }
@Repository // @Component (DB 접근, 예외 변환)
public class UserRepository { ... }
@Controller // @Component (웹)
public class HomeController { ... }
@RestController // @Controller + @ResponseBody
public class ApiController { ... }
@SpringBootApplication의 @ComponentScan이 자동 검색.
@Configuration + @Bean
@Configuration
public class AppConfig {
@Bean
public RestClient restClient() {
return RestClient.builder()
.baseUrl("https://api.example.com")
.build();
}
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
@Bean 메서드의 반환값이 Bean. 외부 라이브러리 등 직접 @Component 못 다는 경우.
의존성 주입
생성자 주입 (권장)
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
장점:
- final 필드 (불변)
- 테스트 시 mock 주입 쉬움
- 순환 의존성 컴파일 타임 발견
- Spring 없이도 테스트 가능
생성자가 하나면 @Autowired 생략 가능 (4.3+).
Lombok 사용:
@Service
@RequiredArgsConstructor // final 필드에 대한 생성자 자동
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
}
Setter 주입 (선택적 의존성)
@Service
public class UserService {
private EmailService emailService;
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
선택적/late binding 필요 시.
Field 주입 (비권장)
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 비권장
}
테스트 어려움, final 불가, 순환 의존 늦게 발견. 가능하면 피하기.
ApplicationContext
Bean 컨테이너 + 추가 기능 (이벤트, 메시지, 환경).
@Autowired
private ApplicationContext context;
User user = context.getBean(User.class);
String[] beanNames = context.getBeanDefinitionNames();
직접 호출은 거의 안 함. Bean으로 주입받음.
Bean 식별: @Qualifier, @Primary
같은 타입 Bean이 여러 개일 때.
@Bean
public DataSource primaryDataSource() { ... }
@Bean
public DataSource secondaryDataSource() { ... }
@Autowired
@Qualifier("primaryDataSource")
private DataSource ds;
// 또는 @Primary
@Bean
@Primary
public DataSource primary() { ... }
@Primary는 기본 선택. 다른 곳에서 @Qualifier 명시 시 그것.
Bean Scope
@Service
@Scope("singleton") // 기본 (애플리케이션 1개)
public class UserService { }
@Component
@Scope("prototype") // 매번 새 인스턴스
public class TempBean { }
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) // HTTP 요청당
public class RequestScopedBean { }
@Component
@Scope("session")
public class SessionBean { }
@Component
@Scope("application") // ServletContext 당 (= singleton과 거의 같음)
public class AppBean { }
@Component
@Scope("websocket")
public class WsBean { }
기본은 singleton. 대부분 그대로.
라이프사이클
@Component
public class MyBean {
@PostConstruct
public void init() {
// 모든 의존성 주입 후 호출
}
@PreDestroy
public void cleanup() {
// 컨테이너 종료 시 호출
}
}
InitializingBean/DisposableBean 인터페이스도 가능하나 어노테이션이 더 깔끔.
@Value: 외부 값 주입
@Service
public class MyService {
@Value("${app.api.key}")
private String apiKey;
@Value("${app.timeout:30}") // 기본값 30
private int timeout;
@Value("${app.tags}") // CSV → List
private List<String> tags;
@Value("#{systemProperties['user.home']}") // SpEL
private String home;
}
application.properties/application.yml의 값. 환경변수도.
더 큰 설정은 @ConfigurationProperties:
@ConfigurationProperties(prefix = "app")
public record AppProperties(
String name,
Api api,
int timeout
) {
public record Api(String url, String key) { }
}
// application.yml
app:
name: myapp
api:
url: https://api.example.com
key: xxx
timeout: 30
@EnableConfigurationProperties(AppProperties.class)
@Configuration
public class Config { }
@Service
public class MyService {
private final AppProperties props;
public MyService(AppProperties props) { this.props = props; }
}
Conditional Bean
@Bean
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public FeatureService featureService() { ... }
@Bean
@ConditionalOnMissingBean
public DefaultService defaultService() { ... }
@Bean
@ConditionalOnClass(SomeClass.class)
public IntegrationBean integration() { ... }
@Bean
@Profile("prod") // 활성 프로파일이 prod일 때
public ProductionConfig prodConfig() { ... }
@Bean
@Profile("!test") // test가 아닐 때
public RealEmailService email() { ... }
Spring Boot 자동 설정의 핵심 메커니즘.
Profile
환경 분리.
# application.yml
spring:
profiles:
active: dev
---
spring:
config:
activate:
on-profile: dev
spring:
datasource:
url: jdbc:h2:mem:devdb
---
spring:
config:
activate:
on-profile: prod
spring:
datasource:
url: ${DATABASE_URL}
실행:
java -jar app.jar --spring.profiles.active=prod
# 또는
SPRING_PROFILES_ACTIVE=prod java -jar app.jar
ObjectProvider (선택적 / Optional / 0..N)
@Service
public class MyService {
private final ObjectProvider<EmailService> emailProvider;
public void notify(User user) {
emailProvider.ifAvailable(email -> email.send(user));
}
}
Bean이 없거나 여러 개일 때 안전 처리.
순환 의존성
@Service
public class A {
public A(B b) { }
}
@Service
public class B {
public B(A a) { } // 순환! 시작 실패
}
해결:
- 설계 재검토 (대부분 책임 분리 문제)
@Lazy주입- ApplicationContext 통해 lookup
2.6+ 부터 순환 의존성 기본 금지. 환경에 따라 명시 허용:
spring.main.allow-circular-references=true # 권장 X
함정
1. field injection의 테스트 어려움
public class UserService {
@Autowired private UserRepository repo; // 어떻게 mock?
// 리플렉션으로 가능하지만 번거로움
}
생성자 주입으로 전환.
2. @ComponentScan 범위
@SpringBootApplication은 자기 패키지와 하위만 스캔. 다른 패키지의 @Component는 안 등록됨.
@SpringBootApplication(scanBasePackages = {"com.example", "com.other"})
3. prototype scope 안의 singleton
@Component
@Scope("singleton")
public class Singleton {
@Autowired
private Prototype prototype; // singleton 만들 때 1번만 주입
}
매번 새 prototype 원하면 ObjectProvider 또는 Provider<T>.
4. @PostConstruct vs @Bean(initMethod=…)
@PostConstruct // 어노테이션 기반
public void init() { ... }
@Bean(initMethod = "init") // XML 호환
public MyBean myBean() { ... }
둘 다 동작. 어노테이션이 깔끔.
5. @Configuration vs @Component for @Bean methods
@Configuration
public class Config {
@Bean MyBean a() { return new MyBean(); }
@Bean MyBean b() { return a(); } // 같은 a() 인스턴스 (proxy)
}
@Component // proxy 없음
public class Config {
@Bean MyBean a() { return new MyBean(); }
@Bean MyBean b() { return a(); } // 새 MyBean (의도와 다름)
}
@Configuration은 CGLIB proxy로 같은 호출엔 같은 인스턴스. @Component는 일반 호출.
관련 위키
이 글의 용어 (8개)
- [Java] JavaBean 프로퍼티 규약java
- 정의 JavaBean 은 다음 세 가지 규약을 만족하는 클래스: 1. public no-args constructor (인자 없는 public 생성자) 2. 프로퍼티 접근자: /…
- [Spring] @Autowired 해결 전략: @Qualifier, @Primary, byTypespring
- 정의 가 의 기초 (생성자/필드/setter 주입) 를 다룬다면, 이 페이지는 같은 타입의 Bean 이 여러 개일 때 어떻게 하나를 골라내는지 의 해결 전략을 다룬다. 기본 알고…
- [Spring] @Conditional, Profile, @ConfigurationPropertiesspring
- 정의 Spring Boot는 조건부 Bean 등록 메커니즘을 제공한다. classpath, property, 다른 Bean 유무 등에 따라 자동 구성을 활성화/비활성화. , 어노…
- [Spring] AOP: @Aspect, Pointcut, Advicespring
- 정의 AOP (Aspect-Oriented Programming)는 cross-cutting concerns(로깅, 트랜잭션, 보안 등)를 비즈니스 코드에서 분리하는 기법. Sp…
- [Spring] Bean Lifecycle: 초기화와 소멸 콜백spring
- 정의 가 Bean 등록과 주입 을 다룬다면, 이 페이지는 등록 이후 생성 → 사용 → 소멸 사이에 끼어드는 콜백을 다룬다. Spring 컨테이너가 단순히 객체를 만들고 끝내는 게…
- [Spring] Bean Scope 심화: Singleton, Prototype, Web Scopespring
- 정의 가 5가지 scope 의 개요 를 다룬다면, 이 페이지는 그 중 함정 (prototype-into-singleton) 과 해결 패턴 (ScopedProxy, ObjectPr…
- [Spring] BeanPostProcessor와 BeanFactoryPostProcessorspring
- 정의 Spring 컨테이너의 2가지 확장점: - BeanFactoryPostProcessor (BFPP): Bean 인스턴스가 만들어지기 전, (메타데이터) 을 수정. 클래스명,…
- [Spring] Stereotype 어노테이션: @Component, @Service, @Repository, @Controllerspring
- 정의 Stereotype 어노테이션 은 클래스가 Spring 컨테이너의 어떤 역할 (role) 인지를 의미적으로 표시한다. 모두 를 메타 어노테이션 으로 가진 specializa…
이 개념을 다룬 위키 페이지 (10)
- wiki[Java] JavaBean 프로퍼티 규약
- wiki[Java] Reflection: Class, Method, Field, MethodHandle
- wiki[Spring] @Autowired 해결 전략: @Qualifier, @Primary, byType
- wiki[Spring] Bean Lifecycle: 초기화와 소멸 콜백
- wiki[Spring] BeanPostProcessor와 BeanFactoryPostProcessor
- wiki[Spring] Bean Scope 심화: Singleton, Prototype, Web Scope
- wiki[Spring Boot] Properties 외부화: PropertySource, Profile, @ConfigurationProperties
- wiki[Spring] Events: @EventListener, @TransactionalEventListener
- wiki[Spring] Reflection 유틸: ReflectionUtils, AnnotationUtils, ResolvableType
- wiki[Spring] Stereotype 어노테이션: @Component, @Service, @Repository, @Controller
💬 댓글