Published on
👁️

Spring Framework

Authors
  • avatar
    Name
    River
    Twitter
16Spring과 Spring Boot의 차이를 말해주세요.쉬움
Spring Boot는 Spring Framework 위에 구축된 편의 도구로, 4가지 핵심 기능을 추가로 제공합니다. 자동 설정으로 외부 라이브러리에 대한 복잡한 Bean 설정을 application.yml 설정 파일로 간소화하고, 스타터 의존성으로 버전 호환성을 보장하며 쉽게 관련 의존성을 추가할 수 있습니다. 또한 내장 서버로 별도 서버 설치 없이 jar 실행이 가능하며, DevTools나 Actuator 같은 개발 편의 기능을 제공합니다
상세 설명

Spring Framework

  • Java 기반 엔터프라이즈 애플리케이션 개발을 위한 포괄적인 프레임워크
    • 엔터프라이즈 애플리케이션 = 기업용 대규모 시스템
      • 은행, 쇼핑몰, 병원 시스템 등

  • 핵심 기능
    • IoC (Inversion of Control)
    • DI (Dependency Injection)
    • AOP (Aspect-Oriented Programming)

  • 높은 유연성과 확장성을 제공하지만, 초기 설정이 복잡하고 러닝 커브가 높다.
  • Spring Framework는 아주 오래되고 많이 사용되는 Framework로 Jenkins, Docker, Kubernetes 등 배포 도구들과의 연동이 잘 되어있다.

Spring Boot

  • Spring Framework + Tomcat 내장 서버
  • Spring Boot는 Spring Framework 대체 프레임워크가 아니라, Spring Framework 위에 구축된 도구
    • Spring Framework의 기능들을 사용하되, 복잡한 설정과 시작 과정을 단순화한 것으로

      빠른 개발과 배포를 위한 도구와 기능들을 제공한다.


참고
  • 핵심 기능
    • 자동 설정 (Auto Configuration)
    • 스타터 의존성 (Starter Dependencies)
    • 내장 서버 (Embedded Server)
    • 편의 기능 (DevTools, Actuator 등)

자동 설정 (Auto Configuration)

  • Spring

    @Configuration
    @EnableWebMvc
    @EnableTransactionManagement
    @ComponentScan(basePackages = "com.example")
    public class WebConfig implements WebMvcConfigurer {
        @Bean
        public DataSource dataSource() {
            HikariDataSource dataSource = new HikariDataSource();
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db");
            dataSource.setUsername("user");
            dataSource.setPassword("password");
            dataSource.setMaximumPoolSize(20);
            // 수십 줄의 설정...
            return dataSource;
        }
    }
    
    • Spring Framework는 복잡한 수동 설정이 필요하다.


  • Spring Boot

    • ClassPath를 기반으로 필요한 Bean들을 자동 등록
    • @SpringBootApplication 에노테이션으로 간단하게 활성화
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/db
    
    • application.yml or application.properties만 있으면 된다

내장 서버 (Embedded Server)

  • Spring
    1. Tomcat 서버 별도 설치
    2. WAR 파일 생성
    3. Tomcat에 배포
    4. 서버 시작

  • Spring Boot
    • Tomcat, Jetty, Undertow를 내장하여 별도 서버 설치 불필요
    • jar 파일 하나로 어디서든 실행 가능

스타터 의존성 (Starter Dependencies)

  • Spring

    dependencies {
        implementation 'org.springframework:spring-core:5.3.21'
        implementation 'org.springframework:spring-web:5.3.21'
        implementation 'org.springframework:spring-webmvc:5.3.21'
    }
    
    • Spring은 라이브러리 하나씩 의존성이 되어 있다.


  • Spring Boot의 스타터 의존성 (Starter Dependencies)
    • 관련된 라이브러리들을 묶어서 제공

      ⇒ 버전 충돌 걱정 없이 기능 단위로 의존성 관리

    • spring-boot-starter-web, spring-boot-starter-data-jpa

      dependencies {
          implementation 'org.springframework.boot:spring-boot-starter-web'
      }
      
      • 이 하나에 core, beans, context, web, webmvc 모두 포함!

Actuator

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  • 애플리케이션 상태 모니터링 REST API 제공
  • 복잡한 설정 없이 의존성만 추가하면 바로 사용 가능

