이번 시간에는 스프링 프레임워크의 기본적인 컨셉과 기능에 대해서 알아보도록 하겠습니다.
지금부터 서술될 내용들은 전부 스프링 공식문서(https://docs.spring.io/spring-framework/reference/overview.html) 에서 발췌된 것임을 알립니다.
개요
스프링은 자바 엔터프라이즈 애플리케이션을 쉽게 만들 수 있게 해주는 프레임워크입니다. 자바 언어를 엔터프라이즈 환경에서 활용할 수 있도록 필요한 모든 것을 제공하며, JVM 기반의 대안 언어로서 Groovy 와 Kotlin 도 지원합니다. 스프링 프레임워크 6.0 버전부터는 Java 17 이상이 필요합니다.
스프링이 자바 엔터프라이즈 애플리케이션을 쉽게 만들 수 있는 이유
1. 의존성 주입(Dependency Injection, DI)과 제어의 역전(Inversion of Control, IoC)
스프링은 의존성 주입(DI)과 제어의 역전(IoC) 패턴을 중심으로 설계되어 있습니다. 이는 객체 간의 의존성을 코드에서 명시적으로 연결하지 않고도 관리할 수 있게 해주며, 애플리케이션 구성 요소 간의 결합도를 낮추어 코드의 유지보수성을 높입니다. 예를 들어, 새로운 서비스나 모듈을 추가할 때 코드를 수정하지 않고도 기존 코드와 통합할 수 있어 개발이 용이해집니다.
2. 모듈화된 구조
스프링 프레임워크는 여러 개의 모듈로 구성되어 있으며, 애플리케이션의 필요에 따라 필요한 모듈만 선택적으로 사용할 수 있습니다. 이 구조 덕분에 개발자는 복잡한 엔터프라이즈 애플리케이션에서도 필요한 기능만 선택적으로 사용하여, 불필요한 복잡성을 피하면서 효율적인 개발이 가능합니다.
3. 광범위한 기술 스택 지원
스프링은 데이터 액세스, 트랜잭션 관리, 웹 애플리케이션 개발, 메시징, 배치 처리 등 다양한 엔터프라이즈 애플리케이션 요구 사항을 지원합니다. 이러한 기술 스택의 광범위한 지원은 개발자가 다양한 비즈니스 요구 사항을 충족시키는 데 있어 스프링을 원스톱 솔루션으로 사용할 수 있게 해줍니다. 예를 들어, 스프링은 JPA, JMS, REST, SOAP 등 다양한 기술과 표준을 지원하여, 개발자가 특정 기술에 얽매이지 않고 다양한 선택을 할 수 있도록 도와줍니다.
4. 스프링 부트(Spring Boot)의 등장
스프링 부트(Spring Boot)는 스프링 프레임워크의 복잡한 설정 작업을 단순화하고, 기본적인 설정을 제공하여, 개발자가 빠르게 애플리케이션을 시작하고 배포할 수 있도록 해줍니다. 이는 특히 DevOps와 클라우드 환경에서 중요한데, 복잡한 설정 없이도 쉽게 컨테이너화하고 배포할 수 있기 때문입니다. 스프링 부트는 대부분의 설정을 자동화하여 개발자가 비즈니스 로직에 집중할 수 있게 하고, 필요 시 세부적인 설정도 쉽게 조정할 수 있는 유연성을 제공합니다.
5. 강력한 커뮤니티와 풍부한 에코시스템
스프링은 오랜 기간 동안 발전해온 오픈 소스 프로젝트로, 강력한 커뮤니티 지원과 방대한 에코시스템을 자랑합니다. 스프링의 커뮤니티는 풍부한 문서, 예제, 서드파티 라이브러리, 플러그인 등을 제공하여 개발자가 자주 직면하는 문제를 쉽게 해결할 수 있도록 도와줍니다. 또한, 커뮤니티의 지속적인 피드백과 기여 덕분에 스프링은 최신 기술 트렌드를 빠르게 수용하고 진화해 왔습니다.
6. 유연성과 확장성
스프링은 특정 개발 방식에 강요하지 않고 다양한 방법을 지원하는 비관적인(unopinionated) 프레임워크입니다. 즉, 개발자는 자신의 요구에 맞게 자유롭게 아키텍처를 설계하고, 다양한 기술과 통합할 수 있습니다. 예를 들어, 스프링은 전통적인 MVC 아키텍처뿐만 아니라, 최근에는 WebFlux와 같은 비동기 리액티브 프로그래밍 모델도 지원하여, 다양한 애플리케이션 요구 사항에 대응할 수 있습니다.
7. 일관된 트랜잭션 관리
스프링은 트랜잭션 관리에 있어 매우 일관된 프로그래밍 모델을 제공합니다. 개발자는 비즈니스 로직을 복잡한 트랜잭션 관리 코드와 분리하여 작성할 수 있으며, 선언적 트랜잭션 관리 방식으로 코드를 간결하게 유지할 수 있습니다. 이러한 트랜잭션 관리의 일관성은 애플리케이션의 안정성과 신뢰성을 높이는 데 기여합니다.
8. 테스트 용이성
스프링은 유닛 테스트 및 통합 테스트를 쉽게 작성할 수 있도록 지원합니다. 스프링의 의존성 주입과 IoC 컨테이너 덕분에, 모의 객체(Mock Object)를 활용한 테스트가 용이해지고, 테스트 가능한 코드 작성이 가능합니다. 스프링 테스트 모듈(Spring Test Module)은 테스트 환경 설정을 간단하게 하고, 다양한 테스트 시나리오를 지원합니다.
스프링이 엔터프라이즈 애플리케이션 구축에 적합하지 않다는 의견과 그에 대한 생각
1. 복잡성 증가
비판: 스프링은 매우 유연하고 기능이 풍부한 프레임워크이지만, 이로 인해 처음 사용하는 개발자들에게 상당히 복잡하게 느껴질 수 있습니다. 의존성 주입, 다양한 설정 방식(ex. XML, Java Config, 애너테이션), 그리고 다양한 스프링 프로젝트(ex. 스프링 부트, 스프링 데이터, 스프링 시큐리티 등)가 얽혀 있어, 학습 곡선이 가파르고, 복잡한 설정과 구성에 압도될 수 있습니다. 이러한 복잡성은 프로젝트가 커질수록 더 심화될 수 있습니다.
생각: 스프링의 복잡성은 그만큼 많은 기능과 유연성을 제공하기 때문에 발생한다고 생각합니다. 자바 스프링과 대척점을 이루는 Rust, Golang 으로 구성된 프레임워크의 경우에는 심플하고 가볍다라는 장점이 있을 수 있지만, 아무것도 지원해주지 않는(?) 단점이 더 크다고 생각합니다 (ex. ExpressJS). 또한, 스프링부트 기술을 통해 설정의 복잡성은 어느 정도 해결되었기 때문에 더더욱 복잡성에 대한 반박의 여지가 존재한다고 생각합니다.
2. 설정 과다와 관용성 부족
비판: 스프링은 다양한 설정 방식을 제공하지만, 설정 과다로 인해 코드가 비대해질 수 있습니다. XML 설정 파일은 특히 복잡한 프로젝트에서 관리가 어려워질 수 있고, 코드 내 애너테이션 사용 또한 다른 개발자들에게 혼란을 줄 수 있습니다. 또한, 스프링은 유연하지만, 특정 관례를 따르지 않으면 비효율적일 수 있다는 점에서 관용성이 부족하다는 지적이 있습니다.
의견: 스프링이 초기에는 프로젝트 설정에서부터 수많은 새싹(?)들을 힘들게 한 부분은 사실입니다. 하지만 그 불편함을 개선하고자 노력하였고, 스프링 부트와 같은 기술을 통해 그 복잡성이 크게 줄어든 것 또한 사실입니다. 따라서 설정 과다는 상황에 따라 다르게 느껴질 수 있지만, 최신 스프링 환경에서는 설정이 단순화되고 효율적으로 관리될 수 있습니다.
3. 무거운 프레임워크
비판: 스프링은 다양한 기능을 제공하기 때문에 프레임워크 자체가 무겁고, 애플리케이션의 성능을 저하시킬 수 있다는 우려가 있습니다. 특히, 경량화가 중요한 마이크로서비스 아키텍처나 서버리스 환경에서는 스프링이 적합하지 않을 수 있다는 지적이 있습니다.
의견: 이 부분은 어느 정도 동의가 되지만, 반박의 여지가 충분히 있습니다. 스프링 자체는 기본적으로 무거운 프레임워크임에는 의심의 여지가 없지만, 스프링 부트와 같은 경량화된 프로젝트는 마이크로서비스와 같은 환경에 잘 맞도록 최적화되어 있고 Spring Gateway 와 같은 기술을 통해 MSA 를 지원하려는 노력 또한 계속되고 있습니다. 또한, 스프링은 필요한 모듈만 선택적으로 사용할 수 있기 때문에, 적절한 선택을 통해 성능 문제를 최소화할 수 있습니다. 하지만 정말 극한의 성능이 요구되는 환경이라면 Rust, Golang 과 같은 선택지가 훨씬 좋다라는 것에는 동의합니다.
4. 추상화로 인한 학습 곡선 증가
비판: 스프링은 많은 부분에서 추상화를 제공하여 개발을 용이하게 하지만, 이로 인해 개발자가 기본적인 자바/Jakarta EE 기술을 제대로 이해하지 못하게 될 위험이 있습니다. 개발자는 스프링의 추상화 뒤에 숨겨진 복잡한 메커니즘을 이해하지 못하고, 문제가 발생했을 때 디버깅이 어려워질 수 있습니다.
의견: 스프링의 추상화는 개발 생산성을 높이는 데 큰 도움이 되지만, 이로 인해 개발자가 기본적인 기술에 대한 이해가 부족해질 위험이 있다는 점은 부정할 수 없는 사실입니다. 따라서 스프링을 사용하는 개발자는 스프링에서 잘 정돈해서 제공해주는 추상화 기술에 대해서 심도 깊은 학습 과정이 필요할 것입니다.
5. 커스터마이징의 한계
비판: 스프링은 대부분의 상황에 적합한 유연한 프레임워크이지만, 특정한 경우에는 프레임워크의 한계로 인해 원하는 대로 커스터마이징하기 어렵다는 지적이 있습니다. 예를 들어, 특정 기능을 비표준 방식으로 구현하거나, 스프링이 제공하는 추상화를 무시하고자 할 때 어려움이 있을 수 있습니다.
의견: 동의합니다. 스프링은 확실히 정해진 틀에서는 높은 개발 생산성을 제공하지만, 그 틀을 벗어난다면 생산성이 수직 하락한다고 생각합니다. 따라서, 비표준적인 요구사항이 존재하는 비즈니스라면 스프링 대신 다른 프레임워크를 선택하는 것이 좋은 선택입니다.
또한, 스프링은 다양한 애플리케이션 시나리오를 지원합니다. 대규모 엔터프라이즈 환경에서는 애플리케이션이 오랜 시간 동안 유지보수 없이 방치되었을 수도 있고, 개발자가 제어할 수 없는 JDK 와 애플리케이션 서버의 업그레이드 주기에 맞춰 실행되어야 할 수도 있습니다. 또 다른 경우로는 서버를 내장하여 단일 JAR 로 실행되거나, 클라우드 환경에서 실행될 수도 있습니다. 일부는 서버가 필요 없는 독립형 애플리케이션 (ex. 배치 작업 혹은 통합 워크로드) 일 수도 있습니다.
스프링은 오픈 소스이며, 많은 사용자들이 참여하는 활발한 커뮤니티를 가지고 있습니다. 다양한 실제 사용 사례를 바탕으로 지속적인 피드백을 제공하는 커뮤니티 덕분에 스프링은 오랜 기간 동안 성공적으로 발전할 수 있었습니다.
스프링이란 무엇인가?
스프링이라는 용어는 상황에 따라 다르게 사용됩니다. 스프링 프레임워크 프로젝트 자체를 가리킬 수도 있고, 시간이 지나면서 스프링 프레임워크 위에 구축된 다른 스프링 프로젝트들을 의미할 수도 있습니다. 대부분의 경우, 사람들이 "스프링"이라고 말할 때, 이는 전체 스프링 프로젝트 군을 의미합니다. 이 문서에서는 스프링 프레임워크 자체에 초점을 맞추고 있습니다.
스프링 프레임워크는 여러 모듈로 나뉩니다. 애플리케이션은 필요한 모듈을 선택할 수 있습니다. 핵심 모듈은 코어 컨테이너로, 여기에는 구성 모델과 의존성 주입 메커니즘이 포함됩니다. 그 외에도 스프링 프레임워크는 메시징, 트랜잭션 데이터 및 영속성, 웹 등을 포함한 다양한 애플리케이션 아키텍처를 지원합니다. 또한 Servlet 기반의 Spring MVC 웹 프레임워크와 함께 Spring WebFlux 리액티브 웹 프레임워크도 포함됩니다.
스프링의 역사와 스프링 프레임워크
스프링은 2003년 초기 J2EE 스펙의 복잡성에 대한 대응으로 탄생했습니다. 일부는 자바 EE와 그 현대적 후속작인 Jakarta EE가 스프링과 경쟁한다고 생각하지만, 사실 이들은 상호 보완적입니다. 스프링 프로그래밍 모델은 Jakarta EE 플랫폼 스펙을 수용하지 않으며, 전통적인 EE 우산에서 신중하게 선택된 개별 스펙들과 통합됩니다.
J2EE(Java 2 Platform, Enterprise Edition)는 1999년에 발표된 자바 기반의 엔터프라이즈 애플리케이션 개발 플랫폼입니다. J2EE는 대규모 엔터프라이즈 애플리케이션을 개발하고 배포하기 위해 필요한 여러 가지 표준과 API를 정의합니다. 이후, J2EE는 Java EE(Java Platform, Enterprise Edition)로 이름이 바뀌었고, 현재는 Jakarta EE로 불리며 진화하고 있습니다.
J2EE의 주요 구성 요소와 스펙
1. Servlets와 JSP(JavaServer Pages)
● Servlet: 웹 서버에서 동작하는 자바 클래스입니다. 클라이언트의 요청을 처리하고, 동적인 웹 콘텐츠를 생성하는 역할을 합니다.
● JSP: HTML 내에 자바 코드를 삽입하여 동적인 웹 페이지를 생성할 수 있게 해주는 기술입니다. JSP는 서블릿의 기능을 좀 더 쉽게 사용할 수 있도록 돕는 역할을 합니다.
2. EJB(Enterprise JavaBeans): EJB는 서버 사이드에서 비즈니스 로직을 처리하기 위한 컴포넌트 기반 아키텍처를 제공합니다. EJB는 트랜잭션 관리, 보안, 원격 호출, 지속성 등 복잡한 엔터프라이즈 애플리케이션에서 필요한 많은 기능을 제공합니다.
3. JMS(Java Message Service): JMS는 자바 애플리케이션 간에 비동기 메시징을 지원하는 API입니다. JMS를 사용하면 애플리케이션이 서로 독립적으로 동작하면서 메시지를 주고받을 수 있습니다.
4. JNDI(Java Naming and Directory Interface): JNDI는 네트워크 상의 자원(예: 데이터베이스, EJB, 메시지 큐 등)을 이름을 통해 찾고 접근할 수 있도록 도와주는 API입니다.
5. JTA(Java Transaction API)와 JTS(Java Transaction Service)
● JTA: 분산 트랜잭션을 관리하기 위한 API입니다. 여러 자원(예: 데이터베이스, 메시지 큐 등)에 걸친 트랜잭션을 하나의 작업으로 처리할 수 있게 해줍니다.
● JTS: 트랜잭션을 관리하고 조정하는 서비스로, JTA의 구현체입니다.
6. JPA(Java Persistence API): JPA는 자바 객체를 관계형 데이터베이스에 매핑하여 데이터베이스의 데이터를 자바 객체로 다룰 수 있게 해주는 표준 API입니다. 이는 데이터베이스 연동을 보다 쉽게 하고, SQL 쿼리를 작성하지 않고도 데이터를 처리할 수 있게 해줍니다.
7. RMI(Remote Method Invocation): RMI는 자바 애플리케이션이 원격으로 다른 자바 객체의 메서드를 호출할 수 있도록 해주는 기술입니다. 이를 통해 분산 애플리케이션을 쉽게 개발할 수 있습니다.
8. JCA(Java Connector Architecture): JCA는 자바 애플리케이션이 엔터프라이즈 정보 시스템(EIS)에 쉽게 연결할 수 있도록 도와주는 표준 API입니다. 이는 다양한 백엔드 시스템과 통합할 수 있는 연결 아키텍처를 제공합니다.
9. Web Services (JAX-RS, JAX-WS): J2EE는 웹 서비스 개발을 위한 API를 제공합니다. JAX-RS는 RESTful 웹 서비스를, JAX-WS는 SOAP 기반 웹 서비스를 개발하는 데 사용됩니다.
또한, 필요한 경우 트랜잭션 조정을 위한 JTA/JCA 설정도 지원합니다.
스프링 프레임워크는 의존성 주입(JSR 330) 및 공통 주석(JSR 250) 스펙도 지원하며, 애플리케이션 개발자는 스프링 프레임워크에서 제공하는 스프링 전용 메커니즘 대신 이를 선택적으로 사용할 수 있습니다. 원래 이들은 공통 javax 패키지를 기반으로 했습니다.
스프링 프레임워크 6.0 버전부터는, 스프링이 jakarta 네임스페이스를 기반으로 하는 Jakarta EE 9 수준(예: 서블릿 5.0+, JPA 3.0+)으로 업그레이드되었습니다. EE 9을 최소한으로 하고 EE 10도 이미 지원하며, Jakarta EE API의 추가 발전을 위해 준비되어 있습니다. 스프링 프레임워크 6.0은 Tomcat 10.1, Jetty 11, Undertow 2.3과 같은 웹 서버 및 Hibernate ORM 6.1과 완벽하게 호환됩니다.
시간이 지나면서, 애플리케이션 개발에서 Java/Jakarta EE의 역할도 진화해 왔습니다. 초기 J2EE와 스프링 시절에는 애플리케이션이 애플리케이션 서버에 배포되도록 만들어졌습니다. 오늘날에는 Spring Boot를 통해, 애플리케이션이 DevOps 및 클라우드 친화적인 방식으로, 서블릿 컨테이너가 내장되어 쉽게 변경 가능하게 만들어집니다. 스프링 프레임워크 5부터는 WebFlux 애플리케이션이 서블릿 API를 직접 사용하지 않으며, Netty와 같은 서블릿 컨테이너가 아닌 서버에서도 실행될 수 있습니다.
디자인 철학
프레임워크를 배울 때, 그것이 무엇을 하는지 뿐만 아니라 어떤 원칙을 따르는지도 아는 것이 중요합니다. 스프링 프레임워크의 주요 원칙은 다음과 같습니다.
- 모든 레벨에서 선택권 제공: 스프링은 가능한 늦게 디자인 결정을 연기할 수 있게 해줍니다. 예를 들어, 코드를 변경하지 않고도 설정을 통해 지속성 제공자를 변경할 수 있습니다. 이는 많은 다른 인프라 문제와 서드파티 API 통합에서도 마찬가지입니다.
- 다양한 관점 수용: 스프링은 유연성을 수용하며, 어떤 방식으로 일을 해야 한다고 강요하지 않습니다. 다양한 관점에서 다양한 애플리케이션 요구 사항을 지원합니다.
- 강력한 역호환성 유지: 스프링의 발전은 버전 간에 최소한의 중단을 강요하도록 신중하게 관리되어 왔습니다. 스프링은 다양한 JDK 버전과 서드파티 라이브러리를 신중하게 선택하여 지원함으로써 스프링에 의존하는 애플리케이션과 라이브러리의 유지 보수를 용이하게 합니다.
- API 디자인에 대한 높은 기준 설정: 스프링 팀은 직관적이고, 여러 버전과 여러 해에 걸쳐 일관성을 유지할 수 있는 API를 만드는 데 많은 시간과 노력을 투자합니다.
- 코드 품질에 대한 높은 기준 설정: 스프링 프레임워크는 의미 있고, 현재의 정확한 javadoc에 강한 중점을 둡니다. 순환 종속성이 없는 깨끗한 코드 구조를 자랑할 수 있는 매우 드문 프로젝트 중 하나입니다.
Spring Core Technologies
Introduction to the Spring IoC Container and Beans
이 챕터에서는 스프링 프레임워크의 제어 역전(Inversion of Control, IoC) 원칙의 구현에 대해 다룹니다. 의존성 주입(Dependency Injection, DI)은 IoC의 한 형태로, 객체가 자신이 사용할 다른 객체들을 생성자 인수, 팩토리 메서드 인수, 또는 객체 인스턴스가 생성된 후 또는 팩토리 메서드에서 반환된 후 설정된 속성을 통해서만 정의합니다. 이후 IoC 컨테이너는 빈을 생성할 때 이러한 의존성을 주입합니다. 이 과정은 본래 빈이 클래스의 직접적인 생성이나 서비스 로케이터 패턴과 같은 메커니즘을 통해 의존성을 제어하는 것과 반대되기 때문에 제어의 역전(Inversion of Control)이라고 불립니다.
org.springframework.beans와 org.springframework.context 패키지는 스프링 프레임워크의 IoC 컨테이너의 기반을 이룹니다. BeanFactory 인터페이스는 모든 유형의 객체를 관리할 수 있는 고급 설정 메커니즘을 제공합니다. ApplicationContext는 BeanFactory의 하위 인터페이스로, 다음과 같은 기능을 추가합니다.
- 스프링의 AOP 기능과의 더 쉬운 통합
- 국제화에 사용될 메시지 리소스 처리
- 이벤트 발행
- 웹 애플리케이션에서 사용할 수 있는 WebApplicationContext와 같은 애플리케이션 계층별 특정 컨텍스트
간단히 말해, BeanFactory는 설정 프레임워크와 기본 기능을 제공하며, ApplicationContext는 더 많은 엔터프라이즈 전용 기능을 추가합니다.
스프링에서 애플리케이션의 핵심을 구성하고 스프링 IoC 컨테이너에 의해 관리되는 객체들을 빈(Bean)이라고 부릅니다. 빈은 스프링 IoC 컨테이너에 의해 인스턴스화되고, 조립되고, 관리되는 객체입니다. 그 외에 빈은 단순히 애플리케이션 내의 여러 객체 중 하나일 뿐입니다. 빈과 그들 간의 의존성은 컨테이너에 의해 사용되는 설정 메타데이터에 반영됩니다.
Container Overview
org.springframework.context.ApplicationContext 인터페이스는 스프링 IoC 컨테이너를 나타내며, 빈(bean)의 인스턴스화, 설정, 그리고 조립을 담당합니다. 이 컨테이너는 구성 메타데이터를 읽어서 어떤 컴포넌트를 인스턴스화하고, 설정하고, 조립할지를 결정합니다. 구성 메타데이터는 주석이 달린 컴포넌트 클래스, 팩토리 메서드가 있는 설정 클래스, 외부 XML 파일 또는 Groovy 스크립트 형태로 표현될 수 있습니다. 이러한 형식 중 하나를 사용해 애플리케이션을 구성하고, 컴포넌트 간의 복잡한 의존성도 설정할 수 있습니다.
ApplicationContext 인터페이스의 여러 구현체는 스프링 코어의 일부입니다. 독립 실행형 애플리케이션에서는 AnnotationConfigApplicationContext나 ClassPathXmlApplicationContext 인스턴스를 생성하는 것이 일반적입니다.
대부분의 애플리케이션 시나리오에서는 스프링 IoC 컨테이너 인스턴스를 명시적으로 생성할 필요가 없습니다. 예를 들어, 일반적인 웹 애플리케이션에서는 web.xml 파일에 간단한 템플릿 웹 디스크립터 XML을 포함하는 것만으로도 충분합니다. 스프링 부트(Spring Boot) 시나리오에서는 애플리케이션 컨텍스트가 일반적인 설정 규칙에 따라 자동으로 부트스트랩됩니다.
아래 다이어그램은 스프링이 작동하는 방식의 상위 수준 개요를 보여줍니다. 애플리케이션 클래스와 구성 메타데이터가 결합되면, ApplicationContext가 생성되고 초기화됩니다. 그 결과로, 완전하게 구성되고 실행 가능한 시스템 또는 애플리케이션이 준비됩니다.
이 과정은 다음과 같은 순서로 진행됩니다:
- 애플리케이션 클래스와 구성 메타데이터 준비: 애플리케이션의 주요 클래스들과 이를 설정할 구성 메타데이터(자바 설정 클래스, XML 설정, 주석 등)를 작성합니다.
- ApplicationContext 생성: 스프링 IoC 컨테이너(ApplicationContext)가 해당 메타데이터를 읽어 애플리케이션의 빈을 인스턴스화하고 필요한 설정을 적용합니다.
- 시스템 또는 애플리케이션 실행: ApplicationContext가 초기화된 후, 모든 의존성이 주입된 상태로 애플리케이션이 실행 가능한 상태가 됩니다.
이로써 스프링은 애플리케이션의 구성 및 실행을 관리하며, 이 과정에서 의존성 주입 및 빈 관리가 자동으로 처리됩니다.
Configuration Metadata
스프링 IoC 컨테이너는 구성 메타데이터(configuration metadata)의 한 형태를 소비합니다. 이 구성 메타데이터는 애플리케이션 개발자가 스프링 컨테이너에 애플리케이션 내의 컴포넌트들을 인스턴스화하고, 설정하고, 조립하는 방법을 지시하는 역할을 합니다.
스프링 IoC 컨테이너 자체는 이 구성 메타데이터가 실제로 어떤 형식으로 작성되었는지에 완전히 구애받지 않습니다. 최근에는 많은 개발자들이 스프링 애플리케이션에서 자바 기반의 구성을 선호합니다:
- 주석 기반 구성(Annotation-based configuration): 애플리케이션의 컴포넌트 클래스에 주석을 사용하여 빈을 정의합니다.
- 자바 기반 구성(Java-based configuration): 애플리케이션 클래스 외부에서 자바 기반 설정 클래스를 사용하여 빈을 정의합니다. (ex. @Configuration, @Bean, @Import, @DependsOn )
스프링 구성은 최소한 하나 이상의 빈 정의(bean definition)로 구성되며, 일반적으로 컨테이너가 관리해야 할 빈 정의가 여러 개 있습니다. 자바 구성에서는 주로 @Configuration 클래스 내의 @Bean 주석이 달린 메서드를 사용하며, 각 메서드는 하나의 빈 정의에 해당합니다.
이러한 빈 정의는 애플리케이션을 구성하는 실제 객체들에 해당합니다. 일반적으로 서비스 계층의 객체, 리포지토리 또는 DAO(Data Access Object)와 같은 영속성 계층 객체, 웹 컨트롤러와 같은 프레젠테이션 객체, JPA EntityManagerFactory, JMS 큐 등 인프라 객체를 정의합니다. 도메인 객체와 같은 세분화된 객체는 일반적으로 컨테이너에서 구성하지 않으며, 이는 주로 리포지토리와 비즈니스 로직에서 도메인 객체를 생성하고 로드하는 책임을 집니다.
XML을 외부 구성 DSL로 사용하는 방법
XML 기반 구성 메타데이터는 <beans/> 요소 내에 여러 개의 <bean/> 요소로 빈을 설정합니다. 아래는 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- 이 빈의 협력자와 구성을 여기에 추가 -->
</bean>
<bean id="..." class="...">
<!-- 이 빈의 협력자와 구성을 여기에 추가 -->
</bean>
<!-- 추가 빈 정의 -->
</beans>
- id 속성: 빈 정의를 개별적으로 식별하는 문자열입니다.
- class 속성: 빈의 타입을 정의하며, 완전한 클래스 이름을 사용합니다.
- ref 요소: 협력 객체를 참조하는데 사용됩니다.
예를 들어, id 속성은 다른 객체 간의 협력 관계를 나타내며, ref 요소는 협력 객체를 참조합니다. 빈을 협력 객체로 참조하는 XML은 이 예시에는 나타나지 않았으며, 협력 객체에 대한 자세한 내용은 의존성 설정 섹션에서 설명됩니다.
컨테이너 인스턴스화
IoC 컨테이너를 인스턴스화하려면 XML 리소스 파일의 경로를 ClassPathXmlApplicationContext 생성자에 전달해야 합니다. 이렇게 하면 컨테이너가 로컬 파일 시스템, 자바 클래스패스(CLASS_PATH) 등 다양한 외부 리소스에서 구성 메타데이터를 로드할 수 있습니다.
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
이렇게 services.xml과 daos.xml 파일을 사용해 구성된 컨텍스트를 인스턴스화합니다.
스프링의 리소스 추상화(Resource)는 URI 구문으로 정의된 위치에서 입력 스트림을 읽는 편리한 메커니즘을 제공합니다. Resource 경로는 애플리케이션 컨텍스트를 구성하는 데 사용되며, 이는 리소스 경로 및 애플리케이션 컨텍스트 섹션에서 더 자세히 설명됩니다.
서비스 계층 설정 (services.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 서비스 설정 -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- 추가적인 협력자 및 구성 -->
</bean>
<!-- 추가 서비스 빈 정의 -->
</beans>
이 예에서는 PetStoreServiceImpl 클래스가 서비스 계층을 구성하며, 두 개의 DAO(Data Access Object)를 사용합니다. accountDao와 itemDao는 각각 다른 빈 정의에 대한 참조입니다.
데이터 액세스 객체 설정 (daos.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- 추가적인 협력자 및 구성 -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- 추가적인 협력자 및 구성 -->
</bean>
<!-- 추가 DAO 빈 정의 -->
</beans>
이 예시에서, JpaAccountDao와 JpaItemDao는 JPA(Object-Relational Mapping 표준)에 기반한 데이터 액세스 객체입니다.
XML 기반 구성 메타데이터 구성
스프링에서 빈 정의를 여러 XML 파일에 걸쳐 분할하는 것이 유용할 수 있습니다. 일반적으로 각 개별 XML 구성 파일은 애플리케이션 아키텍처의 논리적 계층이나 모듈을 나타냅니다.
빈 정의의 여러 XML 파일 로드
ClassPathXmlApplicationContext 생성자를 사용하여 여러 XML 파일에서 빈 정의를 로드할 수 있습니다. 이 생성자는 여러 리소스 경로를 받아들일 수 있으며, 이를 통해 다양한 XML 조각에서 빈 정의를 로드할 수 있습니다.
또한 <import/> 요소를 사용하여 다른 파일에서 빈 정의를 가져올 수도 있습니다. 아래 예시는 이를 보여줍니다.
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
위 예시에서, 외부 빈 정의는 services.xml, messageSource.xml, themeSource.xml 파일에서 로드됩니다. 모든 경로는 해당 파일을 가져오는 정의 파일과 상대적인 경로로 해석됩니다. 즉, services.xml 파일은 현재 XML 파일과 동일한 디렉터리 또는 클래스패스에 위치해야 하며, messageSource.xml과 themeSource.xml 파일은 현재 파일이 위치한 디렉터리 아래의 resources 폴더에 있어야 합니다.
경로의 선행 슬래시는 무시되지만, 상대 경로를 사용할 경우 슬래시를 생략하는 것이 더 좋은 형태입니다. 가져오는 파일의 내용은 최상위 <beans/> 요소를 포함해 스프링 스키마에 따라 유효한 XML 빈 정의여야 합니다.
상대 경로의 사용
상위 디렉터리를 참조하는 상대 경로(../ 경로)를 사용할 수도 있지만, 이는 권장되지 않습니다. 이렇게 하면 현재 애플리케이션 외부의 파일에 의존성이 생길 수 있습니다. 특히 클래스패스 URL(classpath:../services.xml)에서 이러한 참조는 권장되지 않습니다. 클래스패스 구성의 변경으로 인해 잘못된 디렉터리가 선택될 수 있기 때문입니다.
절대 경로의 사용
항상, 상대 경로 대신 절대 리소스 경로를 사용할 수 있습니다.
- file:C:/config/services.xml
- classpath:/config/services.xml
하지만 이 방법은 애플리케이션 구성과 특정 절대 위치를 결합시키므로 주의해야 합니다. 이러한 절대 경로에 대한 간접 참조를 유지하는 것이 일반적으로 더 좋습니다. 예를 들어, JVM 시스템 속성을 런타임에 참조하여 ${...} 플레이스홀더를 사용하는 방식이 있습니다.
네임스페이스와 추가 기능
스프링 XML 네임스페이스는 기본 빈 정의 외에도 여러 가지 추가 기능을 제공합니다. 예를 들어, context 네임스페이스와 util 네임스페이스를 사용하면 더 다양한 설정 기능을 활용할 수 있습니다.
Groovy 빈 정의 DSL
스프링은 Grails 프레임워크에서 사용되는 Groovy 빈 정의 DSL(도메인 특화 언어)을 지원하여 외부화된 구성 메타데이터를 정의할 수 있습니다. 이 방식으로 빈 정의는 .groovy 파일에 작성되며, 다음과 같은 구조를 가집니다:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
주요 요소 설명
- beans 블록: 빈 정의가 포함된 루트 블록입니다.
- 빈 정의: 각 빈은 클래스 이름과 함께 정의됩니다. 예를 들어, dataSource 빈은 BasicDataSource 클래스를 사용하며, driverClassName, url, username, password 등의 속성을 설정합니다.
- 빈 간의 의존성: 예를 들어, sessionFactory 빈은 dataSource 빈에 의존하며, 이를 통해 빈 간의 의존성이 설정됩니다.
- 중첩 빈 정의: myService 빈의 nestedBean 속성은 다른 빈을 중첩해서 정의할 수 있도록 합니다. 이 경우, AnotherBean을 정의하면서 dataSource를 설정합니다.
Groovy DSL과 XML 정의의 동등성
이 Groovy 구성 스타일은 XML 기반의 빈 정의와 거의 동등한 역할을 하며, 스프링의 XML 구성 네임스페이스도 지원합니다. 즉, XML에서 제공되는 대부분의 기능을 Groovy DSL에서도 사용할 수 있습니다.
importBeans 지시어
이 구성 스타일은 또한 importBeans 지시어를 통해 XML 빈 정의 파일을 가져올 수 있습니다. 이는 XML 파일과 Groovy 파일을 함께 사용하여 구성 메타데이터를 정의할 수 있음을 의미합니다.
Groovy 빈 정의 DSL은 간결하고 유연하며, 스프링 애플리케이션의 빈 정의를 코드처럼 작성할 수 있는 편리한 방법을 제공합니다.
Using the Container
ApplicationContext는 다양한 빈과 그 의존성을 유지 관리할 수 있는 고급 팩토리 인터페이스입니다. T getBean(String name, Class<T> requiredType) 메서드를 사용하여 빈 인스턴스를 가져올 수 있습니다.
빈 인스턴스 가져오기
아래 예시는 ApplicationContext를 통해 빈 정의를 읽고, 해당 빈을 가져와 사용하는 방법을 보여줍니다.
// 빈 생성 및 구성
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 구성된 인스턴스 가져오기
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 구성된 인스턴스 사용
List<String> userList = service.getUsernameList();
이 예에서 services.xml과 daos.xml 파일에 정의된 빈을 context 객체를 통해 로드한 후, 빈 인스턴스인 PetStoreService를 가져와서 사용합니다.
Groovy 설정을 사용하는 경우
Groovy 설정을 사용하는 경우 부트스트래핑 방식은 거의 동일합니다. Groovy 설정에 대해 Groovy를 인식하는 컨텍스트 구현 클래스를 사용합니다.
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
다양한 설정 파일 읽기
가장 유연한 방법은 GenericApplicationContext와 함께 리더 델리게이트를 사용하는 것입니다. 예를 들어, XML 파일을 위한 XmlBeanDefinitionReader와 함께 사용할 수 있습니다.
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
Groovy 파일을 위한 GroovyBeanDefinitionReader도 사용할 수 있습니다.
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
이처럼 동일한 ApplicationContext에서 여러 설정 소스로부터 빈 정의를 읽어들이기 위해 다양한 리더 델리게이트를 혼합하여 사용할 수 있습니다.
빈 인스턴스 가져오기 (getBean)
이후, getBean() 메서드를 사용하여 빈 인스턴스를 가져올 수 있습니다. 하지만 이상적으로는 애플리케이션 코드에서 getBean() 메서드를 호출하지 않아야 하며, 스프링 API에 의존하지 않도록 설계해야 합니다. 스프링은 웹 프레임워크와 통합되어 다양한 웹 프레임워크 컴포넌트(예: 컨트롤러, JSF 관리 빈 등)에 대한 의존성 주입을 제공합니다. 이를 통해 애플리케이션 코드에서 메타데이터(예: 자동 연결 주석)를 통해 특정 빈에 대한 의존성을 선언할 수 있습니다.
스프링의 장점은 빈 인스턴스를 직접 관리할 필요 없이 의존성 주입을 통해 필요한 의존성을 선언할 수 있다는 것입니다. 이를 통해 애플리케이션 코드가 스프링에 직접적으로 의존하지 않고도 동작할 수 있게 됩니다.
Bean Overview
Spring IoC 컨테이너와 빈(Bean) 정의 관리
스프링 IoC 컨테이너는 하나 이상의 빈을 관리합니다. 이 빈들은 XML <bean/> 정의와 같은 구성 메타데이터에 따라 생성됩니다. 컨테이너 내에서 이러한 빈 정의는 BeanDefinition 객체로 표현되며, 다음과 같은 메타데이터를 포함합니다:
- 패키지로 구분된 클래스 이름: 주로 빈의 실제 구현 클래스입니다.
- 빈의 동작 구성 요소: 빈이 컨테이너 내에서 어떻게 동작해야 하는지(스코프, 라이프사이클 콜백 등)를 정의합니다.
- 협력자 또는 의존성: 해당 빈이 작업을 수행하는 데 필요한 다른 빈에 대한 참조를 포함합니다.
- 기타 설정 값: 새로 생성된 객체에 설정할 추가 구성(예: 커넥션 풀을 관리하는 빈의 경우 풀의 크기 제한이나 사용해야 할 커넥션 수 등)을 정의합니다.
이 메타데이터는 각 빈 정의를 구성하는 속성들의 집합으로 변환됩니다. 아래는 이러한 속성들에 대한 설명입니다.
Bean 정의 속성들
- Class: 빈을 인스턴스화하는 데 사용되는 실제 클래스입니다.
- Name: 빈의 이름을 정의합니다.
- Scope: 빈의 스코프를 지정합니다. (예: singleton, prototype)
- Constructor arguments: 의존성 주입을 위한 생성자 인수입니다.
- Properties: 의존성 주입을 위한 속성들입니다.
- Autowiring mode: 자동 주입 방법을 지정합니다.
- Lazy initialization mode: 지연 초기화 여부를 지정합니다.
- Initialization method: 빈 초기화 시 호출할 메서드입니다.
- Destruction method: 빈 소멸 시 호출할 메서드입니다.
기존 객체의 등록
ApplicationContext 구현체들은 컨테이너 외부에서 생성된 기존 객체를 등록하는 기능도 제공합니다. 이는 getBeanFactory() 메서드를 통해 ApplicationContext의 BeanFactory에 접근하여 가능합니다. 이 메서드는 DefaultListableBeanFactory 구현체를 반환하며, 해당 구현체는 registerSingleton(..) 및 registerBeanDefinition(..) 메서드를 통해 이러한 등록을 지원합니다.
주의사항
- 빈 메타데이터와 수동으로 제공된 싱글톤 인스턴스는 가능한 한 일찍 등록되어야 합니다. 그래야 컨테이너가 의존성 주입과 다른 검토 단계를 제대로 수행할 수 있습니다.
- 기존 메타데이터와 기존 싱글톤 인스턴스를 어느 정도 재정의하는 것은 가능하지만, 런타임에 새로운 빈을 등록하는 것은 공식적으로 지원되지 않습니다. 런타임 동안 컨테이너에 액세스하면서 새로운 빈을 등록하면 동시성 문제나 빈 컨테이너의 일관성 저하가 발생할 수 있습니다.
이러한 메커니즘은 스프링 IoC 컨테이너가 빈을 생성, 관리 및 조립하는 방식을 정의하고, 애플리케이션이 안정적으로 실행될 수 있도록 돕습니다.
Overriding Beans
빈 오버라이딩은 이미 할당된 식별자를 사용하여 빈이 다시 등록될 때 발생합니다. 빈 오버라이딩이 가능하긴 하지만, 설정을 복잡하게 만들 수 있습니다. 또한, 이 기능은 앞으로의 릴리스에서 폐기될 예정입니다.
빈 오버라이딩 비활성화
빈 오버라이딩을 완전히 비활성화하려면, ApplicationContext가 리프레시되기 전에 allowBeanDefinitionOverriding 플래그를 false로 설정할 수 있습니다. 이 경우, 빈 오버라이딩이 발생하면 예외가 발생합니다.
기본 동작
기본적으로 컨테이너는 빈 오버라이딩이 발생할 때마다 INFO 레벨로 로그를 기록하여 설정을 조정할 수 있도록 도와줍니다. 권장되지는 않지만, 로그 출력을 차단하려면 allowBeanDefinitionOverriding 플래그를 true로 설정할 수 있습니다.
자바 구성 (Java Configuration)
자바 기반 구성에서, 동일한 컴포넌트 이름을 가진 스캔된 빈 클래스를 @Bean 메서드가 항상 조용히 오버라이드합니다. @Bean 메서드의 반환 타입이 해당 빈 클래스와 일치하면 컨테이너는 빈 클래스의 미리 선언된 생성자보다 @Bean 팩토리 메서드를 우선 호출하게 됩니다.
즉, @Bean 메서드로 명시적으로 정의된 빈은 같은 이름의 자동 스캔된 빈을 대체할 수 있으며, 이는 설정 단계에서 알아야 할 중요한 부분입니다.
빈 네이밍 (Naming Beans)
스프링에서 각 빈(Bean)은 하나 이상의 식별자를 가집니다. 이 식별자는 해당 빈을 호스팅하는 컨테이너 내에서 고유해야 합니다. 일반적으로 빈은 하나의 식별자만 가지지만, 필요에 따라 여러 식별자를 가질 수도 있으며, 이 경우 추가 식별자는 별칭(Aliases)으로 간주됩니다.
XML 기반 구성 메타데이터에서 빈 이름 지정
- id 속성: 빈에 대해 정확히 하나의 ID를 지정합니다. 이 이름은 알파벳과 숫자의 조합으로 일반적으로 사용되며(myBean, someService 등), 특수 문자도 포함할 수 있습니다.
- name 속성: 여러 별칭을 지정할 수 있으며, 별칭들은 쉼표(,), 세미콜론(;) 또는 공백으로 구분됩니다.
빈의 ID는 xsd:string 타입으로 정의되어 있지만, 빈 ID의 고유성은 XML 파서가 아니라 컨테이너에 의해 보장됩니다.
빈 이름 또는 ID 지정 여부
빈에 대해 이름이나 ID를 명시적으로 지정할 필요는 없습니다. 만약 이름이나 ID를 지정하지 않으면 컨테이너가 빈을 위한 고유한 이름을 자동으로 생성합니다. 하지만 빈을 이름으로 참조하려면(예: ref 요소를 사용하거나 서비스 로케이터 스타일의 조회를 할 때), 반드시 이름을 제공해야 합니다.
빈에 이름을 제공하지 않는 주된 이유는 내부 빈(Inner Beans) 및 자동 연결(Autowiring) 협력자를 사용하는 경우와 관련이 있습니다.
빈 네이밍 규칙
스프링에서는 빈 이름을 정할 때 자바의 인스턴스 필드 네이밍 규칙을 따르는 것이 일반적입니다. 즉, 빈 이름은 소문자로 시작하며, 카멜케이스(camelCase)로 이어집니다. 예를 들면 accountManager, accountService, userDao, loginController와 같은 이름이 있습니다.
이러한 규칙을 따르면 구성의 가독성이 향상되고, 특히 Spring AOP를 사용할 때 이름으로 관련된 빈들에 어드바이스를 적용할 때 유용합니다.
컴포넌트 스캐닝에서의 Bean 이름
클래스패스 내의 컴포넌트를 자동 스캔할 때, 스프링은 명시적으로 이름이 지정되지 않은 컴포넌트에 대해 기본 네이밍 규칙에 따라 빈 이름을 생성합니다. 일반적으로 클래스의 단순 이름에서 첫 글자를 소문자로 변환하여 이름을 생성합니다.
- 예외적인 경우: 첫 두 글자가 모두 대문자인 경우, 원래 대문자가 유지됩니다. 이러한 규칙은 java.beans.Introspector.decapitalize 메서드의 규칙과 동일하며, 스프링도 이 규칙을 사용합니다.
빈 외부에서의 빈 별칭 설정 (Aliasing a Bean outside the Bean Definition)
빈 정의 내에서는 id 속성과 name 속성을 조합하여 하나의 빈에 대해 여러 이름(별칭)을 제공할 수 있습니다. 이러한 별칭은 빈을 참조할 때 유용하며, 애플리케이션의 각 구성 요소가 각기 다른 이름으로 동일한 의존성을 참조할 수 있도록 합니다.
빈 외부에서 별칭 설정
빈이 정의된 위치에서 모든 별칭을 미리 지정하는 것이 항상 적합하지 않을 수 있습니다. 특히 큰 시스템에서는 구성 파일이 각 서브시스템에 따라 분리되어 있으며, 각 서브시스템이 자체 객체 정의를 가지고 있는 경우가 많습니다. 이러한 상황에서는 빈 정의 외부에서 별칭을 추가하는 것이 바람직할 수 있습니다. 이를 위해 XML 기반 구성 메타데이터에서 <alias/> 요소를 사용합니다.
<alias name="fromName" alias="toName"/>
이 경우, 동일한 컨테이너 내에서 fromName으로 정의된 빈은 이제 toName이라는 별칭으로도 참조할 수 있습니다.
활용 예시
예를 들어, 서브시스템 A는 subsystemA-dataSource라는 이름의 DataSource를 사용하고, 서브시스템 B는 subsystemB-dataSource라는 이름의 DataSource를 사용한다고 가정합니다. 메인 애플리케이션은 이 두 서브시스템을 사용하며, myApp-dataSource라는 이름으로 DataSource를 참조합니다. 모든 구성 요소가 같은 DataSource를 참조하도록 하려면 다음과 같은 별칭 정의를 추가할 수 있습니다.
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
이제 각 구성 요소와 메인 애플리케이션은 자신만의 고유한 이름으로 빈을 참조할 수 있으며, 이러한 이름은 충돌하지 않으면서도 동일한 빈을 참조하게 됩니다. 이 방식은 사실상 네임스페이스를 만들어 구성의 유연성을 제공합니다.
자바 구성
자바 기반 구성에서 별칭을 제공하려면 @Bean 어노테이션을 사용할 수 있습니다. 이 방법을 사용하면 XML에서 <alias/>를 사용하는 것과 같은 방식으로 빈의 별칭을 설정할 수 있습니다.
Instantiating Beans
스프링에서 빈 정의는 하나 이상의 객체를 생성하기 위한 일종의 "레시피"로 간주됩니다. IoC 컨테이너는 특정 빈이 요청될 때 빈 정의에서 제공되는 메타데이터를 사용하여 실제 객체를 생성하거나 획득합니다.
생성자를 통한 인스턴스화
가장 일반적인 방식은 빈 정의에서 빈의 클래스를 class 속성으로 지정하고, 스프링 컨테이너가 이를 통해 생성자를 호출하여 빈을 인스턴스화하는 것입니다.
<bean id="exampleBean" class="examples.ExampleBean"/>
- 기본 생성자: 대부분의 스프링 사용자는 기본 생성자(파라미터가 없는 생성자)와 getter/setter 메서드로 구성된 자바 빈(JavaBean)을 선호합니다. 하지만 스프링은 비자바빈 스타일의 클래스도 관리할 수 있습니다.
- 오버로드된 생성자: 여러 생성자가 오버로드된 경우, 컨테이너는 적절한 생성자를 선택합니다. 다만, 모호성을 피하기 위해 가능한 단순한 시그니처를 유지하는 것이 좋습니다.
정적 팩토리 메서드를 통한 인스턴스화
또 다른 방법은 정적 팩토리 메서드를 사용하여 객체를 생성하는 것입니다. 이 경우 class 속성으로 팩토리 메서드를 포함하는 클래스를 지정하고, factory-method 속성으로 호출할 정적 메서드를 지정합니다.
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
이 클래스는 다음과 같이 작성될 수 있습니다.
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
인스턴스 팩토리 메서드를 통한 인스턴스화
정적 팩토리 메서드 대신, 컨테이너에서 관리하는 기존 빈의 인스턴스 메서드를 호출하여 새로운 빈을 생성할 수도 있습니다. 이 경우 class 속성은 비워 두고, factory-bean 속성에 팩토리 메서드를 포함하는 기존 빈의 ID를 지정한 후, factory-method 속성에 호출할 메서드를 지정합니다.
<!-- 팩토리 빈 정의 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator"/>
<!-- 팩토리 메서드를 사용하여 생성된 빈 -->
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
이 클래스는 다음과 같이 작성될 수 있습니다.
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
하나의 팩토리 빈에서 여러 팩토리 메서드를 제공하여 다양한 객체를 생성할 수 있습니다. 예를 들어 DefaultServiceLocator는 ClientService뿐만 아니라 AccountService도 생성할 수 있습니다.
Bean 의 런타임 타입 결정
빈의 런타임 타입을 정확히 파악하는 것은 쉽지 않을 수 있습니다. 스프링 빈 메타데이터에서 지정된 클래스는 초기 클래스 참조일 뿐이며, 실제 런타임 타입과는 다를 수 있습니다. 그 이유는 다음과 같습니다.
- 팩토리 메서드: 빈 메타데이터에 클래스가 명시되어 있더라도, 팩토리 메서드를 사용하여 빈이 생성되는 경우, 반환되는 객체의 실제 타입은 해당 클래스가 아닐 수 있습니다.
- FactoryBean: 빈이 FactoryBean일 경우, FactoryBean 자체가 아니라 그 FactoryBean이 생성하는 객체가 실제 빈으로 반환됩니다.
- 인스턴스 팩토리 메서드: 클래스 속성이 설정되지 않고, 대신 factory-bean 속성을 사용하여 다른 빈의 인스턴스 메서드를 호출해 빈이 생성되는 경우도 있습니다.
- AOP 프록시: 스프링 AOP 기능이 활성화된 경우, 빈이 프록시 객체로 감싸져 인터페이스 기반 프록시로 반환될 수 있습니다. 이 경우 실제 구현 클래스는 숨겨지고, 인터페이스만 노출됩니다.
런타임 타입 확인 방법
빈의 실제 런타임 타입을 확인하는 가장 확실한 방법은 BeanFactory.getType(String beanName) 메서드를 사용하는 것입니다. 이 메서드는 위의 모든 경우를 고려하여, 해당 빈 이름으로 getBean()을 호출했을 때 반환될 객체의 실제 타입을 반환합니다.
BeanFactory factory = ...; // BeanFactory 인스턴스
Class<?> beanType = factory.getType("myBean");
System.out.println("The runtime type of the bean is: " + beanType.getName());
이 방법은 빈의 정의에 관계없이 실제 런타임 타입을 정확히 알려주므로, 빈의 클래스를 동적으로 확인해야 하는 상황에서 유용합니다.
Dependencies
일반적인 엔터프라이즈 애플리케이션은 단일 객체(또는 스프링의 용어로 빈)로 구성되지 않습니다. 가장 간단한 애플리케이션조차도 몇 개의 객체들이 협력하여 사용자에게 하나의 일관된 애플리케이션으로 보이게 만듭니다.
의존성 주입(Dependency Injection, DI)
의존성 주입은 객체가 협력할 다른 객체에 대한 의존성을 직접 관리하지 않고, 생성자 인수, 팩토리 메서드 인수, 또는 객체 인스턴스의 속성 설정을 통해서만 정의하는 방식입니다. 스프링 컨테이너는 빈을 생성할 때 이러한 의존성을 주입하여 객체 간의 결합을 줄이고 더 깔끔한 코드를 작성할 수 있게 합니다.
주요 DI 방법
생성자 기반 의존성 주입(Constructor-based DI):
- 빈이 생성될 때 의존성을 주입합니다. 생성자 인수로 의존성을 제공하여 객체를 생성합니다.
- 이 방법은 객체를 불변(immutable)으로 만들고, 모든 필수 의존성을 보장할 수 있다는 장점이 있습니다.
public class SimpleMovieLister {
private final MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
<bean id="simpleMovieLister" class="examples.SimpleMovieLister">
<constructor-arg ref="movieFinder"/>
</bean>
세터 기반 의존성 주입(Setter-based DI):
- 빈이 생성된 후 세터 메서드를 사용하여 의존성을 주입합니다.
- 주로 선택적 의존성을 처리할 때 유용합니다. 의존성이 없어도 합리적인 기본값을 사용할 수 있는 경우 유리합니다.
public class SimpleMovieLister {
private MovieFinder movieFinder;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
<bean id="simpleMovieLister" class="examples.SimpleMovieLister">
<property name="movieFinder" ref="movieFinder"/>
</bean>
생성자 인수 처리
생성자 인수의 타입 매칭:
- 스프링은 인수의 타입을 사용하여 적절한 생성자를 선택합니다.
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
인덱스 기반 매칭:
- 생성자 인수의 인덱스를 지정하여 매칭할 수 있습니다.
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
이름 기반 매칭:
- 생성자 인수의 이름을 기반으로 의존성을 주입할 수 있습니다.
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
팩토리 메서드를 통한 빈 생성
정적 팩토리 메서드를 사용한 인스턴스화:
- 팩토리 메서드를 사용하여 객체를 생성하는 방법입니다.
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
public class ClientService {
private static ClientService instance = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return instance;
}
}
인스턴스 팩토리 메서드를 사용한 인스턴스화:
- 기존 빈의 인스턴스 메서드를 사용하여 객체를 생성할 수 있습니다.
<bean id="serviceLocator" class="examples.DefaultServiceLocator"/>
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
public ClientService createClientServiceInstance() {
return new ClientService();
}
}
순환 의존성(Circular Dependency)
- 생성자 주입을 주로 사용하는 경우 순환 의존성이 발생할 수 있습니다. 예를 들어, 클래스 A가 클래스 B를 생성자 주입받고, 클래스 B가 다시 클래스 A를 생성자 주입받는 경우입니다.
- 스프링은 이런 상황에서 BeanCurrentlyInCreationException을 발생시킵니다. 해결 방법은 세터 주입으로 전환하거나 소스 코드를 리팩터링하는 것입니다.
DI의 장점
- 테스트 용이성: DI를 통해 객체가 직접 의존성을 관리하지 않으므로, 모의 객체(mock objects)를 사용하여 유닛 테스트가 더 쉬워집니다.
- 유연성: DI는 객체 간의 결합을 줄여 더 유연한 설계를 가능하게 합니다.
- 재사용성: 객체 간의 의존성이 느슨해지므로, 재사용이 더 용이해집니다.
스프링 의존성 주입 및 구성 상세 설명
이 섹션에서는 스프링에서 빈(Bean)의 속성 및 생성자 인수를 설정하는 방법에 대해 설명합니다. 빈의 속성이나 생성자 인수는 다른 관리되는 빈(협력자) 또는 인라인으로 정의된 값일 수 있습니다. 스프링의 XML 기반 구성 메타데이터는 이를 처리하기 위한 <property/>와 <constructor-arg/> 요소를 지원합니다.
기본값 설정(Primitive, String 등)
스프링에서는 <property/> 요소의 value 속성을 사용하여 속성이나 생성자 인수를 문자열로 지정할 수 있습니다. 스프링의 변환 서비스는 이 문자열 값을 해당 속성이나 인수의 실제 타입으로 변환합니다.
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<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="misterkaoli"/>
</bean>
p-네임스페이스를 이용한 간결한 설정
p-네임스페이스를 사용하면 XML 구성을 더 간결하게 작성할 수 있습니다.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
p-네임스페이스를 사용하면 코드가 간결해지지만, 오타는 런타임에서만 발견될 수 있습니다. 자동 완성 기능을 제공하는 IDE를 사용하는 것이 좋습니다.
java.util.Properties 인스턴스 구성
java.util.Properties 인스턴스를 설정하는 방법입니다.
<bean id="mappings" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
스프링 컨테이너는 <value/> 요소 내부의 텍스트를 java.util.Properties 인스턴스로 변환합니다.
idref 요소
idref 요소는 다른 빈의 ID(문자열 값, 참조 아님)를 전달하는 안전한 방법입니다.
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
이 방법은 빈이 존재하는지 유효성 검사를 수행하므로 안전합니다.
다른 빈에 대한 참조
ref 요소는 다른 빈(협력자)을 참조하는 방법입니다. 이를 통해 하나의 빈이 다른 빈을 의존성으로 설정할 수 있습니다.
<bean id="clientService" class="...">
<property name="accountDao">
<ref bean="accountDao"/>
</property>
</bean>
이 방식은 의존성 주입을 통해 협력자 빈을 설정합니다.
내부 빈(Inner Beans)
<bean/> 요소 내부에서 다른 빈을 정의하는 경우, 이를 내부 빈이라고 합니다. 내부 빈은 ID가 필요하지 않으며, 오직 외부 빈에 의해서만 사용됩니다.
<bean id="outer" class="...">
<property name="target">
<bean class="com.example.Person">
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
컬렉션 (Collections) 설정
스프링에서는 <list/>, <set/>, <map/>, <props/> 요소를 사용하여 자바 컬렉션 타입인 List, Set, Map, Properties를 설정할 수 있습니다. 이 요소들은 스프링 빈의 속성이나 생성자 인수로 사용됩니다. 아래는 이러한 컬렉션 요소들을 사용하는 예시입니다.
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- java.util.Properties에 값을 설정 -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- java.util.List에 값을 설정 -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource"/>
</list>
</property>
<!-- java.util.Map에 값을 설정 -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- java.util.Set에 값을 설정 -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource"/>
</set>
</property>
</bean>
이 예시에서 각 컬렉션 타입에 값을 설정하고, 필요한 경우 다른 빈에 대한 참조를 사용합니다. 예를 들어, adminEmails 속성은 Properties로, someList는 List, someMap은 Map, 그리고 someSet은 Set으로 설정됩니다.
값 유형
Map의 키나 값, Set의 값 등에는 다양한 요소를 사용할 수 있습니다.
- bean
- ref
- idref
- list
- set
- map
- props
- value
- null
컬렉션 병합 (Collection Merging)
스프링 컨테이너는 컬렉션 병합을 지원합니다. 부모 빈의 컬렉션을 자식 빈에서 상속받아 병합할 수 있으며, 자식 컬렉션의 값이 부모 컬렉션의 값을 덮어씁니다.
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
</beans>
이 예시에서 child 빈의 adminEmails 속성은 parent 빈의 adminEmails 속성을 상속받으며, 자식 값이 부모 값을 덮어씁니다. 결과는 다음과 같습니다:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
컬렉션 병합의 제한 사항
- 서로 다른 컬렉션 타입 간의 병합은 허용되지 않습니다. 예를 들어, Map과 List를 병합하려고 하면 예외가 발생합니다.
- 병합 속성은 자식 정의에만 지정되어야 합니다. 부모 정의에서 병합 속성을 지정하는 것은 중복되며, 원하는 병합 결과를 얻을 수 없습니다.
강력한 타입의 컬렉션
자바 제네릭스를 사용하면 강력한 타입의 컬렉션을 선언할 수 있습니다. 스프링은 이러한 컬렉션을 주입할 때 타입 변환을 자동으로 처리해줍니다.
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
스프링은 제네릭 정보를 사용하여 문자열 값을 Float 타입으로 변환합니다.
Null 및 빈 문자열 값 설정
스프링에서는 속성 값이 비어 있는 경우, 이를 빈 문자열로 처리합니다. 또한, 속성 값을 null로 설정할 수 있도록 <null/> 요소를 제공합니다.
빈 문자열 설정
스프링에서 빈 문자열 값을 설정하는 방법은 다음과 같습니다. 아래 XML 기반의 설정 예시는 email 속성을 빈 문자열 값으로 설정하는 방식입니다.
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
// 위 설정은 다음 자바 코드와 동일
exampleBean.setEmail("");
Null 값 설정
<null/> 요소를 사용하여 속성 값을 null로 설정할 수 있습니다. 다음 예시는 email 속성을 null로 설정하는 방법을 보여줍니다.
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
// 위 설정은 다음 자바 코드와 동일합니다.
exampleBean.setEmail(null);
p-네임스페이스를 사용한 XML 단축 구성 (XML Shortcut with the p-namespace)
p-네임스페이스는 XML에서 빈의 속성 값을 더 간결하게 설정할 수 있도록 하는 스프링의 편의 기능입니다. 일반적으로 <property/> 요소를 사용하여 속성을 설정하지만, p-네임스페이스를 사용하면 빈 정의의 속성으로 이를 설정할 수 있습니다.
스프링은 확장 가능한 구성 형식을 지원하는데, 이는 XML 스키마 정의(XML Schema Definition, XSD)를 기반으로 합니다. 그러나 p-네임스페이스는 XSD 파일에서 정의되지 않으며, 스프링 코어에만 존재합니다.
기본 사용 예시
아래 두 가지 예시는 같은 결과를 내지만, 첫 번째는 표준 XML 형식을 사용하고, 두 번째는 p-네임스페이스를 사용한 것입니다.
표준 XML 형식
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
</beans>
p-네임스페이스 사용
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
이 예시에서 p:email="someone@somewhere.com"은 스프링에게 email 속성에 값을 설정하라는 의미를 전달합니다. p-네임스페이스는 속성 이름이 바로 속성 값으로 연결되며, XSD 스키마에 의해 정의되지 않지만 스프링이 이를 처리합니다.
빈 참조 사용 예시
다음 예시는 다른 빈을 참조하는 두 가지 방식(표준 방식과 p-네임스페이스 방식)을 보여줍니다.
표준 XML 형식
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
p-네임스페이스 사용
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
이 예시에서는 spouse 속성이 빈 참조를 가리키고 있습니다. 표준 방식에서는 <property name="spouse" ref="jane"/>를 사용하지만, p-네임스페이스에서는 p:spouse-ref="jane"로 같은 효과를 얻습니다. 여기서 -ref 접미어는 값이 아니라 다른 빈을 참조한다는 것을 나타냅니다.
p-네임스페이스의 한계
p-네임스페이스는 간편하지만 표준 XML 형식만큼 유연하지는 않습니다. 예를 들어, 속성 이름이 Ref로 끝나는 경우 참조 선언 형식과 충돌할 수 있습니다. 또한, XML 문서에서 여러 접근 방식을 혼합하여 사용할 경우 복잡해질 수 있으므로, 팀원들과 협의하여 일관된 스타일을 유지하는 것이 중요합니다.
c-네임스페이스를 사용한 XML 단축 구성 (XML Shortcut with the c-namespace)
스프링 3.1에서 도입된 c-네임스페이스는 생성자 인수를 구성할 때, 기존의 <constructor-arg> 요소 대신 속성으로 직접 설정할 수 있도록 지원하는 기능입니다. 이는 p-네임스페이스와 유사하지만, 속성 값을 설정하는 대신 생성자 인수를 설정하는 데 사용됩니다.
c-네임스페이스 사용 예시
전통적인 방식 (Constructor-based Dependency Injection):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- 전통적인 생성자 주입 방식 -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
</beans>
c-네임스페이스를 사용한 방식:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- c-네임스페이스를 사용한 생성자 인수 설정 -->
<bean id="beanOne" class="x.y.ThingOne"
c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree"
c:email="something@somewhere.com"/>
</beans>
위 예시에서 c:thingTwo-ref="beanTwo"는 생성자 인수 thingTwo를 빈 beanTwo로 설정하는 것을 의미합니다. 이는 기존의 <constructor-arg> 요소를 속성으로 대체하여 더 간결한 구성을 가능하게 합니다.
이름 대신 인덱스 사용
만약 생성자 인수 이름이 없는 경우(예: 디버깅 정보 없이 컴파일된 경우), 인덱스를 기반으로 인수를 설정할 수 있습니다. 인덱스 기반 설정은 속성 이름 앞에 _를 추가해야 합니다.
인덱스 기반 c-네임스페이스 사용 예시:
<!-- c-네임스페이스 인덱스 선언 -->
<bean id="beanOne" class="x.y.ThingOne"
c:_0-ref="beanTwo"
c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
인덱스 속성 이름에는 앞에 _를 붙여야 하며, 이는 XML 문법에서 속성 이름이 숫자로 시작할 수 없기 때문입니다.
depends-on 속성 사용
depends-on 속성은 스프링 빈 간의 초기화 순서를 명시적으로 지정할 때 사용됩니다. 일반적으로 빈이 다른 빈에 의존성을 가지는 경우, 해당 빈을 속성으로 설정하는 방식으로 이를 처리합니다. 이때 XML 기반 구성에서는 <ref/> 요소를 사용합니다. 하지만 빈 간의 의존성이 더 복잡하거나 직접적이지 않을 때, depends-on 속성을 사용하여 한 빈이 다른 빈보다 먼저 초기화되도록 강제할 수 있습니다.
예시 1: 단일 빈에 대한 의존성
아래 예시는 beanOne이 manager 빈보다 나중에 초기화되도록 지정합니다. 즉, beanOne이 초기화되기 전에 manager 빈이 반드시 먼저 초기화됩니다.
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
예시 2: 여러 빈에 대한 의존성
depends-on 속성을 사용하여 여러 빈에 대한 의존성을 설정할 수 있습니다. 이 경우 빈 이름을 쉼표, 공백 또는 세미콜론으로 구분하여 전달하면 됩니다.
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
위 예시에서 beanOne은 manager와 accountDao 빈이 모두 초기화된 후에야 초기화됩니다.
사용 사례
- 정적 초기화 코드: 특정 클래스에서 데이터베이스 드라이버 등록과 같은 정적 초기화 코드가 실행되어야 할 때 depends-on을 사용하여 해당 클래스의 초기화 순서를 제어할 수 있습니다.
- 종료 순서 제어: 싱글톤 빈의 경우 depends-on은 종료 순서도 제어합니다. depends-on 관계로 명시된 빈들은 기본 빈이 파괴되기 전에 먼저 파괴됩니다. 이를 통해 애플리케이션이 종료될 때 빈들이 올바른 순서로 파괴되도록 할 수 있습니다.
지연 초기화된 빈(Lazy-initialized Beans)
기본적으로, ApplicationContext 구현체는 초기화 과정에서 싱글톤 빈을 미리 생성하고 설정합니다. 이 미리 생성(pre-instantiation)은 일반적으로 바람직한 동작입니다. 왜냐하면 환경 설정 오류나 기타 문제들이 애플리케이션 실행 초기에 바로 발견되기 때문입니다. 그러나 특정 상황에서는 이러한 미리 생성 동작을 피하고 싶을 때가 있습니다. 이때 **지연 초기화(lazy initialization)**를 사용하여 빈이 처음 요청될 때까지 인스턴스화를 미루도록 설정할 수 있습니다.
XML에서의 지연 초기화
lazy-init 속성을 사용하여 특정 빈을 지연 초기화로 설정할 수 있습니다. 아래는 이 동작을 설정하는 예시입니다.
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
위 설정에서 lazy 빈은 ApplicationContext가 시작될 때 즉시 생성되지 않고, 해당 빈이 처음 요청될 때까지 생성이 지연됩니다. 반면, not.lazy 빈은 애플리케이션 컨텍스트가 시작될 때 즉시 생성됩니다.
지연 초기화 빈과 싱글톤 빈의 의존성
지연 초기화된 빈이 지연 초기화되지 않은 싱글톤 빈의 의존성인 경우, ApplicationContext는 싱글톤 빈의 의존성을 충족시키기 위해 지연 초기화된 빈을 즉시 생성합니다. 따라서 지연 초기화된 빈이라도, 다른 싱글톤 빈에서 사용된다면 애플리케이션 컨텍스트 시작 시점에 생성될 수 있습니다.
컨테이너 수준에서 지연 초기화 제어
default-lazy-init 속성을 <beans/> 요소에 사용하여 컨테이너 전체에 지연 초기화를 설정할 수 있습니다. 이를 통해 모든 빈이 처음 요청될 때까지 생성되지 않도록 할 수 있습니다.
<beans default-lazy-init="true">
<!-- 모든 빈이 처음 요청될 때까지 생성되지 않습니다. -->
</beans>
이 설정은 각 빈에 대해 개별적으로 lazy-init 속성을 지정하지 않고도, 전체 애플리케이션의 빈들을 지연 초기화할 수 있는 방법을 제공합니다.
Autowiring Collaborators (자동 와이어링으로 빈 간 협력 관계 설정)
스프링 컨테이너는 자동으로 빈 간의 관계를 설정할 수 있는 자동 와이어링(Autowiring) 기능을 제공합니다. 이 기능을 통해 애플리케이션 컨텍스트 내에서 다른 빈과의 협력 관계를 자동으로 해결할 수 있습니다. 자동 와이어링을 사용하면 수동으로 속성이나 생성자 인수를 설정할 필요가 줄어들며, 특히 개발 초기 단계에서 유용합니다. 다만, 코드가 더 안정화되었을 때는 명시적인 설정으로 전환하는 것이 권장될 수 있습니다.
Autowiring 모드
스프링의 XML 기반 구성에서, <bean/> 요소의 autowire 속성을 사용하여 자동 와이어링 모드를 지정할 수 있습니다. 스프링은 네 가지 자동 와이어링 모드를 지원합니다:
1. no (기본값)
- 설명: 자동 와이어링을 사용하지 않습니다. 모든 빈 참조는 <ref/> 요소를 통해 명시적으로 정의해야 합니다.
- 장점: 명시적 설정이므로 제어와 명확성이 보장됩니다.
2. byName
- 설명: 속성 이름을 기준으로 자동 와이어링이 이루어집니다. 속성 이름과 동일한 이름을 가진 빈이 컨테이너에서 검색되어 주입됩니다.
- 예시: master라는 속성이 있다면, 스프링은 master라는 이름을 가진 빈을 찾아 주입합니다.
3. byType
- 설명: 속성의 타입을 기준으로 자동 와이어링이 이루어집니다. 컨테이너 내에 해당 타입의 빈이 하나만 존재할 경우, 그 빈이 주입됩니다. 여러 개의 빈이 존재하면 예외가 발생합니다.
- 특징: 컬렉션이나 배열과 같은 타입 기반 의존성도 처리할 수 있습니다.
4. constructor
- 설명: byType 모드와 유사하지만, 생성자 인수에 적용됩니다. 생성자 인수 타입에 일치하는 빈이 컨테이너에 하나만 있을 때 자동으로 주입됩니다.
- 특징: 여러 개의 빈이 존재하면 예외가 발생하며, 배열이나 컬렉션도 주입 가능합니다.
자동 와이어링의 한계 및 단점
자동 와이어링을 사용할 때 고려해야 할 몇 가지 한계와 단점이 있습니다:
- 명시적 설정이 우선: 속성이나 생성자 인수를 명시적으로 설정한 경우, 자동 와이어링보다 우선됩니다.
- 단순 속성 지원 제한: 기본형 데이터 타입(primitive), String, Class와 같은 단순 속성은 자동 와이어링이 지원되지 않습니다.
- 모호성 문제: 같은 타입의 빈이 여러 개 있을 때, 자동 와이어링은 모호해집니다. 단일 값을 기대하는 속성에서 모호성이 발생하면 예외가 발생합니다.
모호성 해결 방법
자동 와이어링에서 모호성이 발생할 경우, 다음과 같은 해결 방법을 사용할 수 있습니다:
- 명시적 설정 사용: 자동 와이어링을 포기하고 명시적으로 설정합니다.
- autowire-candidate 속성 사용: 특정 빈을 자동 와이어링 후보에서 제외할 수 있습니다.
- 주 빈 설정: 특정 빈을 주 빈(primary)으로 지정하여 모호성을 해결할 수 있습니다. primary="true" 속성을 사용합니다.
- 어노테이션 기반 구성 사용: 어노테이션을 사용하여 더 세밀하게 제어할 수 있습니다 (예: @Autowired).
특정 빈의 자동 와이어링 제외
빈을 자동 와이어링 후보에서 제외하려면 <bean/> 요소에서 autowire-candidate="false"로 설정할 수 있습니다. 이 설정을 통해 해당 빈은 자동 와이어링에 사용되지 않지만, 명시적 참조는 여전히 가능합니다.
<bean id="myBean" class="com.example.MyClass" autowire-candidate="false"/>
또한, 특정 패턴을 기준으로 자동 와이어링 후보를 제한할 수 있습니다. 예를 들어, 빈 이름이 Repository로 끝나는 모든 빈만 자동 와이어링 후보로 지정하려면 다음과 같이 설정할 수 있습니다:
<beans default-autowire-candidates="*Repository">
메서드 주입(Method Injection)
메서드 주입은 스프링 IoC 컨테이너에서 제공하는 고급 기능으로, 특히 빈의 라이프사이클이 다를 때 유용합니다. 예를 들어, 싱글톤 빈 A가 프로토타입 빈 B를 매번 새로운 인스턴스로 사용해야 할 경우, 메서드 주입을 통해 이를 해결할 수 있습니다. 스프링 IoC 컨테이너는 이러한 상황에서 메서드 호출 시마다 새로운 인스턴스를 반환하는 메서드를 동적으로 주입할 수 있습니다.
문제 상황: 싱글톤 빈과 프로토타입 빈의 협력
싱글톤 빈 A가 프로토타입 빈 B를 필요로 할 때, 빈 A는 최초 생성 시점에 빈 B의 인스턴스를 주입받게 됩니다. 그러나 프로토타입 빈 B는 매번 새로운 인스턴스를 생성해야 할 때가 많습니다. 이 경우 스프링 IoC 컨테이너는 싱글톤 빈 A가 프로토타입 빈 B를 요청할 때마다 새로운 인스턴스를 제공하도록 동적으로 메서드를 주입할 수 있습니다.
기본적인 방법: ApplicationContextAware를 사용한 해결
스프링 컨테이너에 대한 의존성을 명시적으로 추가하여, 프로토타입 빈을 필요할 때마다 ApplicationContext.getBean() 메서드를 호출해 가져올 수 있습니다. 그러나 이 방식은 비즈니스 코드가 스프링 API에 종속되므로 바람직하지 않습니다.
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
이 방식은 스프링 프레임워크에 종속적이기 때문에 권장되지 않습니다.
해결 방법: Lookup 메서드 주입
스프링 프레임워크는 Lookup 메서드 주입을 통해 이러한 문제를 해결할 수 있습니다. 이 방법은 컨테이너가 동적으로 빈을 생성하고 해당 메서드를 오버라이드하여 특정 빈을 반환합니다. 이를 위해 스프링은 CGLIB 바이트코드 생성을 사용해 런타임에 동적으로 서브클래스를 생성합니다.
예시
아래는 CommandManager 클래스에서 createCommand() 메서드를 동적으로 주입하는 방법을 보여줍니다. 이 경우 CommandManager 클래스는 스프링 API에 의존하지 않으며, 스프링이 런타임에 메서드를 구현해줍니다.
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
protected abstract Command createCommand();
}
# XML 구성 예시
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"/>
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
이 설정에서 commandManager 빈은 createCommand() 메서드를 호출할 때마다 myCommand 프로토타입 빈의 새로운 인스턴스를 반환합니다.
어노테이션 기반 구성
스프링 4.1 이후에는 @Lookup 어노테이션을 사용하여 메서드 주입을 구성할 수 있습니다. 이는 XML 기반 구성과 동일한 효과를 가지며, 더 간결하게 작성할 수 있습니다.
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
이 경우, createCommand() 메서드 호출 시 스프링이 동적으로 새로운 Command 빈을 반환합니다.
임의의 메서드 대체
또 다른 형태의 메서드 주입은 임의의 메서드 대체입니다. 이 기능을 사용하면 스프링이 관리하는 빈의 특정 메서드를 다른 구현으로 교체할 수 있습니다. 이 방식은 거의 사용되지 않으며, 매우 특수한 상황에서 필요할 수 있습니다.
public class MyValueCalculator {
public String computeValue(String input) {
// 실제 구현
}
}
위 클래스의 computeValue 메서드를 대체하려면 MethodReplacer 인터페이스를 구현한 클래스를 사용해야 합니다.
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// 새로운 구현
return ...;
}
}
XML 구성에서는 다음과 같이 설정합니다.
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<replaced-method name="computeValue" replacer="replacementComputeValue"/>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
빈 스코프 (Bean Scopes)
스프링에서 빈 정의는 특정 클래스의 인스턴스를 만들기 위한 "레시피"와 같습니다. 이 레시피로부터 여러 객체 인스턴스를 만들 수 있으며, 생성된 객체의 스코프(scope) 를 설정할 수 있습니다. 스코프는 빈이 어떻게 생성되고 사용되는지를 결정하는 중요한 개념입니다. 스프링은 다양한 스코프를 지원하며, 일부 스코프는 웹 애플리케이션에서만 사용할 수 있습니다.
다음은 스프링에서 지원하는 빈 스코프입니다:
빈 스코프 설명
singleton | (기본값) 하나의 빈 정의로부터 스프링 IoC 컨테이너당 하나의 객체 인스턴스만 생성됩니다. |
prototype | 하나의 빈 정의로부터 여러 개의 객체 인스턴스를 생성할 수 있습니다. |
request | 하나의 HTTP 요청에 대해 하나의 빈 인스턴스를 생성합니다. 웹 애플리케이션에서만 유효합니다. |
session | 하나의 HTTP 세션에 대해 하나의 빈 인스턴스를 생성합니다. 웹 애플리케이션에서만 유효합니다. |
application | 하나의 ServletContext 생명주기에 걸쳐 하나의 빈 인스턴스를 생성합니다. 웹 애플리케이션에서만 유효합니다. |
websocket | 하나의 WebSocket 생명주기에 걸쳐 하나의 빈 인스턴스를 생성합니다. 웹 애플리케이션에서만 유효합니다. |
스프링은 이 외에도 ThreadScope와 같은 사용자 정의 스코프도 지원합니다.
싱글톤 스코프 (Singleton Scope)
싱글톤 스코프는 기본적으로 하나의 빈 정의로부터 하나의 객체 인스턴스만 생성되며, 이후 모든 요청에서 동일한 객체 인스턴스가 반환됩니다. 이 빈 인스턴스는 스프링 IoC 컨테이너 내에서 캐시되고, 모든 요청에서 해당 캐시된 객체가 반환됩니다.
- 싱글톤 스코프 설정 예시 (XML):
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
이 설정은 기본값이므로 scope="singleton" 속성을 명시하지 않아도 동일한 결과가 발생합니다.
싱글톤 패턴과의 차이점
스프링의 싱글톤은 GoF의 싱글톤 패턴과 다릅니다. GoF 싱글톤 패턴은 JVM의 클래스 로더별로 하나의 인스턴스만을 생성하는 반면, 스프링의 싱글톤 스코프는 컨테이너별로 하나의 인스턴스만을 생성하는 방식입니다.
프로토타입 스코프 (Prototype Scope)
프로토타입 스코프는 요청이 있을 때마다 새로운 객체 인스턴스를 생성합니다. 즉, 동일한 빈 정의로부터 여러 개의 인스턴스를 만들 수 있습니다. 프로토타입 스코프는 주로 상태를 가지는 빈에 사용되며, 상태가 없는 빈에는 싱글톤 스코프를 사용하는 것이 좋습니다.
- 프로토타입 스코프 설정 예시 (XML):
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
프로토타입 스코프의 특징
스프링 컨테이너는 프로토타입 빈의 전체 생명주기를 관리하지 않습니다. 프로토타입 빈의 인스턴스는 컨테이너가 생성하고 클라이언트에 반환되지만, 이후의 생명주기 관리는 클라이언트가 직접 처리해야 합니다. 예를 들어, 리소스 해제를 위해 클라이언트가 직접 객체를 정리해야 합니다.
싱글톤 빈과 프로토타입 빈의 의존성 문제
싱글톤 빈이 프로토타입 빈을 의존성으로 가질 때, 주의할 점이 있습니다. 스프링 컨테이너는 싱글톤 빈이 생성될 때 한 번만 프로토타입 빈을 주입합니다. 따라서 런타임 중에 프로토타입 빈의 새로운 인스턴스를 계속 생성해야 할 경우, 단순 의존성 주입만으로는 해결되지 않습니다. 이 문제를 해결하려면 **메서드 주입(Method Injection)**을 사용해야 합니다. 이를 통해 싱글톤 빈이 런타임 중에 새로운 프로토타입 빈 인스턴스를 얻을 수 있습니다.
Request, Session, Application, and WebSocket Scopes in Spring
Spring 프레임워크는 request, session, application, websocket 스코프를 제공하여 웹 애플리케이션에서 다양한 생명주기 관리 방식을 지원합니다. 이러한 스코프는 웹 애플리케이션 컨텍스트에서만 사용할 수 있으며, 일반적인 Spring IoC 컨테이너에서 사용할 경우 IllegalStateException이 발생합니다.
웹 관련 빈 스코프 활성화 초기 설정
이 스코프들을 사용하려면 약간의 초기 설정이 필요합니다. 만약 Spring Web MVC에서 스프링의 DispatcherServlet을 사용하는 경우, 별도의 추가 설정이 필요하지 않습니다. 하지만 JSF와 같이 Spring의 DispatcherServlet 외부에서 요청이 처리되는 경우, RequestContextListener를 등록해야 합니다.
Web.xml 파일에서 RequestContextListener 설정 예시:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
또한, 문제가 발생할 경우 RequestContextFilter를 사용할 수도 있습니다. 이 필터는 HTTP 요청 객체를 특정 스레드에 바인딩하여 요청 및 세션 범위의 빈을 사용할 수 있게 합니다.
Request Scope
Request Scope는 HTTP 요청 단위로 빈을 생성하고 관리합니다. HTTP 요청이 끝나면 해당 빈도 소멸합니다.
XML 설정 예시:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
어노테이션 기반 설정:
@RequestScope
@Component
public class LoginAction {
// ...
}
Session Scope
Session Scope는 HTTP 세션 동안 빈을 생성하고 유지합니다. 세션이 종료되면 해당 빈도 소멸합니다.
XML 설정 예시:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
어노테이션 기반 설정:
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application Scope
Application Scope는 하나의 ServletContext 생명주기 동안 빈을 유지합니다. 애플리케이션 전체에서 단일 인스턴스를 사용하게 됩니다.
XML 설정 예시:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
어노테이션 기반 설정:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
WebSocket Scope
WebSocket Scope는 WebSocket 세션 동안 빈을 유지합니다. 이는 STOMP 메시징을 사용하는 WebSocket 애플리케이션에서 유효합니다.
Scoped Beans as Dependencies (스코프 빈의 의존성 주입)
더 짧은 생명주기를 가진 스코프 빈을 더 긴 생명주기를 가진 빈에 주입해야 할 때는 프록시 객체를 사용하여 해결할 수 있습니다. 프록시는 스코프에 따라 실제 빈을 동적으로 가져와 메서드 호출을 해당 객체로 위임합니다.
XML 프록시 설정 예시:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userService" class="com.something.SimpleUserService">
<property name="userPreferences" ref="userPreferences"/>
</bean>
이 설정에서 userService는 userPreferences 빈의 프록시를 주입받으며, 실제로는 세션에 따라 올바른 userPreferences 객체로 메서드를 위임합니다.
JDK 인터페이스 기반 프록시 예시:
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
이 설정은 JDK 인터페이스 기반 프록시를 사용하며, 프록시를 통한 빈 참조는 인터페이스를 통해 이루어져야 합니다.
HTTP 요청 및 세션 객체 직접 주입
Spring WebApplicationContext는 HttpServletRequest, HttpSession과 같은 웹 관련 객체도 빈에 직접 주입할 수 있습니다. 이를 통해 일반적인 빈 주입과 함께 HTTP 요청 또는 세션 객체도 활용할 수 있습니다.
Custom Scopes in Spring
Spring의 빈 스코프 기능은 확장 가능하며, 개발자가 직접 사용자 정의 스코프를 구현할 수 있습니다. 다만 기존의 singleton 및 prototype 스코프를 재정의하는 것은 불가능합니다.
사용자 정의 스코프 생성
Spring에 사용자 정의 스코프를 통합하려면 org.springframework.beans.factory.config.Scope 인터페이스를 구현해야 합니다. 이 인터페이스는 네 가지 주요 메서드를 제공합니다
get(String name, ObjectFactory<?> objectFactory)
객체를 스코프에서 가져오거나 없을 경우 새로운 객체를 생성하여 반환합니다. 예를 들어, 세션 스코프 구현은 세션 내에서 객체를 반환하거나, 해당 객체가 없으면 새 인스턴스를 생성하여 세션에 바인딩한 후 반환합니다.
Object get(String name, ObjectFactory<?> objectFactory);
remove(String name)
스코프에서 객체를 제거하고, 제거된 객체를 반환합니다. 객체가 없을 경우 null을 반환할 수 있습니다.
Object remove(String name);
registerDestructionCallback(String name, Runnable destructionCallback)
스코프가 종료되거나 스코프 내 객체가 제거될 때 실행할 콜백을 등록합니다.
void registerDestructionCallback(String name, Runnable destructionCallback);
getConversationId()
스코프의 대화 식별자를 반환합니다. 예를 들어, 세션 스코프에서는 세션 ID가 될 수 있습니다.
String getConversationId();
사용자 정의 스코프 사용
사용자 정의 스코프를 작성하고 테스트한 후에는 스프링 컨테이너에 해당 스코프를 등록해야 합니다. 스프링 컨테이너는 ConfigurableBeanFactory 인터페이스를 통해 새로운 스코프를 등록할 수 있습니다.
void registerScope(String scopeName, Scope scope);
이 메서드의 첫 번째 인자는 스코프의 고유한 이름입니다. 예를 들어, singleton이나 prototype과 같은 스코프 이름을 설정할 수 있습니다. 두 번째 인자는 실제 사용자 정의 스코프 구현체입니다.
사용자 정의 스코프 등록 예시
다음은 Spring에서 제공하는 SimpleThreadScope를 등록하는 예시입니다.
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
이제 해당 스코프를 따르는 빈 정의를 작성할 수 있습니다.
<bean id="..." class="..." scope="thread"/>
선언적 스코프 등록
사용자 정의 스코프는 프로그램적으로 등록하는 것 외에도 선언적으로 등록할 수 있습니다. 이를 위해 CustomScopeConfigurer 클래스를 사용할 수 있습니다.
XML 설정 예시
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
이 설정에서 thing2는 thread 스코프를 따르며, thing1은 thing2를 의존성으로 가지게 됩니다.
aop:scoped-proxy를 이용한 스코프 프록시
<aop:scoped-proxy/> 태그를 사용하여 스코프 빈을 프록시로 노출할 수 있습니다. 이 프록시는 실제 객체 대신 빈을 호출하는 메서드를 가로채서 올바른 스코프에 맞는 객체를 반환합니다.
프록시 설정 예시
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
프록시 타입 선택
기본적으로 스프링 컨테이너는 CGLIB 기반의 클래스 프록시를 생성하지만, proxy-target-class="false" 속성을 사용하여 JDK 인터페이스 기반 프록시로 설정할 수 있습니다.
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
여기까지 스프링의 개념과 빈 설정에 대한 전반적인 내용을 다뤄보았습니다. 다음 시간에는 IoC Container 관련 내용들을 마무리해보겠습니다.
'Spring' 카테고리의 다른 글
[Spring] Spring Security - 1 (0) | 2024.09.03 |
---|---|
[Spring] Spring Framework Overview - 2 (0) | 2024.08.22 |