ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • IoC 컨테이너와 빈(1) - 빈설정, @autowired
    백기선(인프런 강의)/스프링 프레임워크 핵심 기술 2020. 3. 15. 18:37
    반응형

    스프링 IoC 컨테이너와 빈

    IoC(Inversion of Control)

    의존관계주입(Dependecy Injection)이라고도 하며, 어떤 객체가 사용하는 의존 객체를 직접 만들어 사용하는게 아니라, 주입받아 사용하는 방법

     

    스프링 IoC컨테이를 사용하는 이유

    여러 개발자들이 스프링 커뮤니티에서 만들어낸 여러가지 DI(Dependecy Injection)방법과 노하우가 쌓여있는 프레임워크

    스프링 IoC 컨테이너

    • 애플리케이션 컨포넌트의 중앙 저장소
    • 빈 설정 소스로 부터 빈 정의를 읽어들이고, 빈을 구성하고 제공한다.
    • PoJo(Plain Old Java Obejct) 객체를 빈으로 등록하고, 등록된 빈들을 주입받아서 사용가능
    • BeanFactory

    • 스프링 IoC 컨테이너가 관리하는 객체
    • 장점
      • 의존성 관리 
      • Bean으로 등록되어있지 않으면 기본적으로 싱글톤으로 사용된다.
        • 싱글톤 : 하나
          • 프로토타입에 비해서 메모리면에서 효율적이다.
          • 미리 만들어 놓은 객체를 사용하기 때문에 런타임 시 유리
            • ex) DB와 연결된 객체
        • 프로토타입 : 매번 다른 객체
      • 라이프사이클 인터페이스
        • PostConstruct 
          • 객체의 초기화 부분
          • 객체가 생성된 후 별도의 초기화 작업을 위해 실행하는 메소드를 선언한다.
          •  WAS가 띄워질 때 실행된다.
        • PreDestory
          • 마지막 소멸 단계
          • 스프링 컨테이너에서 객체(빈)를 제거하기 전에 해야할 작업이 있다면 메소드위에 사용하는 어노테이션.
          • close() 하기 직전에 실행 -> ((AbstractApplicationContext) context).close()

    빈 생명주기

    2020/03/15 - [spring/Srping Bean] - Spring Bean Life Cycle

     


     

    Applicationcointext와 다양한 빈설정 방법

    스프링 IoC 컨테이너의 역활

    • 빈 인스턴스 생성
    • 의존 관계 설정
    • 빈 제공

    ApplicationContext

    XML 기반 설정 (ClassPathXmlApplicationContext)

    <bean id = "testService1" class = "kr.co.study.service.TestService1">
    	<property name = "testService2" ref="testService2">
        // 앞의 testService2는 setter
        // 뒤의 testService2는 bean의 ID
    </bean>
        
    <bean id = "testService2" class = "kr.co.study.service.TestService2"/>

     

    어노테이션 bean scan(ClassPathXmlApplicationContext)

    <context:component-scan base-package="kr.co.study.service">

     

    자바 설정 (AnnotationConfigApplicationContext)

    @Configuration
    public class ApplicationConfig {
    	@Bean
    	public TestService1 testService1(TestService2 service) {
    		return new TestService1(service);
    	}
    	
    	@Bean
    	public TestService2 testService2(TestService1 service) {
    		return new TestService2(service);
    	}
    }

     

    Srping 4.3부터 어떠한 클래스에 생성자가 1개뿐이고, 그 생성자를 주입받는 래퍼런스 변수들이 Bean으로 등록되어있다면 그 빈을 자동으로 주입하도록 되어있다.

     


     

    IoC 컨테이터 : @Autowired

    Field Injection

    @Controller
    public class HomeController {
    	
    	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    	
    	@Autowired
    	private TestService1 service1;
    	
    	@RequestMapping(value = "/", method = RequestMethod.GET)
    	public String home(Locale locale, Model model) {
    		
    		service1.test1();
    		return "home";
    	}
    }
    @Service
    public class TestService1 {
    	@Autowired
    	TestService2 service2;
    	
    	
    	public void test1() {
    		
    		service2.test2();
    	}
    }
    
    @Service
    public class TestService2 {
    	
    	@Autowired
    	TestService1 service1;
    	
    	public void test2() {
    		service1.test1();
    	}
    
    }

    StackOverFlowError

    Field Injection의 단점

    순환참조

    • 위와같이 @Autowired를 사용하는 것이다. 위의 단점은 Bean객체들이 서로를 바라보게 된다면 위와 같은 오류(StackOverflowError)가 발생이 된다. 이게 바로 서로를 참조하게 되는 순환참조이다
    • 순환참조는 객체 생성시점에는 오류를 잡을 수 없고 객체생성 후 로직상에서 잡을 수 밖에 없다.

    DI 컨네이너와 결합이 매우 강하게되어 외부에서 사용하기 어려워진다.

    • DI 프레임 워크의 핵심 아이디어 중 하나는 ‘관리되는 클래스가 DI 컨테이너에 의존하지 않아야 한다’ 이다. 즉, 모든 필수 종속성을 전달하면 독립적으로 인스턴스화 할 수있는 단순한 POJO 여야한다. 이렇게 하면 DI 컨테이너를 시작하지 않고 단위 테스트에서 이를 인스턴스화하고 별도로 테스트 할 수 있다. 하지만 필드 주입의 경우, 스프링 설정 파일을 읽고 모든 Bean 설정이 되어야 테스트를 할 수 있다.

    의존 관계가 보이지 않는다.

    • DI 컨테이너를 사용하면 클래스가 스스로 종속성을 관리 할 필요가 없음을 의미한다. 의존성을 얻기 위한 책임을 더 이상 클래스가 하지 않는다. DI 컨테이너 또는 단위 테스트의 경우, mock 객체를 직접 만드는 등과 같은 다른 무언가가 책임진다. 그래서 클래스가 더 이상 종속성을 가져올 책임이 없으면, 수정자 또는 생성자를 사용하여 클래스 의존관계를 명확하게 보여줘야 한다. 그러나 필드 주입은 클래스 의존관계가 명확하지 않다. 그래서 필수 또는 불변 프로퍼티 DI 라면 생성자 주입, 선택적이고 가변 프로퍼티 DI라면 수정자 메소드 주입을 사용해서 보다 정확하게 의존 관계를 설명해야 한다.

     

    Setter Based Injection(수정자를 통한 주입)

    @Controller
    public class HomeController {
    
    	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    
    	private TestService1 service1;
    
    	@Autowired
    	public void setTestService1(TestService1 service) {
    		this.service1 = service;
    	}
    
    	@RequestMapping(value = "/", method = RequestMethod.GET)
    	public String home(Locale locale, Model model) {
    
    		service1.test1();
    		return "home";
    	}
    }

     

     

    @Service
    public class TestService1 {
    	private TestService2 service2;
    
    	@Autowired
    	public void setTestService2(TestService2 service) {
    		this.service2 = service;
    	}
    
    	public void test1() {
    
    		service2.test2();
    	}
    }

     

     

    @Service
    public class TestService2 {
    
    	private TestService1 service1;
    
    	@Autowired
    	public void setTestService1(TestService1 service) {
    		this.service1 = service;
    	}
    
    	public void test2() {
    		service1.test1();
    	}
    }

     

    StackOverFlowError

    Field Injection의 단점

    순환참조

    • 위와같이 @Autowired를 사용하는 것이다. 위의 단점은 Bean객체들이 서로를 바라보게 된다면 위와 같은 오류(StackOverflowError)가 발생이 된다. 이게 바로 서로를 참조하게 되는 순환참조이다
    • 순환참조는 객체 생성시점에는 오류를 잡을 수 없고 객체생성 후 로직상에서 잡을 수 밖에 없다.

     

     

    Constructor based Injection(생성자 주입)

    @Service
    public class TestService1 {
    	private TestService2 service2;
    
    	public TestService1(TestService2 service) {
    		this.service2 = service;
    	}
    
    	public void test1() {
    		service2.test2();
    	}
    }
    

     

    @Service
    public class TestService2 {
    
    	private TestService1 service1;
    
    	public TestService2(TestService1 service) {
    		this.service1 = service;
    	}
    
    	public void test2() {
    		service1.test1();
    	}
    }

     

     

    RROR: org.springframework.web.servlet.DispatcherServlet - Context initialization failed
    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'homeController': Unsatisfied dependency expressed through field 'service1'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testService1' defined in file [C:\spring\worksapce\test\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\study\WEB-INF\classes\kr\co\study\service\TestService1.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testService2' defined in file [C:\spring\worksapce\test\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\study\WEB-INF\classes\kr\co\study\service\TestService2.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Requested bean is currently in creation: Is there an unresolvable circular reference?
    	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:592)
    	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:370)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1219)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:551)
    	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:754)
    	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
    	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
    	at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:668)
    	at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:634)
    	at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:682)
    	at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:553)
    	at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:494)
    	at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
    	at javax.servlet.GenericServlet.init(GenericServlet.java:158)
    	at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1134)
    	at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1089)
    	at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:983)
    	at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4871)
    	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5180)
    	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384)
    	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374)
    	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    	at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
    	at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909)
    	at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:841)
    	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384)
    	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374)
    	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    	at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
    	at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909)
    	at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262)
    	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    	at org.apache.catalina.core.StandardService.startInternal(StandardService.java:421)
    	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    	at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930)
    	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    	at org.apache.catalina.startup.Catalina.start(Catalina.java:633)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:344)
    	at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:475)

    Constructor based Injection 장점

    순환참조 방지

    • 객체 생성 시점에서 발견하여 오류를 발생시킨다.

     

    @Autowired 대상이 되는 객체가 여러개일 경우

    @Controller
    public class HomeController {
    
    	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    
    	@Autowired
    	private BookRepository bookrepository;
    
    	@RequestMapping(value = "/", method = RequestMethod.GET)
    	public String home(Locale locale, Model model) {
    
    		bookrepository.test();
    		return "home";
    	}
    }
    @Repository
    public class MybookRepository implements BookRepository{
    	public void test() {
    		System.out.println("MybookRepository1 입니다.");
    	}
    }
    
    @Repository
    public class MybookRepository2 implements BookRepository{
    	public void test() {
    		System.out.println("MybookRepository2 입니다.");
    	}
    }
    
    org.springframework.beans.factory.UnsatisfiedDependencyException: 
    Error creating bean with name 'homeController': Unsatisfied dependency expressed through field 'bookrepository'; 
    nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
    No qualifying bean of type 'kr.co.study.service.BookRepository' available: 
    expected single matching bean but found 2: mybookRepository,mybookRepository2

    코드를 실행시키게 되면 위의 오류가 발생하게 된다. 그 이유는 위의 에러코드에 발생이되는데 bookRepository가 MybookRepository, MybookRepositor2 중에 어떤 Bean을 사용해야되는지 몰라서 발생이되는 에러이다. 

     

    해결법

    @Primary

     

    @Autowired에 대상이 되는 클래스중 먼저 주입 시키고 싶은 클래스에 위의 어노테이션을 붙이면 Srping Contatiner에서 자동으로 해당 객체를 주입시킨다.

     

    @Qualifier

    @Repository(value ="mybookRepository")
    public class MybookRepository implements BookRepository{
    	public void test() {
    		System.out.println("MybookRepository1 입니다.");
    	}
    }
    @Controller
    public class HomeController {
    
    	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    
    	@Autowired
    	@Qualifier("mybookRepository")
    	private BookRepository bookrepository;
    
    	@RequestMapping(value = "/", method = RequestMethod.GET)
    	public String home(Locale locale, Model model) {
    
    		bookrepository.test();
    		return "home";
    	}
    }

    @Autowired 어노테이션으로 주입하는 곳에서 @Qualifier("bean id")를 붙이게 되면 어떤 Bean을 넣을지 명시하게하여 해결 할 수 있다.

     

     

    3. 모든 Bean을 주입받기

    @Controller
    public class HomeController {
    
    	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    
    	@Autowired
    	private List<BookRepository> bookrepository;
    
    	@RequestMapping(value = "/", method = RequestMethod.GET)
    	public String home(Locale locale, Model model) {
    		for(BookRepository book : bookrepository) {
    			book.test();
    		}
    		return "home";
    	}
    }

    위와같이 모든 Bean을 List에 담아 사용가능하다.

     

     

     

     

     

     

     

     

    출처

    https://goddaehee.tistory.com/46 

    백기선의 스프링 프레임워크 핵심기술 강의

    반응형
Designed by Tistory.