DevTools

  • Spring Boot 전용 (Spring Framework에는 없음)
  • 코드 수정 시 자동 재시작
  • 브라우저 자동 새로고침
  • 템플릿 캐시 비활성화

@SpringBootApplication 에노테이션

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
  • @SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan
  • @Configuration
    • 설정 클래스라는 표시
  • @EnableAutoConfiguration
    • 자동 설정 활성화 ⇒ ClassPath의 외부 라이브러리를 보고 자동으로 Bean 생성
  • @ComponentScan
    • 내 클래스 파일에서 @Component 등 스캔해서 Bean 등록

주요 차이점

항목Spring FrameworkSpring Boot
설정 방식XML or Java Config로 모든 Bean 직접 정의application.yml + 자동 설정
의존성 관리개별 라이브러리 + 버전 충돌 관리스타터로 호환 버전 일괄 제공
서버 실행외부 Tomcat + WAR 배포내장 서버 + JAR 실행
개발 도구별도 구성 필요DevTools, Actuator 기본 제공
프로젝트 시작수십 개 설정 파일 작성spring initializr로 즉시 시작
17Spring에서 객체를 Bean으로 관리하는 이유를 설명해주세요.보통
Spring에서 객체를 Bean으로 관리하는 이유는 IoC 컨테이너를 통해 객체의 생명주기를 자동으로 관리하고, 의존성 주입을 통해 객체 간의 결합도를 낮추기 위함입니다. 이를 통해 개발자는 객체 생성과 관리에 신경 쓰지 않고 비즈니스 로직에만 집중할 수 있습니다. 또한 Bean은 기본적으로 싱글톤 패턴으로 관리되어 메모리 효율성을 확보할 수 있습니다. 설계 관점에서는 컨테이너가 의존성 주입을 담당함으로써 SOLID 원칙 중 의존성 역전 원칙(DIP)를 준수할 수 있습니다. 뿐만 아니라 Bean으로 관리되는 객체들은 IoC 컨테이너가 프록시를 생성하여 AOP, 트랜잭션 관리, 캐싱 등의 부가 기능을 자동으로 적용할 수 있게 해줍니다.
상세 설명

Spring Bean이란?

  • Spring IoC 컨테이너에 의해 관리되는 객체

  • 컨테이너가 Bean의 생명주기(생성, 초기화, 소멸)를 담당

  • 애플리케이션의 핵심 구성 요소들을 Spring이 관리하는 형태

  • 내부 클래스 파일은 @Component, @Service, @Repository, @Controller 등으로 등록하고

    외부 라이브러리의 경우 @Configuration@Bean 메서드로 등록

