SPRING - 오브젝트와 의존관계 (5)

🚀 스프링 IOC

스프링의 핵심을 담당하는 것은 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것입니다. 한번 알아보죠~!

🔧 오브젝트 팩토리를 이용한 스프링 IOC

  • 스프링 빈
    • 스프링에서는 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트를 빈이라고 부릅니다. 또한, 스프링 컨테이너가 생성과 관계설정, 사용들을 제어해주는
      제어의 역전이 적용된 오브젝트를 가리키는 말입니다.

애플리케이션 컨텍스트, 빈 팩토리

  • 빈의 생성과 관계설정 같은 제어를 담당하는 IOC 오브젝트를 빈 팩토리 또는 애플리케이션 컨텍스트라고 부릅니다.
  • 별도의 설정 정보(오브젝트를 어떻게 생성하고, 어떤 의존관계를 맺어주고)를 참고해서 빈의 생성, 관계설정 등의 제어 작업을 총괄합니다.

설정정보를 만드는 방법은 여러가지가 있는데,

AppConfig.java

@RequiredArgsConstructor
public class AppConfig {

    private final EntityManager em;

    public UserRepositoryV5 userRepository(){
        return new UserRepositoryV5Impl(em);
    }

    public UserServiceV5 userService(){
        return new UserServiceV5(userRepository());
    }
}

이러한 (롬복을 사용하긴 했지만?) 순수 자바 코드도, 스프링 애노테이션을 활용하 간단하게 설정정보를 만들어 줄 수 있습니다.

설정 정보 만드는 방법
1. 애플리케이션 컨텍스트를 위한 오브젝트 설정을 담당하는 클래스라고 인식할수 있도록 @Configuration이라는 어노테이션을 추가합니다.
2. 오브젝트를 만들어주는 메소드에는 @Bean이라는 애노테이션을 추가합니다.

AppConfig.java

@Configuration
public class SpringAppConfigV1 {

    @Bean
    @Primary
    public LocalEntityManagerFactoryBean getEmf(){
        LocalEntityManagerFactoryBean emf=new LocalEntityManagerFactoryBean();
        emf.setPersistenceUnitName("hello");
        return emf;
    }

    @Bean
    @Primary
    public EntityManager getEm(){
        return getEmf().getObject().createEntityManager();
    }

    @Bean
    public UserRepositoryV5 userRepository(){
        return new UserRepositoryV5Impl(getEm());
    }

    @Bean
    public UserServiceV5 userService(){
        return new UserServiceV5(userRepository());
    }
}

현재, 이 설정 정보에서는 EntityManager Bean을 직접 만들어줬습니다. 헌재까지는, 스프링 부트에서 자동 주입하여 주는 EntityManager를 사용해주었습니다., 지금
이 설정정보 클래스에서는 아직 스프링 부트에서 만든 EntityManager Bean이 존재하지 않기 때문에 따로 EntityManager Bean과 TransactionManager Bean을
만들어야합니다. 저는 Transaction이 존재하지 않아도 테스트를 수행하는데는 무리가 없다 생각하여, EntityManager만 생성해 주었고
스프링 애플리케이션이 로드될때, 스프링 부트에서 만드는 EntityManager와 혼동이 생길 수 있으므로 @Primary 애노테이션을 추가로 붙여 주었습니다.

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/dependencytest"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
        </properties>
    </persistence-unit>
</persistence>

이렇게, 애플리케이션 컨텍스트가 Ioc방식의 기능을 제공할 때 사용할 완벽한 설정정보를 만들었습니다.

이제, 테스트 코드를 통해 ApplicationContext를 만들어봅시다.
1. AnnotationConfigApplicationContext를 이용하여 설정정보를 적용한 애플리케이션 컨텍스트 생성
2. getBean() 메소드를 활용하여 설정정보에 정의해 놓은 Bean을 가져올 수 있습니다.

SpringAppConfigTest.java

@SpringBootTest
@Transactional
public class SpringAppConfigV1Test {

    @Test
    @DisplayName("Bean Test")
    void 빈_테스트(){
        User user=createUser("hong","123");

        AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(SpringAppConfigV1.class);

        UserServiceV5 userService= ac.getBean("userService",UserServiceV5.class);

        Long saveId = userService.join(user);

        User findUser = userService.findOne(saveId);

        Assertions.assertThat(findUser.getName()).isEqualTo(user.getName());
        Assertions.assertThat(findUser.getPassword()).isEqualTo(user.getPassword());
    }

