📍 Bean이란?
📍 @Component, @Controller, @Service, @Repository의 차이
@Component, @Controller, @Service, @Repository 들을 알기 위해서는 먼저 Bean에 대해 알아야 합니다.
Spring에서는 객체를 직접 new로 만들지 않고, 대신 Spring 컨테이너가 객체를 만들어서 관리합니다. 이렇게 Spring이 관리하는 객체를 Bean(빈) 이라고 합니다.
우리가 스프링을 사용하지 않으면 아래와 같이 new를 사용해서 객체를 생성합니다.
public class MyService {
...
}
public static void main(String[] args) {
MyService service = new MyService();
}
하지만 스프링에서는
@Service
public class MyService {
// 이 클래스는 Spring이 직접 만들어서 관리해주는 객체가 됩니다.
}
위처럼 @Service와 같은 애노테이션을 붙이면 Spring이 "이 클래스를 내가 관리해야겠다!" 하고 빈으로 등록해줍니다. Spring은 서비스, 리포지토리, 컨트롤러 같은 객체들을 대신 만들어서 메모리에 올려주고,
필요한 곳에 자동으로 넣어줍니다. 이것을 의존성 주입이라고 합니다.
Bean에 대해서는 다른 글에서 자세하게 정리하도록 하겠습니다.
위 네 개의 애노테이션은 모두 Spring Bean으로 등록하라는 표시로, 역할에 따라 구분되어 있습니다.
특정한 역할(Controller, Service, Repository)에 딱 맞지 않는 공통 기능이나 유틸리티 컴포넌트를 만들 때 사용합니다.
@Autowired나 생성자 주입으로 사용 가능예시
@Component
public class PasswordEncoder {
public void send(String message) {
System.out.println("알림 발송: " + message);
}
}
HTTP 요청을 받고, 응답을 리턴하는 클래스에서 사용합니다.
@Controller를 통해 이 클래스가 요청 핸들러라는 걸 인식하고 URL을 매핑해줌@Component만 붙이면 요청을 처리하지 않음 (Spring 6 이후부터 더 엄격해짐)@Controller
public class UserController {
private final UserService userService;
// 생성자는 @AllArgsConstructor, @RequiredArgsConstructor, @NoArgsConstructor로 대체할 수 있습니다.
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/users")
public String signup(@RequestParam String name) {
userService.signup(name);
return "success";
}
}
REST API라면 @RestController를 사용해서 JSON 리턴도 가능합니다.
📌
@RestController는@Controller에@ResponseBody가 결합된 형태로,
메서드가 JSON이나 문자열을 그대로 응답 본문에 담아 리턴합니다.
@RestController
public class ApiController {
@GetMapping("/api/hello")
public String hello() {
return "Hello, world!";
}
}
비즈니스 로직을 처리하는 클래스에서 사용합니다. ex) 포인트 계산, 회원 등급 처리 등
예시
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void signup(String name) {
// 사용자 생성 비즈니스 로직
userRepository.save(new User(name));
}
}
DB랑 직접 통신하는 클래스에서 사용합니다.
예시
@Repository
public class UserRepository {
private final EntityManager em;
public UserRepository(EntityManager em) {
this.em = em;
}
public void save(User user) {
em.persist(user);
}
}
이론상 가능하지만 결론부터 말하면 네 안됩니다!
Spring 6(Spring Boot 3) 이전 버전에서는 @Component + @RequestMapping으로도 Bean 및 핸들러로 등록되었습니다. 하지만 Spring 6 이후 부터 @Controller 외에는 핸들러로 등록하지 않아 @Component를 붙이면 웹 요청을 정상적으로 수행할 수 없습니다.
// 이렇게 하면 동작하지 않습니다. (Spring 6부터)
@Component
public class MyController {
@GetMapping("/hello")
public String hello() { return "hi"; }
}
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
...
@Override
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class); // 컨트롤러 애너테이션인지 확인
}
...
}
DB 관련 예외가 Spring의 통합 예외 처리(DataAccessException)로 변환되지 않습니다.
Spring은 @Repository를 보면 예외를 감싸서 알려주는데, 이게 없으면 DB 오류가 생겼을 때 일관되게 처리하지 못합니다.
AOP는 포인트컷을 정의할 때 "@Service가 붙은 클래스에만 적용해" 이런 식으로 계층 구분을 사용합니다. 전부 @Component만 쓰면 이런 구분이 안 되어서 AOP도 제대로 적용하기 어렵습니다.
| 애노테이션 | 역할 | @Component로 대체 시 문제점 |
|---|---|---|
| @Controller | HTTP 요청 처리 | 요청 핸들러로 인식되지 않음 (Spring 6부터) |
| @Service | 비즈니스 로직 | AOP 포인트컷 적용 어려움 |
| @Repository | DB 연동 | 예외 변환 기능 사라짐 |
하나 배웠습니다