객체를 Bean으로 관리하는 이유

  1. 객체 생명주기 자동 관리

    @Component
    public class DatabaseConnection {
    
        @PostConstruct
        public void init() {
            // 초기화 로직 (컨테이너가 자동 호출)
        }
    
        
        @PreDestroy
        public void cleanup() {
            // 정리 로직 (컨테이너가 자동 호출)
        }
    }
    
    • 개발자가 직접 객체를 생성하고 소멸시킬 필요가 없다
    • 애플리케이션 시작/종료 시 자동으로 초기화/정리 작업 수행


  2. 의존성 주입 (Dependency Injection, DI)

    @Service
    public class UserService {
        private final UserRepository userRepository;
        
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
    • 객체 간의 의존 관계를 Spring이 자동으로 설정
      • Spring이 자동 주입
    • 결합도 감소, 테스트 용이성 향상


  3. 싱글톤 패턴 자동 적용

    @Component
    public class ConfigurationManager {
        // Spring이 기본적으로 싱글톤으로 관리
        // 애플리케이션 전체에서 하나의 인스턴스만 존재
    }
    
    • 기본적으로 싱글톤 스코프로 관리되어 메모리 효율성 제공
    • 여러 곳에서 동일한 인스턴스 사용 보장


  4. AOP (Aspect-Oriented Programming) 적용

    @Service
    @Transactional
    public class OrderService {
        public void processOrder(Order order) {
            // 비즈니스 로직만 작성
            // 트랜잭션 시작/커밋/롤백은 Spring이 자동 처리
        }
    }
    
    • Bean으로 관리되는 객체에만 AOP 적용 가능
    • @Transactional ⇒ Spring이 트랜잭션 프록시 생성
      • 트랜잭션 시작/커밋/롤백은 Spring이 자동 처리
      • 트랜잭션, 보안, 로깅 등을 투명하게 적용


  5. 프록시를 통한 기능 확장

    @Component
    public class CacheableService {
        @Cacheable("users")
        public User findUser(Long id) {
            // 실제 조회 로직
            return userRepository.findById(id);
        }
    }
    
    • @Cacheable("users") ⇒ Spring이 캐싱 프록시 생성

중요
  • Spring에서 Bean으로 관리한다는 것은 단순히 객체를 생성하는 것이 아니라, 의존성 주입, 생명주기 관리, AOP 적용 등 Spring 프레임워크의 모든 기능을 활용할 수 있게 하는 것이다. 이를 통해 개발자는 비즈니스 로직에만 집중할 수 있다.

Bean Scope와 Lifecycle

  • 다양한 Bean Scope 지원

    @Component
    @Scope("singleton")  *// 기본값, 애플리케이션당 하나*
    public class SingletonBean { }
    
    @Component
    @Scope("prototype")  *// 요청할 때마다 새 인스턴스*
    public class PrototypeBean { }
    
    @Component
    @Scope("request")    *// HTTP 요청당 하나 (웹 환경)*
    public class RequestScopedBean { }
    
    • @Scope("singleton")
      • default 값으로 생략 시 기본이 싱글톤 스코프이다.
    • 이러한 스코프에 따라 생명주기가 달라진다.


  • Bean 생명주기 콜백

    @Component
    public class LifecycleBean {
        *// 1. 생성자 호출*
        public LifecycleBean() {
            System.out.println("1. 생성자 호출");
        }
        
        *// 2. 의존성 주입 완료 후*
        @PostConstruct
        public void init() {
            System.out.println("2. 초기화 메서드 호출");
        }
        
        *// 3. 컨테이너 종료 시 (애플리케이션 종료 시)*
        @PreDestroy
        public void destroy() {
            System.out.println("3. 소멸 메서드 호출");
        }
    }
    
    • @Component이므로 기본이 싱글톤 스코프이다.
      • 이 경우 생성자, init(), destroy() 딱 1번만 실행한다.
      1. 애플리케이션 시작 시, 생성자가 호출되고
      2. 이후 의존성 주입 완료 후, 초기화 메서드가 호출된다.
      3. 애플리케이션 실행
      4. 애플리케이션 종료 직전 destroy()가 실행된다.
      5. 애플리케이션 종료
    • 콜백이란, Spring이 특정 시점에 자동으로 호출해주는 메서드

  • Scope
    • Bean이 몇 개까지 만들어지고, 언제까지 살아있는지 정하는 규칙
    • 즉, Singleton Scope인 경우 애플리케이션 전체에서 하나의 객체만 만들도록 설정하는 것

Bean 관리의 실무적 이점

  • 설정의 중앙화
    @Configuration
    public class AppConfig {
        @Bean
        @Profile("dev")
        public DataSource devDataSource() {
            return new H2DataSource();
        }
        
        @Bean
        @Profile("prod")
        public DataSource prodDataSource() {
            return new PostgreSQLDataSource();
        }
    }
    
    • 환경별 설정을 중앙에서 관리
    • 프로파일에 따른 다른 Bean 주입 가능


  • 테스트 지원 (중요!)
    @Service
    public class UserService {
        private final UserRepository userRepository;
        
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
        
        public User createUser(String name) {
            return userRepository.save(new User(name));
        }
    }
    
    
    
    @SpringBootTest
    class UserServiceTest {
    
        @MockBean
        private UserRepository userRepository;  // Mock Bean
        
        @Autowired
        private UserService userService;  
        
        @Test
        void testCreateUser() {
            when(userRepository.save(any())).thenReturn(savedUser);
            // 테스트 수행
        }
    }
    
    • 예를 들어, Bean으로 관리되는 Service를 테스트하는 경우, 테스트 시 쉽게 Mock Bean으로 쉽게 교체할 수 있다.
    • 즉, @MockBean을 설정만 하면 @Autowired 시 Mock Repository가 주입된 UserService 객체가 들어온다.


  • 모니터링과 관리
    @Component
    public class HealthChecker {
        @EventListener
        public void handleContextRefresh(ContextRefreshedEvent event) {
            System.out.println("애플리케이션 컨텍스트 초기화 완료");
        }
    }
    
    • Spring Boot Actuator를 통한 Bean 정보 조회
    • 애플리케이션 상태 모니터링

Bean으로 관리하지 않는 객체들

  • 일반적인 데이터 객체 (ex. DTO, Entity)
    @Entity
    public class User {
        private String email;
    
    • Bean으로 관리하지 않음


  • Utility 클래스의 static 메서드
    public class StringUtils {
        public static boolean isEmpty(String str) {
            return str == null || str.isEmpty();
        }
    }
    
    • Bean으로 관리할 필요가 없다.
18의존성 주입이란 무엇인가요?보통
의존성 주입(Dependency Injection)은 객체가 필요로 하는 의존성을 외부에서 주입받는 디자인 패턴입니다. 이를 통해 객체 간의 결합도를 낮추고, 코드의 유연성과 테스트 용이성을 높일 수 있습니다. Spring에서는 IoC 컨테이너가 객체의 생성과 의존성 주입을 관리하며, @Autowired 애노테이션이나 생성자를 통해 의존성을 주입할 수 있습니다. 또한 인터페이스를 활용하면 코드 변경 없이 다른 구현체로 쉽게 교체할 수 있습니다.
상세 설명

의존성 주입(Dependency Injection)이란?

  • 객체가 필요로 하는 의존성(다른 객체)을 외부에서 주입 받는 디자인 패턴
  • 객체가 직접 의존성을 생성하는 대신, 외부(컨테이너)에서 생성된 객체를 주입 받는 것
  • 제어의 역전(IoC, Inversion of Control)의 구체적인 구현 방법 중 하나
  • 객체 간의 결합도를 낮추고, 코드의 유연성과 테스트 용이성을 향상

의존성 주입(DI) 전후 차이

  • 이전
    public class OrderService {
    
        private EmailSender emailSender = new EmailSender();  // 직접 생성
        private SmsNotifier smsNotifier = new SmsNotifier();  // 직접 생성
        
        
        public void processOrder(Order order) {
            // 주문 처리 로직
            emailSender.sendOrderConfirmation(order);
            smsNotifier.sendNotification(order);
        }
    }
    
    • 문제점
      • OrderService가 구체적인 구현체에 강한 결합

        ⇒ 테스트 시 실제 이메일이 발송되거나 SMS가 전송될 수 있음

      • 다른 알림 방식으로 변경하려면 코드 수정 필요 (OCP 위반)

      • 단위 테스트가 어려움



  • 이후
    @Service
    public class OrderService {
    
        private final NotificationService notificationService;
        
        // 생성자 주입
        public OrderService(NotificationService notificationService) {
            this.notificationService = notificationService;
        }
        
        public void processOrder(Order order) {
            // 주문 처리 로직
            notificationService.sendOrderConfirmation(order);
        }
    }
    
    
    @Component
    public class EmailNotificationService implements NotificationService {
        @Override
        public void sendOrderConfirmation(Order order) {
            // 이메일 발송 로직
        }
    }
    
    • 생성자를 통해 의존성을 주입 받는다.
      • 약한 결합
      • Spring의 경우, IoC 컨테이너로 객체 생성 및 의존성 주입도 자동화할 수 있다.
    • 테스트 시 Mock 객체를 활용할 수 있어 테스트가 용이하다.
    • 인터페이스까지 활용한다면 다른 기능으로 변경 시 쉽게 변경할 수 있다. (OCP 준수)

@Autowired 동작 원리

@Service
public class OrderService {

    private final PaymentService paymentService;
    
    **public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}
  • 타입 기반으로 Bean을 찾아서 주입
  • 같은 타입의 Bean이 여러 개인 경우 @Qualifier 사용
  • @Autowired 생략 가능 (생성자가 하나인 경우)

권장되는 의존성 주입 방식

  • 생성자 + Lombok
    @RequiredArgsConstructor
    @Service
    public class OrderService {
        private final PaymentService paymentService;
    }
    
    • Spring Boot에서는 생성자가 하나인 경우 @Autowired를 생략할 수 있으며, Lombok의 @RequiredArgsConstructor를 사용하면 final 필드에 대한 생성자를 자동 생성하여 더욱 간편하게 의존성 주입을 구현할 수 있다.

    • 장점
      • 불변성 보장
      • 테스트 시 의존성 주입이 쉽다.
      • 순환 참조 즉시 감지

의존성 주입의 장점

  • 낮은 결합도 (Low Coupling)
    • 인터페이스에 의존하여 구현체 변경이 용이
    • 객체 간의 직접적인 의존성 제거


  • 재사용성과 유지보수성 향상
    • 동일한 컴포넌트를 다양한 환경에서 재사용
    • 코드 변경 없이 다른 구현체로 교체 가능


  • 높은 테스트 용이성

    @SpringBootTest
    void orderServiceTest() {
    
    **    @MockBean
        private PaymentService paymentService;
    
        @Autowired
        private OrderService orderService;
        
        @Test
        void orderServiceTest() {
            // 테스트 로직
        }
    **}
    

의존성 주입 주의사항

  • 순환 참조 (Circular Dependency)
    @Service
    public class ServiceA {
        public ServiceA(ServiceB serviceB) { ... }  *// A → B*
    }
    
    @Service
    public class ServiceB {
        public ServiceB(ServiceA serviceA) { ... }  *// B → A (순환 참조)*
    }
    
    • 생성자 주입 시 애플리케이션 시작 시점에 오류 발생


  • @Qualifier를 통한 Bean 선택

    public interface NotificationService { ... }
    
    @Component("emailNotification")
    public class EmailNotificationService implements NotificationService { ... }
    
    @Component("smsNotification") 
    public class SmsNotificationService implements NotificationService { ... }
    
    @Service
    @RequiredArgsConstructor
    public class OrderService {
    
        @Qualifier("emailNotification")
        private final NotificationService notificationService;
    
    }
    
    • NotificationService 구현체가 하나가 아니라면, @Qualifier로 구현체를 특정할 수 있다.

실무에서의 패턴

  • 프로파일별 Bean 설정
    @Service
    @Profile("dev")
    public class MockEmailService implements EmailService { ... }
    
    @Service
    @Profile("prod")
    public class RealEmailService implements EmailService { ... }
    
19@Component, @Controller, @Service, @Repository의 차이점에 대해서 설명해주세요.쉬움
@Component, @Controller, @Service, @Repository는 모두 Spring의 스테레오타입 애노테이션입니다. 이 애노테이션은 해당 클래스를 Spring Bean으로 등록하는 역할을 합니다. 이 중 @Component는 가장 기본적인 애노테이션으로 일반적인 Spring Bean을 등록할 때 사용되고 다른 애노테이션이 이 애노테이션을 상속받습니다. @Controller는 웹 요청을 처리하는 컨트롤러 계층에 사용합니다. @Service는 비즈니스 로직을 처리하는 서비스 계층에, @Repository는 데이터 접근 계층에 사용하여 각 클래스의 역할을 명확히 구분할 수 있습니다. @Repository의 경우 데이터베이스 예외를 Spring의 DataAccessException으로 변환하는 추가 기능을 제공하기도 합니다.
상세 설명

스테레오타입 애노테이션(Stereotype Annotation)이란?

  • Spring에서 클래스의 역할을 명시하고 Bean으로 등록하기 위해 사용하는 애노테이션들
  • 모두 @Component를 기반으로 하는 특수화된(specialized) 애노테이션
    • 최상위인 @Component를 다른 애노테이션이 상속 받고 있다.
  • 컴포넌트 스캔(@ComponentScan)을 통해 자동으로 Bean으로 등록
    • 컴포넌트 스캔은 @SpringBootApplication 애노테이션으로 활성화된다.

      • @SpringBootApplication

        = @Configuration + @ComponentScan + @EnableAutoConfiguration

    • 등록된 Bean은 의존성 주입에 사용된다.

  • 코드의 가독성과 유지보수성을 향상시키고, 계층별 역할을 명확히 구분한다.


  • 종류
    • @Component
      • 일반적인 스프링 컴포넌트 (최상위)
    • @Service
      • Service Layer (비즈니스 로직 계층)
    • @Repository
      • DAO Layer (DB 접근 계층)
    • @Controller
      • Controller Layer (웹 계층)
    • @Configuration
      • 설정 파일, @Bean을 수동 등록할 때 사용하는 클래스
    • @RestController 등

참고
  • @Entity
    • Spring이 아닌 JPA(Hibernate)의 에노테이션이다.
    • Java Persistence API(JPA)의 일부로, DB 테이블과 매핑하기 위해 사용

@Component

@Component
public class EmailSender {
    public void sendEmail(String message) {
        // 이메일 전송 로직
    }
}
  • 가장 기본적인 스테레오타입 애노테이션
  • 특별한 역할이 없는 일반적인 Spring Bean을 등록할 때 사용
  • 다른 스테레오타입 애노테이션들의 기반이 되는 메타 애노테이션
  • 사용
    • Utility 클래스, Helper 클래스 등에 주로 사용

@Controller

@Controller
public class UserController {

    @RequestMapping("/users")
    public String getUsers(Model model) {
        // 사용자 목록 조회 로직
        return "users";
    }
}
  • Spring MVC의 컨트롤러 계층에 사용

  • 웹 요청(HTTP 요청)을 처리하고 응답을 생성하는 역할

  • @RequestMapping, @GetMapping 등과 함께 사용하여 URL 매핑

  • Spring MVC에서 Handler Mapping 시 컨트롤러로 인식

    즉, 즉, 해당 클래스가 HTTP 요청을 처리할 수 있는 컨트롤러로 인식되도록 해준다.

    • Handler Mapping
      • Spring MVC가 HTTP 요청을 처리할 때, 어떤 메서드가 이 요청을 처리할지 결정

참고

@RestController는 @Controller + @ResponseBody의 조합으로, JSON/XML 등의 데이터를 직접 반환하는 RESTful API 컨트롤러에 사용됩니다.


중요
  • Spring MVC 동작 구조

    1. 클라이언트가 요청을 보냄 (GET /users/1)
    2. DispatcherServlet이 요청을 받음
    3. HandlerMapping이 요청 URL에 맞는 컨트롤러 메서드를 찾음
    4. HandlerAdapter가 해당 메서드 실행
    5. 컨트롤러 종류에 따라 html 렌더링이나 JSON 반환
  • HandlerMapping이 찾을 때 @Controller로 컨트롤러를 인식

@Service

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    
    public User createUser(User user) {
        // 비즈니스 로직 처리
        validateUser(user);
        return userRepository.save(user);
    }
}
  • 비즈니스 로직을 처리하는 서비스 계층에 사용
    • 서비스 계층
      • 트랜잭션 처리, 비즈니스 로직 구현 등
  • @Component와 기능적으로 동일하지만, 비즈니스 로직의 역할을 명시
  • 코드의 가독성과 유지보수성 향상

@Repository

@Repository
public class UserRepositoryImpl implements UserRepository {

    @PersistenceContext
    private EntityManager entityManager;
    
    public User findById(Long id) {
        return entityManager.find(User.class, id);
    }
}
  • 데이터 접근 계층(DAO, Data Access Object)에 사용
    • 데이터베이스와의 상호작용을 담당
  • 특별한 기능
    • 데이터 접근 계층에서 발생하는 예외를 Spring의 DataAccessException으로 자동 변환
  • JPA, MyBatis, JDBC 등과 함께 사용

중요

@Repository의 예외 변환 기능은 Spring AOP를 통해 구현됩니다. 데이터베이스별로 다른 예외들을 Spring의 일관된 예외 계층구조로 변환하여 데이터베이스 벤더에 독립적인 코드를 작성할 수 있게 해줍니다.

계층 구조 (Layered Architecture)

  • Controller Layer
    • 클라이언트 요청을 받아 응답 처리
    • 요청 데이터 검증 및 변환
    • 적절한 Service 메서드 호출

  • Service Layer
    • 비즈니스 로직 처리
    • 트랜잭션 정의
    • 여러 Repository 조합하여 복합적인 비즈니스 기능 제공

  • Repository Layer
    • DBMS와의 상호작용
    • CRUD 연산 수행

실무에서의 사용 패턴

@Controller
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;
    
    @PostMapping("/orders")
    public String createOrder(@ModelAttribute Order order) {
        orderService.processOrder(order);
        return "redirect:/orders";
    }
}


