개발에 들어가기전, 내가 사용하는 기술에 대한 이해를 돕기위해 시리즈를 작성합니다.
- Spring 역사를 정리한다.
- Spring 도큐먼트에서 필요한 내용을 번역하고 요약한다. ref. Spring Document 5.2.5.RELEASE
- Spring의 컨셉과 핵심 요소를 이해한다.
- Spring의 동작원리를 이해한다.
- .xml 파일의 작성 방법을 정리한다.
🔎Spring Container
✏️ Container
✏️ Bean Factory
✏️ ApplicationContext
프레임 워크에 대해 배울 때는 해당 프레임 워크의 기능뿐만 아니라 그에 따르는 원칙을 아는 것이 중요합니다. Spring Framework의 기본 원칙은 다음과 같습니다.
모든 개발 단계에서 선택(결정)하십시오. Spring을 사용하면 설계에 대한 결정을 최대한 늦출 수 있습니다. 예를 들어, 코드를 변경하지 않고 configuration을 통해 persistence provider를 전환 할 수 있습니다. 다른 많은 인프라 관련 문제와 타사 API와의 통합에서도 마찬가지입니다.
다양한 관점을 받아들이십시오. Spring은 유연성을 수용하며 어떻게 작업을 수행하는 지에 대해서 강제하는 방식이 없습니다. 다양한 관점에서 다양한 어플리케이션의 요구사항을 지원합니다.
이전 버전과의 호환성을 유지하십시오. Spring은 버전을 업그레이드 함에 있어서 과도한 변경 사항을 발생시키지 않도록 신중하게 관리됩니다. Spring은 신중하게 선택된 JDK 버전과 써드 파티 라이브러리를 지원하여 Spring에 의존하는 애플리케이션 및 라이브러리의 유지 보수를 용이하게합니다.
API 디자인에 주의를 기울이십시오. Spring 팀은 많은 버전과 수년에 걸쳐 유지되는 직관적인 API를 만드는 데 많은 시간과 노력을 기울였습니다.
코드 품질에 대한 높은 표준을 설정하십시오. 스프링 프레임 워크는 의의를 가지고, 정확하고, 최신인 javadoc에 중점을 둡니다. Spring은 패키지간에 순환 종속성이없는 깨끗한 코드 구조를 주장 할 수 있는 몇 없는 프로젝트 중 하나입니다.
📌 가장 중요한 것은 IoC(Inversion of Control) 컨테이너
📌 이어서 AOP(Aspect-Oriented Programming)에 대한 내용을 전달한다.
유형 | 이름 |
---|---|
package | org.springframework.beans |
package | org.springframework.context |
interface | BeanFactory |
interface | ApplicationContext |
BeanFactory : 모든 유형의 객체를 관리할 수 있는 configuration 매커니즘을 제공
ApplicationContext : BeanFactory의 하위 인터페이스
Bean : Spring에서 Application의 backbone(척추-핵심)을 형성하고 Spring IoC 컨테이너에 의해 인스턴스화 되고 조립 및 관리되는 객체
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
<!-- 이는 XML에서는 아래와 같이 표현된다.
id : Bean의 식별자(문자열)
class : Bean의 type을 정의하는 완전한 (path를 포함한)클래스 이름 -->
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
<beans>
<import resource="services.xml"/> <!-- 동일한 디렉토리 or 클래스 path -->
<import resource="resources/messageSource.xml"/> <!-- resource 하위 -->
<import resource="/resources/themeSource.xml"/> <!-- resource 하위 : 선행 슬래시('/') 무시됨, 하지만 이렇게 쓰면 상대경로로 오해할 수 있으므로 사용하지 않는 것이 좋다.-->
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
📌일반적인 엔터프라이즈 어플리케이션은 단일 오브젝트(Spring에서의 Bean)로 구성되지 않는다. Bean은 함께 작동하는 몇 가지 객체를 가진다.
🔪스프링 컨테이너는 컨테이너가 생성 될 때 각 Bean의 configuration을 확인하지만, Bean property는 실제로 Bean이 생성되기 전까지 설정되지 않는다.(중요✔️)
단, Bean의 생성은 컨테이너 생성과 동일하다.(<Default>Singleton-scope, 사전 인스턴스화 인 경우)
🤔순환 종속
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
<!--
*
*
* setter 주입을 사용한 경우 -->
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
<!--
*
*
* 생성자 주입을 사용한 경우 -->
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method;
// 이 메소드에 대한 argument는 해당 인수가 실제로 사용되는 방법에 관계없이
// 리턴되는 Bean의 의존성으로 간주될 수 있다.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
<!--
*
*
* Static Factory를 사용해 인스턴스를 리턴한 경우 -->
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<!-- property가 아닌 외부 Bean을 강제로 우선 초기화 시키게 한다. -->
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
Scope | Description |
---|---|
singleton | (default) 단일 Bean 정의의 범위를 각 Spring IoC 컨테이너의 단일 오브젝트 인스턴스로 지정한다. |
prototype | 단일 Bean 정의의 범위를 임의의 수의 오브젝트 인스턴스로 지정한다. |
request | 단일 Bean 정의의 범위를 단일 HTTP 요청의 라이프 사이클로 지정한다. 즉, 각 HTTP 요청에는 단일 Bean 정의의 뒤에서 생성된 고유한 Bean 인스턴스가 존재한다. HTTP request가 상주하는 Spring ApplicationContext 내에서만 유효 객체를 생성하고 재사용한다. (web-aware) |
session | 단일 Bean 정의의 범위를 HTTP 세션의 라이프 사이클로 지정한다. Bean을 해당 세션이 가진 ApplicationContext에 바인딩한다. (web-aware) |
application | 단일 Bean 정의의 범위를 ServletContext의 라이프 사이클로 지정한다. Only valid in the context of a web-aware Spring ApplicationContext |
websocket | 단일 Bean 정의의 범위를 WebSocket의 라이프 사이클로 지정한다. Only valid in the context of a web-aware Spring ApplicationContext |
🔎Singleton-scope
단일 Bean의 공유 인스턴스 하나만 관리되며, 하나의 특정 Bean 인스턴스의 정의와 일치하는 ID 또는 ID들을 가진 Bean에 대한 모든 요청은 Spring 컨테이너에 의해 리턴된다.
다시 말해서 Bean의 정의와 범위를 singleton으로 정의할 때, Spring IoC 컨테이너는 해당 Bean 정의에 의해 정의된 오브젝트의 인스턴스를 정확히 하나 만든다. 이 단일 인스턴스는 이러한 싱글 톤 Bean의 캐시에 저장되며 해당 캐시에 등록된 Bean의 이름과 일치하는 모든 후속 요청 및 참조는 캐시 된 오브젝트를 리턴한다.
🤔 Spring을 구성하기에 Annotation-based가 XML보다 났나요?
👉 "it depends."
각 방법마다 장단점이 있으며 일반적으로 어떤 전략이 더 적합한지 결정하는 것은 개발자의 몫이다.
어노테이션이 정의된 방식 덕에, 어노테이션은 선언에 많은 컨텍스트를 제공하여 더 짧고 간결한 configuration을 만들 수 있게 한다. 하지만 XML은 소스코드를 수정하거나 다시 컴파일하지 않고도 컴포넌트를 연결하는데 탁월하다. 일부 개발자는 소스에 가까운 연결 방식을 선호하는 반면, 다른 일부는 어노테이션이 달린 클래스가 더 이상 POJO가 아니며 configuration이 분산돼 제어하기가 더 어렵다고 주장한다.
선택에 관계없이 Spring은 두 스타일을 모두 수용하고 함께 혼합 할 수 있다. Spring은 JavaConfig 옵션을 통해 대상 컴포넌트 소스 코드를 건드리지 않고, 어노테이션을 코드에 직접 개입하지 않는 방식으로(non-invasive way ) 사용할 수 있으며, 툴링 측면에서 모든 configuration 스타일이 Spring Tools for Eclipse에서 지원되고 있다.
📌어노테이션 삽입은 XML 삽입 전에 수행된다. 따라서 XML configuration은 두 방법 모두를 통해 연결된 속성에 대한 어노테이션을 무시한다.
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
영향을 받는 Bean property(예제에서 MovieFinder)을 구성시, Bean 정의의 명시적 property value 또는 autowiring을 통해 채워야 함을 나타낸다.
만약 그렇게 하지 않을 경우, 컨테이너에서 예외가 발생한다.
init메소드 등을 활용해 Bean 클래스 자체에 해당 property가 채워졌는지 확인하는 로직이 필요하다.
❌필수적인 setting에는 생성자 주입을 사용하기 위해 Spring Framework 5.1부터 공식적으로 사용하지 않는다.
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
생성자, setter, class내 의존성 property (심지어 Array, Collection, Map에도) 사용할 수 있다.
어노테이션이 달린 메소드 및 필드가 required = true 가 default로 씌어진다.
지정된 주입 지점에 대해 일치하는 후보 Bean이 없으면 Autowiring은 실패한다. 선언된 배열, 컬랙션 또는 맵의 경우 하나 이상의 일치하는 요소가 필요하다.
❗️Spring Framework 4.3부터 대상 Bean이 하나의 생성자만 가진다면 이 생성자에 대한 @Autowired 어노테이션은 필요하지 않다. 그러나 여러 생성자를 사용할 수 있는 경우, 컨테이너에 사용할 생성자를 지시하기 위해 적어도 하나에 @Autowired 어노테이션을 추가해야한다.
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
/**************************/
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
Type별 autowiring은 여러 후보로 이어질 수 있으므로 선택 프로세스를 세밀하게 조정해야할 경우가 있다.
@Primary는 여러 Bean이 단일 값 의존성에 자동 연결될 후보인 경우 특정 Bean에 우선 순위가 있음을 나타낸다. (MovieConfiguration에는 MovieCatalog Bean이 두 개 존재하지만 @Primary를 이용해 firstMovieCatalog()가 MovieRecommender의 의존성으로 들어감을 명시한다.)
여러 후보 중 하나의 @Primary Bean이 존재하면 이는 자동연결 값이 된다.
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
/**************************/
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
@Primary는 단 하나에만 설정할 수 있다.
@Qualifier는 이보다 더 세밀하게 제어하는 경우로써, Qualifier 값을 특정 property와 연관시켜 각 유형에 대해 원하는 Bean이 선택되도록 type 일치 set을 좁힐 수 있다.
ref. Custom Qualifier와 자가 주입
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
field나 Bean property setter 메소드에서 @Resource 어노테이션을 사용하여 주입을 지원한다.
이는 JavaEE의 일반적인 패턴이며, Spring 관리 객체에 대해서도 이 패턴을 지원한다.
name 속성을 갖는데, 기본적으로 이를 삽입할 Bean의 이름으로 해석한다.
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
이름이 명시적으로 지정되지 않은 경우엔 기본 이름은 필드 이름 또는 setter 메소드의 Bean property name을 사용한다.(위 예제는 @Resource(name="movieFinder")와 같다.)
//@Value is typically used to inject externalized properties :
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
//with the following configuration :
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
//application.properties file :
catalog.name=MovieCatalog
catalog 매개 변수와 필드는 MovieCatalog(← bean이다.)값과 같다.
Spring은 내장 value resolver을 지원하며, property value를 분석하지 못하면 property name(e.g. ${catalog.name})이 값으로 삽입된다.
존재하지 않는 값을 대체할 수 없도록 엄격하게 제어하려면 다음처럼 PropertySourcesPlaceholderConfigurer Bean을 선언해야한다.
//javaConfig를 사용하여 PropertySourcesPlaceholderConfigurer를 구성할 때 @Bean 메소드는 정적이어야한다.
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
위 configuration을 사용하면 어떤 ${} placeholder를 확인할 수 없는 경우 스프링 초기화 오류가 발생한다. → setPlaceholderPrefix, setPlaceholderSuffix, setValueSeparator같은 메소드를 사용해 placeholder를 커스텀으로 정의할 수도 있다.
✔️@PostConstruct
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
CommonAnnotationBeanPostProcessor는 @Resource 어노테이션 뿐만 아니라 JSR-250 lifecycle 어노테이션(javax.annotation.PostConstruct 및 javax.annotation.PreDestroy)도 인식한다.
Spring 2.5에서 도입된 이러한 어노테이션은 초기화 콜백 및 소멸 콜백에 설명된 lifecycle 콜백 메커니즘의 대안을 제공한다.
CommonAnnotationBeanPostProcessor가 Spring ApplicationContext에 등록된 경우, 이런 어노테이션 중 하나를 전달하는 메소드는 해당 Spring lifecycle 인터페이스 메소드 또는 명시적으로 선언된 콜백 메소드와 동일한 시점에서 lifecycle에서 호출된다.
위의 코드에서 캐시는 초기화시 미리 채워지고, 소멸시 지워진다.
❌ @Resource와 마찬가지로 @PostConstruct 및 @PreDestroy 어노테이션 유형은 JDK 6에서 8까지 표준 Java 라이브러리의 일부였다. 그러나 전체 javax.annotation 패키지는 JDK 9의 핵심 Java 모듈과 분리되어 결국 JDK 11에서 제거되었다. 필요한 경우 javax.annotation-api 아티팩트를 Maven Central을 통해 확보해야하며 다른 라이브러리와 같이 애플리케이션의 클래스 경로에 추가하면 사용할 수 있다.
💁Classpath를 스캔하여 후보 컴포넌트의 감지를 암시하는 옵션에 대해 설명한다.
Spring 3.0+
Spring JavaConfig 프로젝트가 제공하는 많은 기능은 핵심 Spring Framework을 일부이다!
이를 통해 기존 XML 파일을 사용하지 않고 Java를 사용하여 Bean을 정읳라 수 있다.
ref. @Configuration, @Bean, @import, @DependsOn
✔️@Repository 어노테이션은 레포지토리의 역할 또는 스테레오 타입(=DAO)을 충족하는 모든 클래스를 마킹한다.
Spring은 추가 스테레오 타입 어노테이션을 제공한다.
Spring의 IoC 기능을 위한 기본 기반을 제공한다.
specific contracts(뭐라고 번역해야할지 모르겠음🤔)은 주로 Spring의 다른 부분이나 관련 프레임워크와의 통합에 사용되며, DefaultListableBeanFactory 구현체는 상위레벨의 GenericApplicationContext 컨테이너 내의 주요 위임체이다.
BeanFactory 및 관련 인터페이스(e.g. BeanFactoryAware, InitializingBean, DisposableBean)는 다른 프레임워크 컴포넌트와의 중요한 통합 지점이다.
어플리케이션 레벨의 Bean은 동일한 콜백 인터페이스를 사용할 수 있음 → But, 일반적으로 어노테이션 또는 프로그래밍 configuration을 통해 선언적 의존성 삽입을 선호한다.
core BeanFactory API level과 DefaultListableBeanFactory 구현체는 사용될 configuration format이나 컴포넌트 어노테이션에 대해 추정하고 있지 않다.
이러한 모든 특징은 상속(e.g. XmlBeanDefinitionReader, AutowiredAnnotationBeanPostProcessor)을 통해 제공되며 공유 BeanDefinition 객체 핵심 메타데이터 표현으로 제공된다.
이것이 Spring의 컨테이너를 유연하고 확장 가능하게 하는 본질이다.
🔎BeanFactory와 ApplicationContext 인터페이스/구현체에서 제공하는 기능
-추가중-
-추가중-
좋은 글 잘보고 갑니다!