Spring IoC와 DI에 대해 정리.
토비의 스프링 3.1과 스프링 철저입문 도서, 타 블로그의 정리 내용을 보고 정리했습니다.
책 위주로 정리한 내용이고 풀어서 정리되어있기 때문에 간단한 정리는 타 블로그 참고를 추천드립니다.
Reference
- 토비의 스프링 3.1
- 스프링 철저 입문
IoC와 DI에 대한 정리는 아래와 같은 순서로 진행.
- IoC Container란
- IoC Container의 종류와 사용방법
- Web Application의 IoC Container 구성
- Bean 설정 방식, DI 방식
- Autowiring, ComponentScan
- Bean Scope
- Bean 설정 분할과 profile별 설정
- Bean 생명주기, IoC Container 종료
Bean 설정 분할
IoC 컨테이너에서 관리하는 bean이 많아지면 많아질수록 설정 내용도 많아져서 관리하기가 어려워진다.
이럴때는 bean 설정 범위를 명확히 하고 가독성도 높이기 위해 목적에 맞게 분할하는 것이 좋다.
분할 방식에 대해서는 자바 기반 설정에서의 분할, XML 기반 설정에서의 분할로 나눠 정리한다.
어노테이션 기반 설정 방식에 대한 분할은 책에 나와있지도, 언급되지도 않았는데 어노테이션 기반 설정 방식의 경우 ComponentScan을 통해 bean을 컨테이너에 등록해 처리하는 방식이다보니 별다르게 분할을 해야할 이유가 없기 때문이지 않을까라고 생각한다.
1. 자바 기반 설정 방식에서의 분할
자바 기반 설정방식에서 설정된 내용(Configuration Class)을 분할할 때는 @Import 어노테이션(org.springframework.context.annotation.Import)을 사용한다.
예제는 userService, productService, adminService 라는 세개의 빈을 분할해서 테스트한 예제다.
//UserConfig Class
@Configuration
public class UserConfig {
@Bean
UserService userService() {
return new UserServiceImpl();
}
}
//ProductConfig Class
@Configuration
public class ProductConfig {
@Bean
@Scope("prototype")
ProductService productService() {
return new ProductServiceImpl();
}
}
//AppConfig Class
@Configuration
@Import({UserConfig.class, ProductConfig.class})
public class AppConfig {
@Bean
AdminService adminService() {
return new AdminServiceImpl();
}
}
//test Class
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public class testClass {
@Test
public void divTest() {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
String[] beanArr = context.getBeanDefinitionNames();
System.out.println("bean Arr");
for(String beanName : beanArr)
System.out.println(beanName);
UserService userService1 = context.getBean(UserService.class);
UserService userService2 = context.getBean(UserService.class);
assertEquals(userService1, userService2);
AdminService adminService1 = context.getBean(AdminService.class);
AdminService adminService2 = context.getBean(AdminService.class);
assertEquals(adminService1, adminService2);
ProductService productService1 = context.getBean(ProductService.class);
ProductService productService2 = context.getBean(ProductService.class);
assertEquals(productService1, productService2);
}
}
/* 테스트 결과
beanArr
processor 및 EventListener
...
appConfig
com.example.UserConfig
userService
com.example.ProductConfig
productService
adminService
productService1, productService2 검증에 대한 오류
*/
예제를 보면 productService와 userService를 각각 ProductConfig와 UserConfig에 분할해서 설정했다.
그리고 테스트에서 잘 들어오는지 확인해보기 위해 productService는 prototype으로 설정했고 AppConfig에는 adminService라는 bean을 하나 설정해주고 Import로 ProductConfig와 UserConfig를 설정해줬다.
이때 주의사항은 분할된 설정 클래스도 일반 설정 클래스처럼 @Configuration 어노테이션을 꼭 붙여줘야 한다.
테스트에서는 AnnotationConfigApplicationContext로 ApplicationContext를 만들어주고
해당 ApplicationContext에 속해있는 bean의 이름들을 getBeanDefinitnionNames()로 받아 출력했다.
그리고 각 bean들을 두개씩 가져와 테스트해보니 userService와 adminService는 정상적으로 singleton으로 생성된 것을 확인했고 productService역시 오류가 발생했으니 prototype으로 잘 처리되었다고 볼 수 있다.
테스트 결과를 보면 조금 특이한 점이 있는데 이전에 설정 방식에 대해 정리할때는 딱 bean의 이름만 나왔었지만
이번에는 분할한 설정 클래스 역시 같이 출력되었다.
그래서 context.getBean(UserConfig.class); 로 해당 bean을 가져와봤는데 아무 문제가 발생하지 않았다.
bean을 가져올 수 있다고 해서 거기서 UserService bean을 가져온다거나 하는건 안되는것 같고 UserConfig안에 메소드를 하나 만들어서 테스트했더니 해당 메소드를 호출할 수는 있었다.
근데 그 외에는 딱히 사용할 수 있는 뭔가는 보이지 않았다...
2. XML 기반 설정 방식에서의 분할
XML 기반 설정 방식에서 파일을 분할할 때는 <import> 요소를 사용한다.
예제는 자바 기반 설정 방식에서와 동일한 형태로 테스트한다.
<!-- product-config.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="productService" class="com.example.ProductServiceImpl" scope="prototype"/>
</beans>
<!-- user-config.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.UserServiceImpl"/>
</beans>
<!-- 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">
<import resource="User-config.xml"/>
<import resource="Product-config.xml"/>
<bean id="adminService" class="com.example.AdminServiceImpl"/>
</beans>
//test class
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public class testClass {
@Test
public void divTest() {
ApplicationContext context = new GenericXmlApplicationContext(
"file:src/main/webapp/WEB-INF/applicationContext.xml");
String[] beanArr = context.getBeanDefinitionNames();
System.out.println("bean Arr");
for(String beanName : beanArr)
System.out.println(beanName);
UserService userService1 = context.getBean(UserService.class);
UserService userService2 = context.getBean(UserService.class);
assertEquals(userService1, userService2);
AdminService adminService1 = context.getBean(AdminService.class);
AdminService adminService2 = context.getBean(AdminService.class);
assertEquals(adminService1, adminService2);
ProductService productService1 = context.getBean(ProductService.class);
ProductService productService2 = context.getBean(ProductService.class);
assertEquals(productService1, productService2);
}
}
/* 테스트 결과
beanArr
userService
productService
adminService
productService1, productService2 검증에 대한 오류
*/
XML 기반 설정에서 주의할점은 import시에 경로다.
지금 예제에서는 applicationContext.xml과 같은 webapp/WEB-INF 위치에 *-config.xml 파일을 생성했기 때문에 저렇게 딱 파일명만 적어도 되지만 resource 폴더에 생성하는 경우 classpath: 를 사용한다거나 해서 경로를 잘 잡아줘야 한다.
분할된 bean 정의 파일은 일반적인 bean 정의 파일과 동일한 형식으로 작성하면 된다.
테스트 결과를 보면 자바 기반 설정 방식과는 조금 다른것을 볼 수 있는데 자바 기반 설정방식에서는 import 해준 클래스까지 모두 출력된 반면에 xml 기반 설정 방식에서는 생성하고자 했던 bean의 이름만 딱 나오는 것을 볼 수 있다.
사용하는데에 대한 차이는 없었지만 컨테이너에 등록되는 bean의 차이가 좀 있다는 점이 차이점이라면 차이점일 수 있겠다.
프로파일(profile)별 설정
스프링에서는 설정 파일을 특정 환경이나 목적에 맞게 선택적으로 사용할 수 있도록 그룹화 할 수 있으며, 이 기능을 프로파일(profile)이라고 한다.
예를들어, 애플리케이션이 실행될 환경마다 서로 다른 프로파일을 만든다면 개발 환경을 위한 development profile, 검증을 위한 staging profile, 실제 운영 환경을 위한 production profile등을 만들 수 있을 것이다.
프로파일 정의는 모든 자바, XML, 어노테이션 기반 설정 방식 모두 가능하다.
1. 자바기반 설정 방식에서의 profile 정의
자바기반 설정 방식에서 프로파일을 정의할 때는 @Profile 어노테이션(org.springframework.context.annotation.Profile)을 사용한다.
@Configuration
@Profile("development")
public class DevelopmentConfig {
....
}
@Configuration
@Profile("staging")
public class satgingConfig {
....
}
@Configuration
@Profile("production")
public class ProductionConfig {
....
}
이렇게 클래스 단위로 @Profile 어노테이션을 붙여 프로파일을 정의할 수 있다.
그리고 클래스 단위가 아닌 메소드 단위로도 정의가 가능하다.
@Configuration
pbulic class AppConfig {
@Bean(name = "dataSource")
@Profile("development")
DataSource dataSourceForDevelopment() {
....
}
@Bean(name = "dataSource")
@Profile("staging")
DataSource dataSourceForStaging() {
....
}
@Bean(name = "dataSource")
@Profile("production")
DataSource dataSourceForProduction() {
....
}
}
@Profile 어노테이션에서는 @Profile({"development", "staging"})과 같이 여러개의 프로파일을 지정하거나
@Profile("!production")과 같이 production 프로파일만을 제외한다는 부정형으로 표현할 수도 있다.
프로파일을 개발 환경별로 구분하는 방식은 개발이 어느정도 진행된 다음에 다른 개발 환경으로 쉽게 옮겨갈 수 있게 해주는 유용한 기법이다.
2. XML 기반 설정에서의 profile 정의
XML 기반 설정 방식에서는 <beans> 요소의 profile 속성을 활용한다.
<!-- 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"
profile="development">
<!-- 이 안에서 정의한 내용은 지정한 프로파일(development)내에서만 유효하다. -->
</beans>
<!-- 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">
<beans profile="development">
<!-- 이 안에서 정의한 내용은 development 프로파일 내에서만 유효하다 -->
<bean id="datasource" class="....">
<!-- 생략 -->
</bean>
</beans>
<beans profile="staging">
<!-- 이 안에서 정의한 내용은 staging 프로파일 내에서만 유효하다 -->
<bean id="datasource" class="....">
<!-- 생략 -->
</bean>
</beans>
<beans profile="production">
<!-- 이 안에서 정의한 내용은 production 프로파일 내에서만 유효하다 -->
<bean id="datasource" class="....">
<!-- 생략 -->
</bean>
</beans>
</beans>
XML 기반 설정 방식에서는 예제중에 위에 있는 경우처럼 각 프로파일별로 XML 파일을 만들어 정의하는 방법과 하나의 XML 파일 안에 <beans> 요소를 통해 여러개의 프로파일을 정의하는 방법으로 사용할 수 있다.
그리고 여러 프로파일을 동시에 지정하고 싶다면 <beans profile="profile1, profile2, ....."> 과 같이 쉼표로 구분해서 나열하면 된다.
3. 어노테이션 기반 설정 방식에서의 profile 정의
어노테이션 기반 설정 방식에서는 컴포넌트에 @Profile 어노테이션을 부여하고 프로파일을 지정하면 된다.
@Component
@Profile("staging")
public class DummyUserRepository implements UserRepository {
....
}
만약 프로파일이 별도로 지정되지 않은 bean 설정이 있다면 이것들은 모든 프로파일에서 사용이 가능하다고 보면 된다.
프로파일 선택
실행시 어떤 프로파일을 선택해야 할지에 대한 정보는 시스템 프로퍼티를 통해 전달 할 수 있는데, 자바 애플리케이션을 실행할 때 명령행 옵션으로 spring.profiles.active라는 프로퍼티 값과 사용할 프로파일 이름을 지정하면 된다.
# 자바 명령행 옵션으로 프로파일을 지정하는 방법
-Dspring.profiles.active=production
만약 프로파일을 여러개 선택하고 싶으면 쉼표로 구분해서 나열할 수 있다.
자바 명령행 옵션으로 전달하는 방법 외에도 환경 변수를 이용할 수도 있는데 환경 변수명 SPRING_PROFILES_ACTIVE에 사용할 프로파일 이름을 지정하면 된다.
# 환경 변수로 프로파일을 지정하는 방법
export SPRING_PROFILES_ACTIVE=production
웹 애플리케이션에서는 웹 애플리케이션 설정 파일(Web Application Deployment Descriptor)인 web.xml에 다음과 같이 작성하면 된다.
<!-- web.xml -->
....
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>production</param-value>
</context-param>
....
spring.profiles.active를 따로 지정하지 않았다면 기본값으로 spring.profiles.default에서 지정된 프로파일을 사용한다.
웹 애플리케이션이라면 위와 같이 web.xml에 spring.profiles.default를 설정해서 기본 프로파일을 지정한 다음 프로파일을 바꾸고 싶을때만 자바 명령행 옵션으로 spring.profile.active를 지정해 기본 프로파일을 덮어쓰면 된다.
'Spring' 카테고리의 다른 글
JPA 양방향 연관관계 Entity DB 저장 문제 (0) | 2022.12.19 |
---|---|
WebSecurityConfigurerAdapter Deprecated 해결 (0) | 2022.12.06 |
IoC와 DI(6. Bean Scope) (0) | 2022.08.29 |
IoC와 DI(5. Autowiring, ComponentScan) (0) | 2022.08.25 |
IoC와 DI(4. Bean 설정 방식, DI 방식) (0) | 2022.08.24 |