@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrderService {

    private final OrderRepository orderRepository;
    
    public void processOrder(Order order) {
        // 비즈니스 로직
        order.calculateTotal();
        orderRepository.save(order);
    }
}


@Repository
public class OrderRepository {
    @PersistenceContext
    private EntityManager em;
    
    public Order save(Order order) {
        em.persist(order);
        return order;
    }
}
20AutoConfiguration 동작 원리를 설명해주세요.보통
Spring Boot의 AutoConfiguration은 클래스패스에 존재하는 라이브러리와 설정 정보를 기반으로 필요한 Bean을 자동으로 등록하는 기능입니다. @EnableAutoConfiguration 어노테이션을 통해 활성화할 수 있고, 작동 과정은 먼저 AutoConfigurationImportSelector 클래스가 META-INF/spring 폴더의 AutoConfiguration.imports 파일에서 자동 설정 클래스 목록을 읽어온 후, 각 클래스에 대해 조건부 애노테이션을 평가하여 Bean 생성 여부를 판단합니다. 예를 들어 H2 데이터베이스가 클래스패스에 있으면 자동으로 DataSource를 설정하고, JPA가 있다면 EntityManager를 구성합니다. 이를 통해 개발자는 최소한의 설정으로 빠르게 애플리케이션을 구성할 수 있으며, 필요 시 사용자 정의 설정으로 자동 설정 클래스를 오버라이드할 수 있습니다.
상세 설명

