[Spring Core #2] 스프링 컨테이너와 스프링 빈 (스프링 핵심 원리 강의정리)
Index
📌 스프링 컨테이너 생성
- 스프링 컨테이너 생성 과정
📌 컨테이너에 등록된 모든 빈 조회
📌 스프링 빈 조회
- 기본
- 스프링 빈 조회 (동일한 타입 둘 이상인 경우)
- 스프링 빈 조회 (상속 관계)
📌 BeanFactory와 ApplicationContext
- BeanFactory
- ApplicationContext
📌 다양한 설정 형식 지원 - XML, 설정 클래스 파일
📌 스프링 빈 설정 메타 정보 - BeanDefinition
📌 정리
해당 내용은 강의 내용을 기억하기 위한 정리글입니다. 자세한 내용은 강의에서 확인하실 수 있습니다.
(저는 코틀린 베이스로 강의를 진행하였고 게시글의 코드 예시는 대부분 코틀린으로 이루어져 있습니다.)
스프링 핵심 원리 - 기본편(김영한님) #광고아님, #내돈내산, #적극추천
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
📌 스프링 컨테이너 생성
이번 강의에서는 스프링 컨테이너와 스프링 빈의 원리를 설명하고 있습니다. 스프링 컨테이너는 스프링 빈을 관리하는 주체이자 저장소라고 할 수 있습니다.
스프링 컨테이너는 XML 설정 방식, Annotation 기반 자바 설정 클래스(AppConfig)로 구성 가능합니다.
스프링 컨테이너 생성 과정
애노테이션 기반의 설정 클래스 기준으로 먼저 예시를 들고 있습니다.
val context: ApplicationContext = AnnotationConfigApplicationContext(AppConfig::class.java)
AnnotationConfigApplicationContext를 통해 애노테이션 기반 설정 클래스로 구성한 스프링 컨테이너를 가져올 수 있습니다.
@Configuration class AppConfig { @Bean fun memberService(): MemberService { return MemberServiceImpl(memberRepository()) } @Bean fun memberRepository(): MemberRepository { return MemoryMemberRepository() } @Bean fun orderService(): OrderService { return OrderServiceImpl(memberRepository(), discountPolicy()) } @Bean fun discountPolicy(): DiscountPolicy { return FixDiscountPolicy() // return RateDiscountPolicy() } }
설정 클래스 안에는 @Bean 애노테이션으로 빈을 정의하고 있습니다. 여기서 따로 빈 이름을 설정하지 않으면 스프링은 메소드 이름으로 스프링 빈 이름을 설정합니다.
물론 빈 이름을 직접 지정할 수도 있습니다.
@Bean(name = ["memberCustomService"]) fun memberService(): MemberService { return MemberServiceImpl(memberRepository()) }
코틀린에서는 Bean 애노테이션의 name 옵션 값이 Array로 되어 있어 이를 명시적으로 Array 형태로 입력해야 합니다.
스프링 컨테이너를 생성과정은 다음과 같이 축약할 수 있을 것 같습니다.
- 스프링 컨테이너 생성
스프링 빈 구성 정보지정(AppConfig.class) - 스프링 빈 등록
스프링 컨테이너 내부의 빈 저장소는 일종의 key, value로 이루어져 있음. 여기에 설정파일의 메소드 이름과 반환 객체를 가지고 각각 key, value로 객체를 저장소에 저장. - 스프링 빈 의존관계 설정
원래는 스프링 빈들을 다 생성하고 그다음 의존관계를 주입하는 단계가 나누어져 있지만 자바 설정파일로 한 경우에는 빈 등록시 생성자가 호출되면서 해당 생성자 인자를 가지고 의존관계 주입도 같이 이루어지게 됨
주의할 점은 빈 이름은 항상 다른 이름으로 설정해야 한다는 점입니다. 여러 빈들에 같은 이름으로 중복 설정하면 의도치 않게 다른 빈이 무시되거나 기존의 빈을 overwrite할 수 있습니다. (Spring Boot는 기본적으로 이러한 빈 중복을 아예 원천 차단한다네요.)
애노테이션 기반의 설정 클래스로 스프링 빈을 등록하면 그 안에 생성자를 호출하면서 스프링 빈 생성과 의존관계 주입이 한 번에 처리된다는 점도 중요해보였습니다.
📌 컨테이너 등록된 모든 빈 조회
applicationContext.getBeanDefinitionNames()
스프링 컨테이너에 등록된 모든 빈들을 조회할 수 있는데 위와 같이 등록된 모든 빈들의 이름 목록을 가져올 수 있습니다.
@Test @DisplayName("모든 빈 출력하기") fun findAllBean() { val beanDefinitionNames = ac.beanDefinitionNames for (beanDefinitionName in beanDefinitionNames) { val bean = ac.getBean(beanDefinitionName) println("name = ${beanDefinitionName}, object = ${bean}") } }
스프링 빈 이름으로 빈 객체를 조회(getBean) 할 수 있습니다. 위와 같은 코드로 스프링에 등록된 모든 빈들의 이름과 빈 인스턴스를 한 꺼번에 확인할 수 있습니다.
그런데 스프링에 등록된 모든 빈들 중에는 우리가 AppConfig를 통해 직접 설정한 빈들 뿐만 아니라 스프링 내부적으로 사용하기 위해 등록한 빈들도 있습니다. 이를 따로 구분해서 조회할 수 있습니다.
@Test @DisplayName("애플리케이션 빈 출력하기") fun findApplicationBean() { val beanDefinitionNames = ac.beanDefinitionNames for (beanDefinitionName in beanDefinitionNames) { val beanDefinition = ac.getBeanDefinition(beanDefinitionName) if (beanDefinition.role == BeanDefinition.ROLE_APPLICATION) { val bean = ac.getBean(beanDefinitionName) println("name = ${beanDefinitionName}, object = ${bean}") } } }
- ROLE_APPLICATION: 사용자가 설정한 일반적인 스프링 빈
- ROLE_INFRASTRUCTURE: 스프링이 내부적으로 사용하기 위해 등록한 스프링 빈
우리가 직접 등록한 스프링 빈만 확인하고 싶을 때는 ROLE_APPLICATION로 조회하면 됩니다.
📌 스프링 빈 조회
기본
applicationContext.getBean([BeanName], [type]) applicationContext.getBean([type])
기본적으로 스프링 빈 이름과 타입을 가지고 스프링 빈을 조회할 수 있습니다. (ApplicationContext getBean 사용)
@Test @DisplayName("빈 이름으로 조회") fun findBeanByName() { val memberService = ac.getBean("memberService", MemberService::class.java) assertThat(memberService).isInstanceOf(MemberServiceImpl::class.java) }
applicationContext.getBean([BeanName], [type]) 이걸 사용해서 스프링 빈을 조회한 것입니다.
@Test @DisplayName("구체 타입으로 조회") fun findBeanByName2() { val memberService = ac.getBean("memberService", MemberServiceImpl::class.java) assertThat(memberService).isInstanceOf(MemberServiceImpl::class.java) }
첫 번째에서는 MemberService 인터페이스 타입으로 조회했는데 이번에는 MemberService의 구현체 타입(MemberServiceImpl)으로 스프링 빈을 조회한 것입니다.
@Test @DisplayName("이름 없이 타입으로만 조회") fun findBeanByType() { val memberService = ac.getBean(MemberService::class.java) assertThat(memberService).isInstanceOf(MemberServiceImpl::class.java) }
스프링 빈 이름 없이 타입만으로도 스프링 빈을 조회할 수 있습니다. 당연하게 MemberService 뿐만 아니라 MemberServiceImpl로도 조회 가능합니다.
그런데 구체(구현체) 타입으로 조회하는 것은 바람직하지 못합니다. 객체 지향적으로 설계했는데 구체 타입으로 조회하면 유연성이 떨어지고 확장하는데 많은 변경지점이 발생하게 됩니다. (OCP 위반)
@Test @DisplayName("빈 이름으로 조회 X") fun findBeanByNameX() { assertThrows(NoSuchBeanDefinitionException::class.java) { val memberService = ac.getBean("xxxxx", MemberService::class.java) } }
등록이 안 된 빈 이름으로 조회를 하려고 하면 NoSuchBeanDefinitionException 예외가 발생합니다. 참고해두면 좋을 것 같네요.
스프링 빈 조회 (동일한 타입 둘 이상인 경우)
@Configuration private class SameBeanConfig { @Bean fun memberRepository1(): MemberRepository { return MemoryMemberRepository() } @Bean fun memberRepository2(): MemberRepository { return MemoryMemberRepository() } }
이렇게 같은 타입으로 스프링 빈을 2개 중복 등록하면 어떻게 되는지도 강의에서 설명하고 있습니다.
@Test @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.") fun findBeanByTypeDuplicate() { assertThrows(NoUniqueBeanDefinitionException::class.java) { val memberRepository = ac.getBean(MemberRepository::class.java) } }
기본적으로 getBean 조회시 이런 상황에서는 NoUniqueBeanDefinitionException 예외가 발생하게 됩니다.
val memberRepository = ac.getBean("memberRepository1", MemberRepository::class.java) assertThat(memberRepository).isInstanceOf(MemberRepository::class.java)
두 개 이상의 같은 타입으로 중복된 빈들 중 하나를 조회하려면 빈 이름을 특정해서 조회하면 됩니다.
@Test @DisplayName("특정 타입을 모두 조회한다.") fun findAllBeansByType() { val beansOfType = ac.getBeansOfType(MemberRepository::class.java) beansOfType.keys.forEach { println("key = ${it}, value = ${beansOfType[it]}") } println("beansOfType = ${beansOfType}") assertEquals(beansOfType.size, 2) }
ac.getBeansOfType() 을 통해서 특정 타입으로 등록된 모든 스프링 빈들을 조회할 수 있습니다.
Map 형식으로 반환해주는데 key는 빈 이름, value는 해당 빈 인스턴스입니다.
그런데 특정타입 하나로 여러 개의 스프링 빈을 등록한 경우는 실무에서 잘 사용되지 않습니다. (저는 아직까지 본 적이 없네요.)
스프링 빈 조회 (상속 관계)
스프링 빈 조회에 있어서 상속 관계가 적용이 됩니다.
즉, 상위 타입으로 스프링 빈을 조회하면 그에 해당하는 모든 하위 타입들을 같이 조회하게 됩니다.
@Configuration private class TestConfig { @Bean fun rateDiscountPolicy(): DiscountPolicy { return RateDiscountPolicy() } @Bean fun fixDiscountPolicy(): DiscountPolicy { return FixDiscountPolicy() } }
이번에는 DiscountPolicy 구현체 두 개를 다른 이름으로 하여 빈으로 등록했습니다.
@Test @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다.") fun findBeanByParentTypeDuplicate() { assertThrows(NoUniqueBeanDefinitionException::class.java) { ac.getBean(DiscountPolicy::class.java) } }
위에 동일한 타입 둘 이상인 경우와 비슷하게 DiscountPolicy로 다른 구현체 두 개가 스프링 빈으로 등록되었기 때문에 getBean 조회시 중복 에러가 발생합니다. 즉, DiscountPolicy 타입으로 조회하면 그의 하위 타입인 FixDiscountPolicy, RateDiscountPolicy 둘다 조회하게 되는 것을 알 수 있습니다.
@Test @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다..") fun findBeanByParentTypeBeanName() { val discountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy::class.java) assertThat(discountPolicy).isInstanceOf(RateDiscountPolicy::class.java) } @Test @DisplayName("특정 하위 타입으로 조회") fun findBeanBySubType() { val rateDiscountPolicy = ac.getBean(RateDiscountPolicy::class.java) assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy::class.java) }
마찬가지로 등록된 스프링 빈 타입으로 조회하는 방법이 있습니다. 또한 상위 타입이 아닌 구현체 타입으로 특정 지어 조회하는 방법도 있습니다. 하지만 앞에서 봤듯이 유연성이 없어진다는 문제가 있습니다.
@Test @DisplayName("부모 타입으로 모두 조회하기") fun findAllBeansByParentType() { val beansOfType = ac.getBeansOfType(DiscountPolicy::class.java) assertEquals(beansOfType.size, 2) beansOfType.keys.forEach { println("key = ${it}, value = ${beansOfType[it]}") } } @Test @DisplayName("부모 타입으로 모두 조회하기 - Object") fun findAllBeansByObjectType() { val beansOfType = ac.getBeansOfType(Object::class.java) beansOfType.keys.forEach { println("key = ${it}, value = ${beansOfType[it]}") } }
ac.getBeansOfType()을 통해 상위 타입으로 모든 관련 하위 타입들을 한꺼번에 조회할 수 있습니다. 여기서 Object 타입으로 조회하게 되면 모든 스프링 빈들을 조회하게 되는데요. Object는 모든 클래스들의 최상위 타입이기 때문입니다.
📌 BeanFactory와 ApplicationContext
스프링 컨테이너로 사용되는 ApplicationContext는 사실 BeanFactory를 상속받고 있습니다. 즉, 스프링 컨테이너의 최상위 인터페이스가 바로 BeanFactory라 할 수 있습니다.
BeanFactory
BeanFactory는 getBean 메소드 등 기본적인 스프링 빈 조회 기능과 빈이 singleton, prototype인지 체크하는 기능 등 스프링 컨테이너의 베이스가 되는 기본 기능들을 제공해줍니다.
ApplicationContext
BeanFactory의 모든 기능들을 사용하면서 이외 추가 기능들도 제공해줍니다.

- MessageSource
- 국제화(i18n) 기능을 제공하는 인터페이스 - EnvironmentCapable
- 프로파일과 프로퍼티를 다루는 인터페이스
- 로컬, 개발, 운영 등의 애플리케이션 구동 환경을 설정할 수 있음 - ApplicationEventPublisher
- 이벤트를 발행, 구독하는 모델을 편리하게 지원
- 이벤트 프로그래밍에 필요한 인터페이스 - ResourceLoader
- 파일, 클래스패스, 외부에서 리소스를 편리하게 조회
- 리소스를 읽어오는 기능을 제공하는 인터페이스
ApplicationContext는 기본 스프링 빈 조회 기능 뿐만 아니라 그 외 편리한 기능들을 확장한 인터페이스라고 보시면 됩니다.
📌 다양한 설정 형식 지원 - XML, 설정 클래스 파일

스프링은 다양한 스프링 빈 설정 방식을 지원합니다. 위의 Annotation 기반의 설정파일(AppConfig) 방식은 AnnotationConfigApplicationContext를 통해 사용할 수 있습니다.
Annotation 기반 뿐만 아니라 xml 설정 파일 방식도 지원하는데 GenericXmlApplicationContext를 사용합니다.
<?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="memberService" class="hello.core.member.MemberServiceImpl"> <constructor-arg name="memberRepository" ref="memberRepository" /> </bean> <bean id="memberRepository" class="hello.core.member.MemoryMemberRepository" /> </beans>
xml 설정 방식은 위와 같이 bean 태그 안에 지정하고자 하는 빈 class 정보, id 값, 그리고 (생성자, setter)주입 받을 빈 내용을 지정할 수 있습니다. xml 설정 방식은 요즘에도 사용하고 있는 곳이 있지만 거의 사용되지 않는 방식이고 거의 대부분은 Annotation 기반으로 설정한다고 보면 됩니다.
이렇듯 스프링은 빈 설정 방식까지 추상화를 통해 유연하게 여러 가지 방식을 적용할 수 있도록 지원하고 있습니다.
📌 스프링 빈 설정 메타 정보 - BeanDefinition
바로 위의 내용과 같이 스프링은 다양한 스프링 빈 설정 방식을 유연성 있게 지원하고 있습니다. 추상화를 통해 유연성을 가질 수 있었는데요. 여기서 BeanDefinition 내용이 나오게 됩니다.
- AnnotationConfigApplicationContext > AnnotatedBeanDefinitionReader > BeanDefinition
- GenericXmlApplicationContext > XmlBeanDefinitionReader > BeanDefinition
- XxxApplicationContext > XxxBeanDefinitionReader > BeanDefinition
여러 방식으로 빈을 설정하면 해당 설정파일을 ApplicationContext 여러 구현체들이 읽어들이는데요. ApplicationContext 구현체들은 BeanDefinitionReader를 사용해서 BeanDefinition을 생성하게 됩니다.
@Test @DisplayName("빈 설정 메타정보 확인") fun findApplicationBean() { val beanDefinitionNames = ac.beanDefinitionNames for (beanDefinitionName in beanDefinitionNames) { val beanDefinition = ac.getBeanDefinition(beanDefinitionName) if (beanDefinition.role == BeanDefinition.ROLE_APPLICATION) { println("beanDefinitionName = ${beanDefinitionName}, beanDefinition = ${beanDefinition}") } } }

ac.getBeanDefinition을 통해 설정된 BeanDefinition 정보들을 전부 조회할 수 있습니다. (안의 정보들은 참고)
BeanDefinition는 스프링이 다양한 형태의 설정 정보를 추상화해서 사용하는 것 정도만 이해하면 됩니다.
📌 정리
이번 강의 섹션에서는 스프링 컨테이너와 설정된 스프링 빈을 조회하는 방법, 그리고 마지막으로 추상화를 통한 여러 스프링 빈 설정 방식 지원한다는 내용과 BeanDefinition까지 살펴보았습니다. 정리하면 다음과 같습니다.
1. 스프링 생성 과정을 스프링 컨테이너 생성 > 스프링 빈 등록 > 스프링 빈 의존관계 설정 순서로 설명했습니다.
- 특히 애노테이션 기반의 설정 클래스로 스프링 빈을 등록하면 그 안에 생성자를 호출하면서 스프링 빈 생성과 의존관계 주입이 한 번에 처리된다는 점이 중요해 보였습니다.
2. 컨테이너에 등록된 빈 객체들을 조회하는 방법에는 여러가지가 존재했습니다.
- 결국 구현체 타입으로 조회하기 보다 추상화된 타입으로(상위 타입으로) 조회하는 것이 유연성에 부합한다는 것이 중요했습니다.
- 하나의 타입으로 여러 개 빈들을 생성하면 예기치 않은 스프링 에러가 발생할 수 있다는 점에서 주의해야 할 점으로 보았습니다.
3. BeanFactory와 ApplicationContext 관계를 확인할 수 있었습니다.
- BeanFactory는 getBean등 빈 조회하는 가장 기본적인 기능들을 담고 있고 이를 구현한 것이 ApplicationContext 입니다.
- ApplicationContext는 BeanFactory 뿐만 아니라 MessageSource, EnvironmentCapable, ApplicationEventPublisher, ResourceLoader 등 여러 인터페이스들을 상속받아 여러 다양한 기능들을 제공해준다는 점이 있었습니다.
4. 스프링은 다양한 스프링 빈 설정 방식을 지원합니다.
- ApplicationContext를 구현한 구현체 중에서는 xml 방식, annotation 설정 방식 등 지원하는 구현체가 존재합니다.
- 이렇게 여러 스프링 빈 설정 방식을 지원할 수 있는 것은 BeanDefinitionReader를 사용해 BeanDefinition 인터페이스 타입으로 반환하도록 설계했기 때문에 가능합니다.
5. 스프링은 스프링 빈 생성 과정부터 설정 방식까지 OOP에 입각해 유연성 있고 확장가능하도록 설계한 프레임워크라는 점에서 인상 깊었습니다.
'Spring' 카테고리의 다른 글
[Spring] Filter 중복 호출되는 경우와 OncePerRequestFilter를 통한 처리 (0) | 2023.05.07 |
---|---|
[Spring] 설정파일과 Bean 사이의 순환참조(circular references) 이슈 및 해결 (0) | 2022.10.31 |
[Spring Core #1] 스프링의 객체 지향 원리 적용 (스프링 핵심 원리 강의정리) (0) | 2022.07.01 |
[Spring] 객체 지향 설계를 극대화한 스프링의 핵심 개념 정리 (0) | 2022.06.20 |
[Spring] 2. jackson을 이용한 data binding 이해하기(생성자, constructor) (7) | 2022.04.26 |
댓글
댓글(Github)
다른 글
-
[Spring] Filter 중복 호출되는 경우와 OncePerRequestFilter를 통한 처리
[Spring] Filter 중복 호출되는 경우와 OncePerRequestFilter를 통한 처리
2023.05.07목표 - 하나의 request에서 Filter의 중복 호출되는 사례들을 알아보자 - OncePerRequestFilter를 사용함으로써 Filter 중복 호출 방지 Spring Boot를 사용해 web application을 개발하다보면 Filter를 구현해서 적용하는 일이 반드시 생기게 됩니다. Filter를 사용하다보면 Filter가 중복 호출되는 경우가 발생하게 되는데요. 어떤 경우에 이런 Filter 중복 호출 현상이 발생하는지에 대해 알아보고 OncePerRequestFilter를 통해서 이러한 현상을 방지하는 것까지 정리해보려합니다. (참고로 예시 코드는 kotlin 입니다.) 📌 1. 기본적인 Filter 구성 class FirstFilter : Filter { companion object… -
[Spring] 설정파일과 Bean 사이의 순환참조(circular references) 이슈 및 해결
[Spring] 설정파일과 Bean 사이의 순환참조(circular references) 이슈 및 해결
2022.10.31Spring Security만을 사용해서 개인 프로젝트에 간단한 회원가입과 인증 프로세스를 개발하면서 부딪혔던 내용 중 하나를 정리하고자 합니다. Spring Security 설정파일 작성 후 애플리케이션 실행 시 발생했던 순환참조(circular references, dependency cycle)에 대해 기록한 내용입니다. 📌 1. 개발했던 내용 오직 Spring Security 내용을 가지고 인증 프로세스를 구현했던 내용을 정말 간단하게 요약하고 문제상황을 보여드리는게 좋을 것 같습니다. /** * 실제 인증 절차 수행 * @property userDetailsService email로 계정 찾기 */ @Component class ApiAuthenticationProvider( private va… -
[Spring Core #1] 스프링의 객체 지향 원리 적용 (스프링 핵심 원리 강의정리)
[Spring Core #1] 스프링의 객체 지향 원리 적용 (스프링 핵심 원리 강의정리)
2022.07.01Index 📌 새로운 요구사항의 추가 📌 관심사 분리 📌 AppConfig 리팩토링 📌 좋은 객체 지향 설계 5가지 원칙 적용 📌 IoC, DI, 컨테이너 📌 정리 해당 내용은 강의 내용을 기억하기 위한 정리글입니다. 자세한 내용은 강의에서 확인하실 수 있습니다. (저는 코틀린 베이스로 강의를 진행하였고 게시글의 코드 예시는 대부분 코틀린으로 이루어져 있습니다.) 스프링 핵심 원리 - 기본편(김영한님) #광고아님, #내돈내산, #적극추천 스프링 핵심 원리 - 기본편 - 인프런 | 강의 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런…. www.inflearn.com 📌 새로운 요구사항의 추가 주문서비스가 있고 주문… -
[Spring] 객체 지향 설계를 극대화한 스프링의 핵심 개념 정리
[Spring] 객체 지향 설계를 극대화한 스프링의 핵심 개념 정리
2022.06.20Index 📌 스프링 탄생 배경 - EJB의 한계 - 스프링의 등장 📌 스프링의 핵심 📌 객체 지향 5가지 원칙과 스프링 - 스프링 IoC와 DI - 스프링은 객체지향 원칙을 잘 준수해요 📌 정리 📌 1. 스프링의 탄생 배경 1-1. EJB 한계 지금은 알 필요가 없는 EJB읜 한계를 극복하기 위해 로드 존슨 형님이 스프링을 제안하였고 이를 개발했습니다. (유겐 휠러 형님이 사실상 스프링 대부분의 코드 지분을 가지고 있음) EJB(Enterprise Java Beans)에 대해서 본인은 이쪽 세대도 아니었고 한 번도 사용해본 적이 없었기 때문에 EJB 개념조차 제대로 알지 못합니다. 하지만 스프링이 왜 태어났는지에 대해서 찾아보거나 강의를 듣게 되면 EJB는 빠짐없이 등장합니다. 대략적으로 스프링이 탄생…
댓글을 사용할 수 없습니다.