    private User createUser(String name, String password){
        return User.createUser()
                .name(name)
                .password(password)
                .build();
    }
}

동작 확인

동작 확인 까지 하였습니다. 그런데 이렇게 봐서는 더 번거로울 뿐이지 딱히 장점은 없어보입니다.

이러한 고민은, 스프링은 날려버리라고 합니다. 얻을 수 없는 방대한 기능을 제공할테니..

🔧 애플리케이션 컨텍스트의 동작방식

오브젝트 팩토리와 애플리케이션 컨텍스트

사용 방식 및 설정 정보 먼저 보시죠.

오브젝트 팩토리

@RequiredArgsConstructor
public class AppConfig {

    private final EntityManager em;

    public UserRepositoryV5 userRepository(){
        return new UserRepositoryV5Impl(em);
    }

    public UserServiceV5 userService(){
        return new UserServiceV5(userRepository());
    }
}
AppConfig appConfig=new AppConfig(em);
UserServiceV5 userService= appConfig.userService();

애플리케이션 컨텍스트

@Configuration
public class SpringAppConfigV1 {

    @Bean
    @Primary
    public LocalEntityManagerFactoryBean getEmf(){
        LocalEntityManagerFactoryBean emf=new LocalEntityManagerFactoryBean();
        emf.setPersistenceUnitName("hello");
        return emf;
    }

    @Bean
    @Primary
    public EntityManager getEm(){
        return getEmf().getObject().createEntityManager();
    }

    @Bean
    public UserRepositoryV5 userRepository(){
        return new UserRepositoryV5Impl(getEm());
    }

    @Bean
    public UserServiceV5 userService(){
        return new UserServiceV5(userRepository());
    }
}
AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(SpringAppConfigV1.class);
UserServiceV5 userService= ac.getBean("userService",UserServiceV5.class);

여기서 보시면, 오브젝트 팩토리는 service 오브젝트를 생성하고 repository와 관계를 맺어주는 제한적인 역할을 하는 데에 반해,
애플리케이션 컨텍스트는 IOC를 적용해서 관리할 모든 오브젝트에 대한 생성과 관계설정을 담당합니다.

애플리케이션 컨텍스트는 @Configuration 이 붙은 설정 정보를 활용하여 등록 된 빈을 호출해서 가져온 것을 클라이언트가 getBean()을 요청할 때
전달해 줍니다.

애플리케이션 컨텍스트를 사용하는 이유는 범용적이고 유연한 방법으로 Ioc기능을 확장하기 위해서 입니다. 이렇게만 해서는 아직 까지도 장점이 뭔지
감이 안잡힙니다. 자세히 살펴보죠.

애플리케이션 컨텍스트를 사용했을 때의 장점

  • 클라이언트는 구체적인 팩토리 클래스를 알 필요 없다.
    • 애플리케이션 발전시, IoC를 적용한 오브젝트도 계속 추가될 것, 클라이언트가 필요한 오브젝트를 가져오려면 어떤 팩토리 클래스를 사용 했는지를 알아야 하고,
      필요할 때 마다 팩토리 오브젝트를 생성해야하는 번거로움이 있습니다. (AppConfig ac=new AppConfg(), 새로운 팩토리인 DaoFactory가 만들어 졌다면
      DaoFactory da=new DaoFactory() 이렇게 구체적으로 알아야 함.)
    • 애플리케이션 컨텍스트를 활용하게 되면 일관된 방식으로 원하는 오브젝트를 가져올 수 있습니다.(AnnotaionConfigApplicationContext ac=new Annotaion ConfigApplication(@Configuration이 붙은 클래스))
  • 애플리케이션 컨텍스트는 종합 IoC 서비스를 제공.
    • 의존관계를 맺어주는 것 이상으로 오브젝트가 만들어지는 방식, 시점과 전략 등등 효과적인 다양한 기능들을 제공합니다.
  • 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공

👋 다음 포스팅에서 이어서 하도록 하겠습니다!

좋은 웹페이지 즐겨찾기