AutoConfiguration이란?

  • Spring Boot가 제공하는 자동 설정 기능으로, Classpath에 존재하는 외부 라이브러리설정 정보를 기반으로 필요한 Bean을 자동으로 등록

  • 개발자가 명시적으로 설정하지 않은 부분을 Spring Boot가 관례에 따라 자동으로 구성

  • "Convention over Configuration" 철학을 구현한 핵심 기능

    ⇒ 명시적으로 다 설정하지 않아도, 일반적인 관례를 기본값으로 제공하겠다.

  • @SpringBootApplication 애노테이션에 포함된 @EnableAutoConfiguration을 통해 활성화할 수 있다.


  • Classpath
    • JVM이 .class 파일을 찾기 위해 검색하는 경로들의 집합
      • 컴파일 결과 폴더 (/bin, /build/classes, /target/classes)
      • JAR 파일
      • 라이브러리 디렉토리
      • src/main/resources 폴더 (application.yml 등)

AutoConfiguration 동작 과정

  • 1단계 : AutoConfigurationImportSelector 실행
    • @EnableAutoConfiguration@Import(AutoConfigurationImportSelector.class)를 통해 실행

    • AutoConfigurationImportSelector 클래스
      package: org.springframework.boot.autoconfigure
      ...
      
      public class AutoConfigurationImportSelector 
          implements DeferredImportSelector, BeanClassLoaderAware, 
                  ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
      
          @Override
          public String[] selectImports(AnnotationMetadata annotationMetadata) {
              if (!isEnabled(annotationMetadata)) {
                  return NO_IMPORTS;
              }
              // 1. 메타데이터 로드
              AutoConfigurationMetadata metadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
              
              // 2. 자동 설정 엔트리 가져오기
              AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(metadata, annotationMetadata);
              
              return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
          }
      
          protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
              // 3. 후보 설정 클래스들 가져오기 (여기서 파일 읽기!)
              List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
              // 4. 중복 제거, exclude 처리, 필터링
              configurations = removeDuplicates(configurations);
              // 5. 최종 결과 반환
              return new AutoConfigurationEntry(configurations, exclusions);
          }
      }
      
      • selectImports() 메서드가 핵심 진입점 역할 ⇒ getAutoConfigurationEntry()
    • getCandidateConfigurations() 메서드로 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일 읽기

      (Spring Boot 2.7 이상)

    • spring-boot-autoconfigure 라이브러리(JAR)AutoConfiguration.imports 파일에서 미리 정의된 100여 개의 자동 설정 클래스들 목록 획득

    • 중복 제거, exclude 목록 처리, 필터링 수행

    • Ex. org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일

      org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
      org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
      


  • 2단계 : 각 자동 설정 클래스 처리
    • 1단계에서 가져온 자동 설정 클래스들을 하나씩 처리

    • 각 자동 설정 클래스에 대해 조건부 애노테이션 평가 (@ConditionalOnClass, @ConditionalOnMissingBean 등)

      ⇒ Classpath에 특정 라이브러리가 존재하는지, 특정 Bean이 이미 등록되어 있는지 등을 검사

    • @EnableConfigurationProperties가 있는 클래스들은 application.yml 값을 Properties 객체로 바인딩

    • 조건을 만족하는 경우에만 Bean 등록

    • Ex. 예시

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnClass({EntityManager.class})
      @ConditionalOnMissingBean(DataSource.class)
      @EnableConfigurationProperties(JpaProperties.class)  // yml 바인딩
      public class HibernateJpaAutoConfiguration {
          // 조건 만족 시 EntityManager 등 Bean 등록
      }
      
      • JPA 의존성을 넣지 않는 경우

        EntityManager 클래스가 classpath에 없으면 조건부 애노테이션에 따라, JPA 설정을 건들이지 않는다.



  • 3단계 : Bean 등록 및 우선순위 처리
    • 조건을 만족하는 자동 설정 클래스들이 실행되어 필요한 Bean들을 Spring 컨테이너에 등록
    • 사용자가 정의한 Bean이 우선순위를 가지며, 자동 설정 Bean을 오버라이드할 수 있다.

