Spring 컨테이너에서 빈을 구성하는 방법은 3가지가 있다.
1. XML을 사용하여 직접 빈 선언
<bean id="userService" class="com.example.service.UserServiceImpl">
<property name="userRepository" ref="userRepository"/>
</bean>
2. 클래스에 `@Bean` 애노테이션을 사용하여 빈 선언
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl(userRepository());
}
}
3. 컴포넌트 스캔을 사용하여 빈 선언
@Component
public class UserServiceImpl implements UserService {
// 구현 내용
}
최근 Spring에선 컴포넌트 스캔을 주로 사용하고 Spring boot는 컴포넌트 스캔을 기본으로 한다. 컴포넌트 스캔에서 빈으로 등록하기 위해선 `@Component`, `@Controller`, `@Service`, `@Repository`, `@Configuration`등의 애노테이션을 사용한다.
@ComponentScan
Spring은 `@ComponentScan`을 통해 패키지에서 빈을 자동으로 스캔할 수 있다.
@ComponentScan(
basePackages = "com.example",
includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Stub.*"),
excludeFilters = @ComponentScan.Filter(Repository.class)
)
- `basePackages`: 어떤 패키지를 스캔할지 정의
- `includeFilters` / `excludeFilters`: 특정 타입의 빈만 포함하거나 제외
- `useDefaultFilters`: 기본 필터( `@Component`, `@Controller`, `@Service`, `@Repository`)를 사용할지 여부를 지정, 기본 값은 `true`
- `lazyInit`: 빈의 지연 초기화 여부를 설정
동작 방식
- 지정된 패키지와 하위 패키지에서 `@Component`, `@Controller`, `@Service`, `@Repository`등의 스테레오타입 애노테이션이 붙은 클래스를 찾는다.
- 발견한 클래스들을 Spring 컨테이너에 빈으로 등록한다.
Spring Boot에서의 ComponentScan
Spring Boot는 `@SpringBootApplication`애노테이션 내부에 이미 `@ComponentScan`이 포함되어 있다. 따라서, 별도 설정 없이도 해당 클래스가 위치한 패키지에서 자동으로 컴포넌트 스캔을 수행한다.
@SpringBootApplication // 내부적으로 @ComponentScan을 포함
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@Component
`@Component`는 Spring에서 관리되는 객체인 빈을 정의하는 가장 기본적인 애노테이션이다. Spring은 이 애노테이션이 붙은 클래스를 찾아 Spring 컨테이너(IoC 컨테이너)에 빈으로 등록한다.
@Component
public class EmailValidator {
public boolean isValid(String email) {
return email != null && email.contains("@");
}
}
빈 이름을 설정하는 방법은 두 가지이다.
- 기본 이름: 클래스 이름의 첫 글자를 소문자로 바꾼 이름, `CarUtility` 클래스 ➡️ `carUtility` 빈 이름
- 커스텀 이름: `value` 속성 사용
@Component("vehicleUtils")
class CarUtility {
// ...
}
이 경우 빈 이름은 `vehicleUtils`가 된다.
@Component 계층 구조
`@Component`는 일반적으로 Spring 컴포넌트를 나타내지만, Spring에선 특정 역할을 더 명확히 표현하는 애노테이션들을 제공한다. 여기엔, `@Controller`, `@Service`, `@Repository`, `@Configuration`등이 있다.
- `@Repository`: 데이터 액세스 계층(DAO)을 나타낸다.
- `@Service`: 비즈니스 로직 계층을 나타낸다. `@Component`와 기능적으로 동일하지만, 향후 특별한 기능이 추가될 수 있다.
- `@Controller`: 웹 컨트롤러(Spring MVC)를 나타낸다.. `@RequestMapping` 애노테이션을 처리할 수 있다.
- `@Configuration`: 빈 정의 소스를 나타낸다. `@Bean` 메서드를 포함하는 클래스에 사용된다.
각 애노테이션들은 내부적으로 `@Component`를 포함하고 있어, 컴포넌트 스캔 대상이 된다.
@Repository
`@Repository`는 데이터베이스에 접근하는 DAO(Data Access Object) 클래스를 나타내는 애노테이션이다. 이는 데이터 액세스 계층의 빈이라고 명시한다.
@Repository
public class JpaUserRepository implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public User findById(Long id) {
return entityManager.find(User.class, id);
}
}
데이터베이스나 ORM 프레임워크마다 서로 다른 예외 타입을 발생시킨다. 예를 들어:
- JDBC: `SQLException`
- Hibernate: `HibernateException`
- JPA: `PersistenceException`
이렇게 서로 다른 예외들은 애플리케이션 코드가 특정 DAO에 종속되게 만들고, 스택 변경 시 많은 코드 수정이 필요하다.
하지만 `@Repository`는 이러한 다양한 영속성 기술의 예외를 Spring의 통합된 예외 계층인 `DataAccessException`으로 자동 변환해 준다. 이를 통해 다양한 예외를 동일한 방식으로 처리할 수 있다. ➡️ 다형성
이 예외 변환을 활성화하기 위해선 `PersistenceExceptionTranslationPostProcessor`빈을 선언해야 한다. `PersistenceExceptionTranslationPostProcessor` 는 `@Repository`애노테이션이 붙은 빈을 감지하고, 이 빈들에서 발생하는 영속성 관련 네이티브 예외를 가로채어 Spring의 `DataAccessException`계층으로 변환하는 역할을 한다.
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
💡Spring boot에서는 `PersistenceExceptionTranslationPostProcessor`가 자동으로 구성되므로, 별도로 빈을 등록할 필요가 없다. `@SpringBootApplication`을 사용하면 자동으로 설정된다.
@Repository
public class JpaUserRepository implements UserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public User findById(Long id) {
try {
return entityManager.find(User.class, id);
} catch (EntityNotFoundException ex) {
// JPA 예외가 발생하면 Spring이 자동으로 변환
// 이 예외는 실제로 EmptyResultDataAccessException으로 변환됩니다
throw ex; // 변환은 자동으로 일어납니다
}
}
}
위 코드에서 `EntityNotFoundException`은 Spring에 의해 자동으로 `EmptyResultDataAccessException`으로 변환된다. 서비스 계층에선 특정 JPA예외가 아닌 Spring의 예외를 처리하게 된다.
@Service
`@Service`는 Spring에서 비즈니스 로직을 처리하는 서비스 계층의 컴포넌트를 나타내는 애노테이션이다. 이는 `@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;
}
// 사용자 등록 비즈니스 로직
@Transactional
public User registerUser(UserDto userDto) {
// 중복 사용자 검사
if (userRepository.existsByEmail(userDto.getEmail())) {
throw new DuplicateUserException("이미 등록된 이메일입니다: " + userDto.getEmail());
}
// 사용자 엔티티 생성 및 저장
User user = new User();
user.setEmail(userDto.getEmail());
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
User savedUser = userRepository.save(user);
// 환영 이메일 발송
emailService.sendWelcomeEmail(savedUser.getEmail());
return savedUser;
}
}
서비스 계층은
- 비즈니스 로직 캡슐화: 애플리케이션의 핵심 기능을 구현
- 트랜잭션 관리: 데이터의 일관성을 유지하기 위한 트랜잭션을 관리
`@Transactional`을 사용해 성공적으로 완료하면 커밋, 예외가 발생하면 롤백 - 계층 간 인터페이스: 컨트롤러와 레포지토리 사이를 연결
와 같은 역할을 한다.
@Controller
`@Controller`는 `@Component`의 특수화된 형태로, HTTP 요청을 처리하고 응답을 반환하는 역할을 한다. Spring MVC 에서 컨트롤러 역할을 한다고 명시한다. Spring의 컴포넌트 스캔을 통해 자동으로 빈으로 등록되며, 웹 요청을 처리하는 핸들러 메서드를 포함한다. ➡️ 웹 요청과 비즈니스 로직 사이의 연결 고리
@Controller
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/users")
public String listUsers(Model model) {
List<User> users = userService.getAllUsers();
model.addAttribute("users", users);
return "user/list"; // 뷰 이름 반환
}
@GetMapping("/users/{id}")
public String userDetails(@PathVariable Long id, Model model) {
User user = userService.getUserById(id);
model.addAttribute("user", user);
return "user/details";
}
@PostMapping("/users")
public String createUser(@Valid @ModelAttribute("user") UserForm form,
BindingResult result) {
if (result.hasErrors()) {
return "user/form"; // 검증 에러시 폼으로 돌아감
}
userService.createUser(form);
return "redirect:/users"; // 생성 후 목록 페이지로 리다이렉트
}
}
동작 방식
- `DispatcherServlet`이 Client의 URL 형식인 요청 수신
- `HandlerMapping`이 적절한 `@Controller` 클래스의 핸들러 메서드를 찾아 매핑
- `DispatcherServlet`은 해당 메서드를 호출
- 선택한 `@Controller`클래스의 메서드에게 `HandlerAdapter`가 역할 위임
- Controller는 받은 요청을 Service단에서 처리하고 `View Name`을 반환
- `DispaterServlet`은 반환한 `View name`을 `ViewResolver`를 통해 실제 View 구현체를 찾아 반환

출처: https://mangkyu.tistory.com/49
@Configuration
`@Configuration`은 해당 클래스가 `@Bean`을 정의하는 설정 클래스라는 것을 명시하는 애노테이션이다.
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl(userRepository());
}
@Bean
public UserRepository userRepository() {
return new JpaUserRepository();
}
@Bean
public EmailService emailService() {
EmailServiceImpl service = new EmailServiceImpl();
service.setHost("smtp.example.com");
service.setPort(587);
return service;
}
}
@Configuration과 프록시
Spring은 `@Configuration`클래스를 CGLIB 프록시로 감싸 `@Bean`메서드가 여러 번 호출되더라도 항상 같은 `@Bean` 인스턴스를 반환하도록 보장한다.
@Configuration
public class AppConfig {
@Bean
public Service1 service1() {
return new Service1Impl();
}
@Bean
public Service2 service2() {
// service1()을 여러 번 호출해도 항상 같은 인스턴스 반환
return new Service2Impl(service1());
}
@Bean
public Service3 service3() {
// 여기서도 같은 service1() 인스턴스 사용
return new Service3Impl(service1());
}
}
이 예시에선 `service1()`메서드는 내부적으로 여러 번 호출되지만, Spring의 CGLIB 프록시는 실제로 한 번만 호출되고 그 결과를 캐싱하여 재사용한다. 이를 통해 싱글톤 스코프의 빈이 정확히 한 번만 인스턴스화되도록 보장한다.
💡`@Component` 역시 `@Bean`메서드를 포함할 수 있다. 하지만, CGLIB 프록시가 적용되지 않아 `@Component`의 `@Bean`메서드는 호출될 때마다 새 인스턴스가 생성될 수 있다.
하지만 `@Configuration`클래스는 CGLIB 프록시를 사용하기 때문에 싱글톤을 보장한다.
Summary
- `@Component`: 특정 계층에 속하지 않는 일반적인 스프링 관리 컴포넌트에 사용
- 예: 유틸리티 클래스, 팩토리 클래스 등
- `@Repository`: 데이터 액세스 계층의 클래스에 사용
- 예: JPA 레포지토리, JDBC DAO 등
- 데이터 액세스 예외 변환 기능을 활용
- `@Service`: 비즈니스 로직을 포함하는 서비스 계층의 클래스에 사용
- 예: 트랜잭션 관리, 비즈니스 규칙 적용 등을 담당하는 클래스
- `@Controller`: 웹 요청을 처리하는 프레젠테이션 계층의 클래스에 사용
- 예: Spring MVC 컨트롤러
- `@Configuration`: `@Bean` 정의를 포함하는 설정 클래스에 사용
- 예: 애플리케이션 구성, 외부 라이브러리 통합 등
'Spring' 카테고리의 다른 글
| [Spring] 간단한 DTO ↔ Entity 변환에도 Mapper가 필요할까? (1) | 2025.08.19 |
|---|---|
| [Spring] @ExceptionHandler와 @ControllerAdvice에 관하여 (1) | 2025.07.14 |
| [Spring] SSR과 CSR에서 Session 데이터 접근 방식의 차이 (1) | 2025.06.08 |
| [Spring] @Controller vs @RestController (0) | 2024.09.27 |