강의 5: 스프링 설정 방식
XML, 어노테이션, Java Config 기반 설정 방식 비교
스프링 프레임워크 강의 시리즈
강의 5/30
학습 목표
- 스프링 프레임워크의 다양한 설정 방식을 이해한다
- XML 기반, 어노테이션 기반, Java Config 기반 설정의 특징을 비교한다
- 각 설정 방식의 장단점을 파악하고 적절한 상황에 적용할 수 있다
- 컴포넌트 스캔의 개념과 동작 방식을 이해한다
1. 스프링 설정 방식 개요
스프링 프레임워크는 애플리케이션의 객체 생성과 의존성 관리를 위해 다양한 설정 방식을 제공합니다.
초기에는 XML 기반 설정만 제공했지만, 스프링 2.5부터 어노테이션 기반 설정이 도입되었고,
스프링 3.0부터는 Java 코드 기반 설정이 추가되었습니다.
각 설정 방식은 서로 다른 특징과 장단점을 가지고 있으며, 프로젝트의 요구사항과 개발 환경에 따라
적절한 방식을 선택하거나 혼합하여 사용할 수 있습니다.
스프링 설정 방식의 발전 과정
설정 방식 | 도입 버전 | 특징 |
---|---|---|
XML 기반 설정 | 스프링 1.0 | 초기 방식, 모든 빈과 의존성을 XML에 명시적으로 선언 |
어노테이션 기반 설정 | 스프링 2.5 | 코드에 어노테이션을 추가하여 빈과 의존성 정의 |
Java Config 기반 설정 | 스프링 3.0 | 순수 자바 코드로 빈과 의존성 설정 |
2. XML 기반 설정
XML 기반 설정은 스프링의 가장 전통적인 설정 방식으로, applicationContext.xml
과 같은
XML 파일에 빈과 의존성을 정의합니다.
XML 설정의 기본 구조
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 빈 정의 --> <bean id="userService" class="com.example.service.UserServiceImpl"> <!-- 생성자 주입 --> <constructor-arg ref="userRepository" /> </bean> <bean id="userRepository" class="com.example.repository.JdbcUserRepository"> <!-- 세터 주입 --> <property name="dataSource" ref="dataSource" /> </bean> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/mydb" /> <property name="username" value="root" /> <property name="password" value="password" /> </bean> </beans>
XML 설정의 장점
- 모든 빈 설정이 한 곳에 집중되어 있어 전체적인 구성을 파악하기 쉽다
- 코드와 설정의 분리로 인해 코드 변경 없이 설정만 변경 가능하다
- 타사 라이브러리의 클래스를 빈으로 등록할 때 편리하다
- 레거시 시스템과의 호환성이 좋다
XML 설정의 단점
- XML 작성이 번거롭고 장황하다
- 오타나 잘못된 참조로 인한 오류를 컴파일 시점에 발견하기 어렵다
- 대규모 프로젝트에서 XML 파일이 매우 커질 수 있다
- IDE의 자동완성 지원이 상대적으로 부족하다
언제 XML 설정을 사용하는 것이 좋을까?
- 레거시 시스템을 유지보수할 때
- 서드파티 라이브러리의 클래스를 빈으로 등록해야 할 때
- 설정을 코드와 완전히 분리하고자 할 때
- 빈 설정을 런타임에 변경해야 할 필요가 있을 때
3. 어노테이션 기반 설정
어노테이션 기반 설정은 자바 클래스에 직접 어노테이션을 추가하여 빈을 정의하고 의존성을 주입하는 방식입니다.
XML 설정과 함께 사용할 수도 있고, component-scan
을 활용하여
특정 패키지의 어노테이션이 붙은 클래스를 자동으로 빈으로 등록할 수 있습니다.
주요 어노테이션
어노테이션 | 설명 |
---|---|
@Component | 일반적인 스프링 관리 컴포넌트를 정의 |
@Service | 비즈니스 로직을 처리하는 서비스 클래스에 사용 |
@Repository | 데이터 접근 계층의 DAO나 Repository 클래스에 사용 |
@Controller | 웹 요청을 처리하는 컨트롤러 클래스에 사용 |
@Autowired | 의존성 자동 주입을 위해 사용 |
@Qualifier | 여러 빈 중 특정 빈을 지정하기 위해 사용 |
@Value | 프로퍼티 값을 주입하기 위해 사용 |
컴포넌트 스캔 활성화
XML 설정 파일에서 컴포넌트 스캔을 활성화하는 방법:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 컴포넌트 스캔 활성화 --> <context:component-scan base-package="com.example" /> </beans>
어노테이션 기반 코드 예제
package com.example.repository; import org.springframework.stereotype.Repository; @Repository public class UserRepositoryImpl implements UserRepository { @Override public User findById(Long id) { // 사용자 조회 로직 return new User(id, "사용자" + id); } }
package com.example.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.example.repository.UserRepository; @Service public class UserServiceImpl implements UserService { private final UserRepository userRepository; @Autowired public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Override public User getUserById(Long id) { return userRepository.findById(id); } }
어노테이션 기반 설정의 장점
- XML 설정보다 간결하고 직관적이다
- 클래스와 설정이 함께 있어 가독성이 향상된다
- IDE의 지원(자동완성, 리팩토링)이 더 잘된다
- 컴파일 시점에 오류를 발견할 수 있다
어노테이션 기반 설정의 단점
- 코드와 설정이 결합되어 코드 변경 없이 설정만 변경하기 어렵다
- 서드파티 라이브러리의 클래스에는 어노테이션을 추가할 수 없다
- 설정이 여러 클래스에 분산되어 전체적인 구성을 파악하기 어려울 수 있다
언제 어노테이션 기반 설정을 사용하는 것이 좋을까?
- 새로운 프로젝트를 시작할 때
- 코드의 가독성과 유지보수성을 높이고 싶을 때
- 개발자가 빈 설정을 직관적으로 이해하기 쉽게 하고 싶을 때
- 코드 자체가 설정의 일부가 되는 것이 자연스러운 경우
4. Java Config 기반 설정
Java Config는 순수 자바 코드를 사용하여 스프링 빈과 설정을 정의하는 방식입니다.
XML이나 어노테이션 대신 @Configuration
과
@Bean
어노테이션을 활용한 Java 클래스를 작성합니다.
Java Config 기본 구조
package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.apache.commons.dbcp2.BasicDataSource; import com.example.repository.JdbcUserRepository; import com.example.repository.UserRepository; import com.example.service.UserService; import com.example.service.UserServiceImpl; @Configuration public class AppConfig { @Bean public DataSource dataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mydb"); dataSource.setUsername("root"); dataSource.setPassword("password"); return dataSource; } @Bean public UserRepository userRepository() { JdbcUserRepository repository = new JdbcUserRepository(); repository.setDataSource(dataSource()); return repository; } @Bean public UserService userService() { return new UserServiceImpl(userRepository()); } }
Java Config에서 컴포넌트 스캔 활성화
package com.example.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = "com.example") public class AppConfig { // 추가 빈 설정... }
Java Config와 다른 설정 방식 혼합
package com.example.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; @Configuration @Import(SecurityConfig.class) // 다른 Java Config 가져오기 @ImportResource("classpath:legacy-config.xml") // XML 설정 가져오기 public class AppConfig { // 빈 설정... }
Java Config 설정의 장점
- 코드로 설정하므로 타입 안전성이 보장된다
- 리팩토링이 용이하고 IDE의 지원(자동완성, 탐색)이 뛰어나다
- 조건부 로직, 반복문 등 프로그래밍 기능을 활용할 수 있다
- 디버깅과 단위 테스트가 용이하다
- 서드파티 라이브러리의 클래스도 빈으로 등록하기 쉽다
Java Config 설정의 단점
- 설정이 코드에 완전히 통합되어 코드 변경 없이 설정 변경이 어렵다
- 설정 클래스가 많아지면 관리가 복잡해질 수 있다
- 런타임에 설정을 변경하기 어렵다
언제 Java Config 설정을 사용하는 것이 좋을까?
- 복잡한 빈 초기화 로직이 필요할 때
- 프로그래밍 방식으로 빈을 조건부로 정의하거나 생성해야 할 때
- 타입 안전성이 중요한 프로젝트에서
- 서드파티 라이브러리를 통합할 때
- 설정 재사용과 모듈화가 필요할 때
5. 설정 방식 비교와 실습
세 가지 설정 방식 비교
특성 | XML 기반 설정 | 어노테이션 기반 설정 | Java Config 기반 설정 |
---|---|---|---|
가독성 | 낮음 (장황함) | 높음 (간결함) | 중간 (자바 코드) |
타입 안전성 | 낮음 | 높음 | 매우 높음 |
IDE 지원 | 제한적 | 좋음 | 매우 좋음 |
설정/코드 분리 | 완전히 분리 | 통합됨 | 부분적 분리 |
서드파티 통합 | 좋음 | 어려움 | 매우 좋음 |
런타임 변경 | 가능 | 어려움 | 어려움 |
학습 곡선 | 보통 | 낮음 | 보통 |
실습: 세 가지 설정 방식으로 동일한 애플리케이션 구성
다음은 간단한 메시지 서비스 애플리케이션을 세 가지 서로 다른 방식으로 구성하는 예제입니다.
1. 도메인 클래스
// Message.java package com.example.domain; public class Message { private String content; public Message(String content) { this.content = content; } public String getContent() { return content; } } // MessageRepository.java package com.example.repository; import com.example.domain.Message; public interface MessageRepository { Message findMessage(); } // SimpleMessageRepository.java package com.example.repository; import com.example.domain.Message; public class SimpleMessageRepository implements MessageRepository { @Override public Message findMessage() { return new Message("Hello from Repository!"); } } // MessageService.java package com.example.service; import com.example.domain.Message; public interface MessageService { Message getMessage(); } // MessageServiceImpl.java package com.example.service; import com.example.domain.Message; import com.example.repository.MessageRepository; public class MessageServiceImpl implements MessageService { private final MessageRepository messageRepository; public MessageServiceImpl(MessageRepository messageRepository) { this.messageRepository = messageRepository; } @Override public Message getMessage() { return messageRepository.findMessage(); } }
2. XML 기반 설정
<!-- applicationContext.xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="messageRepository" class="com.example.repository.SimpleMessageRepository" /> <bean id="messageService" class="com.example.service.MessageServiceImpl"> <constructor-arg ref="messageRepository" /> </bean> </beans>
// 사용 예제 import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.example.service.MessageService; public class XmlConfigExample { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); MessageService messageService = context.getBean("messageService", MessageService.class); System.out.println(messageService.getMessage().getContent()); } }
3. 어노테이션 기반 설정
// SimpleMessageRepository.java package com.example.repository; import org.springframework.stereotype.Repository; import com.example.domain.Message; @Repository public class SimpleMessageRepository implements MessageRepository { @Override public Message findMessage() { return new Message("Hello from Repository!"); } } // MessageServiceImpl.java package com.example.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.example.domain.Message; import com.example.repository.MessageRepository; @Service public class MessageServiceImpl implements MessageService { private final MessageRepository messageRepository; @Autowired public MessageServiceImpl(MessageRepository messageRepository) { this.messageRepository = messageRepository; } @Override public Message getMessage() { return messageRepository.findMessage(); } }
<!-- applicationContext.xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.example" /> </beans>
// 사용 예제 import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.example.service.MessageService; public class AnnotationConfigExample { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); MessageService messageService = context.getBean(MessageService.class); System.out.println(messageService.getMessage().getContent()); } }
4. Java Config 기반 설정
// AppConfig.java package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.example.repository.MessageRepository; import com.example.repository.SimpleMessageRepository; import com.example.service.MessageService; import com.example.service.MessageServiceImpl; @Configuration public class AppConfig { @Bean public MessageRepository messageRepository() { return new SimpleMessageRepository(); } @Bean public MessageService messageService() { return new MessageServiceImpl(messageRepository()); } }
// 사용 예제 import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.example.config.AppConfig; import com.example.service.MessageService; public class JavaConfigExample { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); MessageService messageService = context.getBean(MessageService.class); System.out.println(messageService.getMessage().getContent()); } }
6. 실제 프로젝트에서의 설정 방식 선택
실제 프로젝트에서는 한 가지 설정 방식만 고집하기보다는 상황에 맞게 여러 방식을 혼합하여 사용하는 것이 일반적입니다.
권장 사항
-
새로운 프로젝트: Java Config와 어노테이션 기반 설정의 조합이 권장됩니다.
- 비즈니스 컴포넌트는 @Component, @Service 등의 어노테이션 사용
- 서드파티 라이브러리나 복잡한 설정은 Java Config 사용
-
레거시 시스템 통합: XML 설정을 유지하면서 점진적으로 Java Config로 마이그레이션
- @ImportResource를 사용하여 XML 설정을 Java Config에 통합
- 새로운 기능은 Java Config나 어노테이션으로 추가
-
복잡한 엔터프라이즈 애플리케이션: 모듈화된 접근법
- 도메인별 설정 모듈화 (데이터 액세스, 서비스, 웹, 보안 등)
- 각 모듈에 가장 적합한 설정 방식 선택
- @Import, @ImportResource를 사용하여 통합
현대적인 스프링 애플리케이션의 설정 방식
최근 Spring Boot의 등장으로 설정 방식이 크게 단순화되었습니다. Spring Boot는 자동 설정(Auto-configuration)을
통해 많은 설정을 자동화하고, 필요한 경우에만 개발자가 커스터마이징할 수 있도록 합니다.
Spring Boot에서는 주로 다음과 같은 방식을 사용합니다:
- 어노테이션 기반 설정 (@Component, @Service 등)
- Java Config (@Configuration, @Bean)
- application.properties/application.yml 파일을 통한 외부 설정
- 조건부 구성을 위한 @Conditional 계열 어노테이션
7. 실습 과제
실습: 세 가지 설정 방식으로 프로젝트 구성하기
간단한 책 관리 시스템을 세 가지 설정 방식(XML, 어노테이션, Java Config)으로 각각 구현해보세요.
요구사항:
- Book 클래스 (id, title, author, isbn 필드)
- BookRepository 인터페이스와 구현체
- BookService 인터페이스와 구현체
- 세 가지 설정 방식으로 각각 구성하고 실행 결과 비교
도전 과제:
- 각 설정 방식에 프로필(개발, 테스트, 운영) 추가
- 다양한 의존성 주입 방법 시도 (생성자, 세터, 필드 주입)
- 서로 다른 설정 방식 혼합하여 사용
답글 남기기