주요 조건부 어노테이션

  • 조건부 애노테이션이란?
    • Bean 등록 또는 설정 적용 여부를 “조건부로 제어”하는 기능
    • 자동 설정이 무조건 적용되지 않고, 상황에 따라 동적으로 적용 여부를 판단하기 위함


  • @Conditiona

    @Conditional(MyCondition.class)
    
    • Spring의 기본형 조건부 애노테이션
    • Condition 인터페이스 구현
    • Spring Boot는 이것을 추상화한 특수 조건 애노테이션들을 제공한다.
      • 모두 @Conditional을 내부적으로 사용한다.


  • @ConditionalOnClass
    @ConditionalOnClass({DataSource.class, JdbcTemplate.class})
    public class DataSourceAutoConfiguration { ... }
    
    • 특정 클래스가 Classpath에 존재할 때만 실행
    • 즉, Classpath의 .class, -jar 파일을 보고 조건에 해당하는 클래스가 있는지 판단한다.


  • @ConditionalOnMissingBean
    @ConditionalOnMissingBean(DataSource.class)
    @Bean
    public DataSource dataSource() { ... }
    
    • 특정 타입의 Bean이 없을 때만 실행


  • @ConditionalOnProperty
    @ConditionalOnProperty(name = "spring.datasource.url")
    
    • 특정 프로퍼티 값에 따라 실행
    • application.yml에 해당 프로퍼티가 존재하고 값이 설정되어 있을 때만 자동 설정 적용

