Spring_07) Spring Boot : Bean
2025. 11. 4. 08:58

들어가며

Spring Boot를 사용하다 보면 @Controller, @Service, @Repository 같은 어노테이션을 자주 접하게 됩니다. 이들은 모두 Spring Container에 Bean으로 등록하기 위한 어노테이션입니다. 이번 포스팅에서는 실제 프로젝트 코드를 바탕으로 Spring Boot Bean의 개념과 사용법을 자세히 알아보겠습니다.


1. Bean이란?

Bean은 Spring Container가 관리하는 객체입니다. Spring은 애플리케이션을 실행할 때 이러한 Bean들을 생성하고 관리하며, 필요할 때 다른 객체에 주입(Dependency Injection)해줍니다.

Bean의 장점

  1. 의존성 관리의 자동화: 객체 생성과 관리를 Spring이 담당
  2. 생명주기 관리: 객체의 생성부터 소멸까지 Spring이 관리
  3. 싱글톤 패턴: 기본적으로 하나의 인스턴스만 생성하여 메모리 효율성 향상
  4. 느슨한 결합: 인터페이스를 통한 의존성 주입으로 유연한 구조

2. 주요 Bean 등록 어노테이션

2.1 @Controller

@Controller는 웹 요청을 처리하는 컨트롤러 클래스를 Bean으로 등록하는 어노테이션입니다. @Component의 특수한 형태로, 웹 계층에서 사용됩니다.

실제 프로젝트 예시:

@Controller
public class BoardController {
    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

위 코드에서 BoardController@Controller 어노테이션으로 Spring Container에 Bean으로 등록됩니다. 생성자 주입을 통해 BoardService도 자동으로 주입받습니다.

주요 특징:

  • HTTP 요청을 처리하는 메서드를 정의
  • @GetMapping, @PostMapping 등의 어노테이션과 함께 사용
  • 뷰 이름을 반환하거나 ModelAndView 객체를 반환

사용 예시:

    // 게시글 목록
    @GetMapping("list.bo")
    public ModelAndView listBoard(@RequestParam(value = "cpage", required = false) Integer cpage, ModelAndView mv){
        int currentPage = cpage != null ? cpage : 1;
        int listCount = boardService.selectAllBoardCount();
        PageInfo pi = new PageInfo(currentPage, listCount, 5, 5);
        ArrayList<Board> list = boardService.selectAllBoard(pi);
        mv.addObject("list", list);
        mv.addObject("pi", pi);
        mv.setViewName("board/listView");
        return mv;
    }

2.2 @Service

@Service는 비즈니스 로직을 처리하는 서비스 계층 클래스를 Bean으로 등록하는 어노테이션입니다.

실제 프로젝트 예시:

@Service
public class BoardServiceImpl implements BoardService {
    private final BoardMapper boardMapper;

    public BoardServiceImpl(BoardMapper boardMapper) {
        this.boardMapper = boardMapper;
    }

BoardServiceImpl@Service 어노테이션으로 Bean에 등록되며, BoardMapper를 생성자 주입으로 받습니다.

주요 특징:

  • 비즈니스 로직을 처리하는 계층
  • 트랜잭션 관리를 위해 @Transactional과 함께 자주 사용
  • Controller와 Mapper 사이의 중간 계층 역할

트랜잭션 처리 예시:

    @Override
    @Transactional // service 함수에 대해서 트랙잭션 처리를 한번에 진행을 하겠다는 어노테이션
    public int insertBoard(Board b, Attachment at) {
        int result = boardMapper.insertBoard(b);

        if(at != null) {
            result *= boardMapper.insertAttachment(at);
        }

        return result;
    }

@Transactional 어노테이션을 사용하면 여러 데이터베이스 작업을 하나의 트랜잭션으로 묶어 처리할 수 있습니다.

2.3 @Mapper

@Mapper는 MyBatis에서 사용하는 어노테이션으로, 데이터베이스 접근을 담당하는 인터페이스를 Bean으로 등록합니다.

실제 프로젝트 예시:

@Mapper
public interface BoardMapper {

MyBatis는 @Mapper가 붙은 인터페이스를 자동으로 구현체를 생성하여 Bean으로 등록합니다.

2.4 @Configuration과 @Bean

@Configuration은 설정 클래스를 의미하며, @Bean은 메서드 단위로 Bean을 등록할 때 사용합니다.

실제 프로젝트 예시:

@Configuration    // 해당 객체는 설정을 위한 객체임을 명시
public class securityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http.csrf(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable);
        return http.build();
    }

    /*
        매서드 단위로 특정 객체를 만들어 반환하는 형태의 빈 등록 어노테이션
        BCryptPasswordEncoder 객체를 스프링 빈에 등록해서 사용하고 싶다.
        다만 외부객체이므로 class에 직접 @Component를 기술할 수 없어서
        해당 객체를 만들어 반환하는 함수 자체를 Bean에 등록하여 필요 시 스프링이 만들어 전달할 수 있게 한다.
     */
    @Bean
    public BCryptPasswordEncoder BCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

주요 특징:

  • 외부 라이브러리 클래스를 Bean으로 등록할 때 사용
  • 메서드가 반환하는 객체가 Bean으로 등록됨
  • 설정 클래스에서 여러 Bean을 등록할 수 있음

3. 의존성 주입 (Dependency Injection)

3.1 생성자 주입 (권장)

생성자 주입은 가장 권장되는 의존성 주입 방식입니다.

실제 프로젝트 예시:

    @Autowired // (생성자 주입 방식) MemberController 생성자가 실행될 때 의존성 주입을 해준다.
    public MemberController(MemberService memberService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.memberService = memberService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

장점:

  • 불변성 보장: final 키워드와 함께 사용하여 필드 값 변경 방지
  • 테스트 용이: 테스트 시 Mock 객체를 쉽게 주입 가능
  • 순환 참조 방지: 컴파일 타임에 순환 참조를 감지 가능

3.2 필드 주입 (비권장)

필드에 직접 @Autowired를 붙이는 방식입니다.

@Autowired
private MemberService memberService;

단점:

  • 테스트가 어려움
  • 불변성을 보장하지 못함
  • 순환 참조를 런타임에만 발견 가능

4. Bean의 생명주기

Spring Container가 Bean을 관리하는 과정:

  1. Bean 등록: 어노테이션 스캔으로 Bean 등록
  2. 의존성 주입: 필요한 다른 Bean들을 주입
  3. 초기화: @PostConstruct 메서드 실행 (있다면)
  4. 사용: 애플리케이션에서 Bean 사용
  5. 소멸: @PreDestroy 메서드 실행 후 Bean 제거

5. 실제 프로젝트에서의 Bean 활용 패턴

5.1 Controller → Service → Mapper 구조

@Controller
public class BoardController {
    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }
@Service
public class BoardServiceImpl implements BoardService {
    private final BoardMapper boardMapper;

    public BoardServiceImpl(BoardMapper boardMapper) {
        this.boardMapper = boardMapper;
    }

이렇게 각 계층이 Bean으로 등록되어 의존성 주입이 이루어집니다.

5.2 설정 클래스에서의 Bean 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {

    /*
        인터셉터를 등록하는 메서드
        여러개의 인터페이스를 등록할 수 있고, 등록 순서대로 실행이 된다.
        InterceptorRegistry를 통해 인터셉터를 등록한다.
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginCheckInterceptor())
                .addPathPatterns(  // 특정 경로에 인터셉터를 동작시키겠다.
                        "/**"
                ).excludePathPatterns(  // 다만 이 경로들은 인터셉터의 적용에서 제외해야겠다.
                      "/",
                      "/login.me",
                      "/enrollForm.me",
                      "/idDulpicateCheck.me",
                      "/insert.me",
                      "/list.bo",
                      "/detail.bo",
                      "/List.th",
                      "/detail.th",
                      "/static/**"
                );
    }
}

WebConfig@Configuration으로 Bean에 등록되며, Spring MVC 설정을 커스터마이징합니다.


6. Bean 스캔 범위

Spring Boot는 기본적으로 메인 애플리케이션 클래스가 있는 패키지와 그 하위 패키지를 스캔합니다.

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@SpringBootApplication은 다음 세 가지를 포함합니다:

  • @Configuration: 설정 클래스
  • @EnableAutoConfiguration: 자동 설정
  • @ComponentScan: 컴포넌트 스캔

7. 실전 팁

7.1 final 키워드 사용

생성자 주입 시 final 키워드를 사용하면 불변성을 보장할 수 있습니다.

    private final MemberService memberService;  // final을 사용하여 불변성을 보장?
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

7.2 @Autowired 생략 가능

Spring 4.3부터 생성자가 하나만 있을 때는 @Autowired를 생략할 수 있습니다.

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

7.3 트랜잭션 관리

서비스 계층에서 @Transactional을 사용하여 트랜잭션을 관리합니다.

    @Override
    @Transactional // service 함수에 대해서 트랙잭션 처리를 한번에 진행을 하겠다는 어노테이션
    public int insertBoard(Board b, Attachment at) {
        int result = boardMapper.insertBoard(b);

        if(at != null) {
            result *= boardMapper.insertAttachment(at);
        }

        return result;
    }

마무리

Spring Boot Bean은 Spring의 핵심 개념 중 하나입니다. 실제 프로젝트에서 @Controller, @Service, @Mapper, @Configuration, @Bean 등을 활용하여 계층별로 Bean을 등록하고, 생성자 주입을 통해 의존성을 관리하는 패턴이 일반적입니다.

Bean을 올바르게 활용하면 코드의 재사용성과 테스트 용이성이 크게 향상되며, 유지보수성이 높은 애플리케이션을 개발할 수 있습니다.


참고 자료:

  • Spring Boot 공식 문서