@ConfigurationProperties

@Configuration
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DataSourceProperties properties) {
        return DataSourceBuilder.create()
            .url(properties.getUrl())
            .username(properties.getUsername())
            .password(properties.getPassword())
            .build();
    }
}

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
    private String url;
    private String username;
    private String password;
        
        // getter, setter...
}

  • @ConfigurationPropertiesAutoConfiguration의 관계
    • AutoConfiguration에서 application.yml 프로퍼티를 Java 객체로 바인딩하는 핵심 메커니즘
    • @EnableConfigurationProperties를 통해 프로퍼티 클래스를 Bean으로 등록
    • 예시
      • application.ymlspring.datasource.url=jdbc:mysql://localhost/test 설정
      • DataSourceProperties 객체의 url 필드에 자동으로 바인딩
      • AutoConfiguration에서 이 객체를 주입 받아 DataSource Bean 설정에 사용

대표적인 AutoConfiguration 예시

  • DataSourceAutoConfiguration
    • H2, MySQL 등의 데이터베이스 드라이버가 Classpath에 있으면 자동으로 DataSource Bean 생성
    • application.yml(properties)spring.datasource.* 프로퍼티를 읽어 설정

  • JpaRepositoriesAutoConfiguration
    • Spring Data JPA가 Classpath에 있으면 JPA Repository 스캔 및 등록
    • EntityManager, TransactionManager 등을 자동 구성

  • WebMvcAutoConfiguration
    • Spring Web MVC 의존성이 있으면 DispatcherServlet, ViewResolver 등을 자동 설정

사용자 정의 설정

  • 특정 자동 설정 제외
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    public class MyApplication { ... }
    
    • exclude = {DataSourceAutoConfiguration.class}


  • 사용자 정의 Bean으로 오버라이드
    @Configuration
    public class CustomDataSourceConfig {
    
        @Bean
        @Primary
        public DataSource customDataSource() {
            // 사용자 정의 DataSource
        }
    }
    

AutoConfiguration의 장점

  • 빠른 개발 시작
    • 최소한의 설정으로 애플리케이션 실행 가능
  • 검증 완료
    • 설정되는 것은 Spring 팀이 검증한 설정들이다.
  • 유연성
    • 필요에 따라 자동 설정을 부분적으로 오버라이드하거나 완전히 제외 가능
  • 유지보수성
    • 라이브러리 업그레이드 시 설정 변경이 자동으로 반영