ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 학원 day83. 스프링의 의존성 주입(2)
    기록 2022. 12. 30. 12:58

    스프링의 의존성 주입

    수동 의존성 주입

    1. 애플리케이션 실행에 관여하는 객체를 스프링 컨테이너가 생성하게 한다.

    2. 의존성 주입을 받는 객체는 의존하는 객체를 전달받기 위해서 멤버변수와 Setter 메소드를 정의한다.

    3. 스프링 컨테이너가 생성한 객체들을 조립시킨다. 

       

    타입이 다르면 안쪽 방향을 탐색한다.

    PostDao의 구현부가 없는 메소드를 재정의한 PostOracleDao의 insertPost메소드가 실행된다.

     

    인터페이스를 이용한 느슨한 결합이란,

    PostController은 PostOracleDao 클래스(구현클래스)의 존재를 알 필요가 없다. 

    즉, PostController에게 PostOracleDao 객체가 아닌 다른 객체를 전달해줘도 상관없다.(단, PostDao 인터페이스의 구현클래스여야 한다.)

     


    환경설정 바뀌었을 때 ↓

    ** 의존성 주입을 통해서 PostController의 소스코드는 하나도 바꾸지 않고 PostController가 사용할 객체를 달라지게 할 수 있는 것이 핵심이다. **

    (단, 요청처리를 담당하는 contoroller의 소스코드가 바뀌지 않는것이지 데이터베이스 엑세스하는 PostMySQLDao의 소스코드를 새로 만들고 스프링의 빈설정파일인 context-2.xml의 소스코드는 바뀐다.)

    의존성 주입을 사용하지 않았다면.. ↓

    의존성주입을 사용하지 않은 예
    의존성주입 활용

    개발자 컴퓨터의 저장경로와 테스트서버, 운영서버의 경로가 다를 수 있다. 

    테스트 서버는 테스트를 목적으로 하기 때문에 어플리케이션마다 폴더를 다르게 할 수 있다. 따라서 운영 서버와 저장경로다 다를 수 있다. 

    xml파일에서 기본자료형 값, 문자열, 숫자의 경우 ref대신 value로 적는다. (ref는 bean의 id를 적는다. 객체일때 적는다.)

    Controller에 저장경로를 적어두지 않고 xml파일에 적어도 원격저장소에서 받아오면 수정해야 한다. 

    따라서, 따로 file을 만들어줘야 한다. 

    git의 버전관리대상에서 제외하는 app.properties라는 파일을 만들어주고 경로를 적어준다. (파일이름은 무엇이든 상관없다.)

    gitIgnore파일에 app.properties를 적어놓으면 서버에 올라가지 않게된다.

    컴퓨터마다 서로 다른 app.properties를 갖고 있게 한다.

    app.properties파일 작성 내용

    아래는 app.properties파일을 읽어오기 위해서 하는 작업으로, 

    Namespaces에서 context를 체크하면 추가적으로 사용할 수 있는 태그가 늘어난다.

    환경설정파일xml에도 파일경로가 안적혀있고 app.properties에 적혀있다.

    개발환경, 테스트환경, 운영환경이 다를 것이기 때문에 app.properties라는 별도의 파일을 만든것이다.

    <context:property-placeholder location="classpath:spring/app.properties"/>

    -> location속성으로 지정된 app.properties라는 파일을 읽어오는 객체를 스프링 컨테이너에 추가한다.

    <property name="saveDirectory" value="${file.save.directory}"></property>

    스프링 빈 환경설정파일에서 ${}표현식을 사용해서 값을 사용할 수 있다.

    ** 주의 : value="${file.save.directory}" 중괄호 사이에 빈공간이 있으면 안되고 중괄호 뒤에도 띄어쓰기가 있으면 안된다.


    생성자 메소드를 이용해서 의존성 주입받기 

    public class ProductController {
    
    	private ProductDao productDao;
    	private PostDao postDao;
    	
    	// 생성자 메소드를 이용해서 의존성 주입받기
    	public ProductController(ProductDao productDao, PostDao postDao) {
    		this.productDao = productDao;
    		this.postDao = postDao;
    	}
    	
    	public String home() {
    		
    		productDao.deleteAllProducts();
    		postDao.insertPost();
    		
    		return "home.jsp";
    	}
    }

    setter메소드인 경우, public이기 때문에 객체만 획득하면 set을 이용해서 null값을 넣어버릴 수 있기 때문에 NullPointerException이 발생할 수 있다. 

    따라서, setter주입말고도 생성자를 이용해 주입하는 방법도 쓸 수 있다.

    생성자 메소드는 객체 생성시점에 한번 실행되고 그 이후에는 생성자 메소드를 호출할 수 없다. 

    객체 생성을 스프링에서 하니까 우리가 생성자 메소드를 실행할 일이 없다.

    스프링에게 생성자 주입 으로 의존성 주입하라고 알려주고 싶은 것이다.

     

    스프링에서 멤버변수에 적어놓은 건 항상 필요한 객체가 무엇인지 스프링에게 알려주려고 선언하는 것이고, 전달받기 위해 setter메소드나 생성자메소드를 정의한다.

     

    ProductController에는 매개변수가 0개인 생성자가 정의되어 있지 않아서, 즉 default 생성자가 정의되어 있지 않아서 오류가 뜬다. 

    Bean을 등록하면 스프링이 기본생성자 메소드를 호출해서 객체를 생성한다. 그런 다음 setter메소드를 이용해서 의존성주입을 하고 있는 것이다.

    객체 생성하고 무조건 생성자를 호출하긴 해야하는데, 기본생성자가 아닌 매개변수가 있는 생성자를 호출하게 할 것이다. 

    context-2.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"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    	<!-- 
    		<context:property-placeholder />
    			location 속성으로 지정된 properties 파일을 읽어오는 객체가 스프링 컨테이너에 추가된다.
    			스프링 빈 환경설정파일에서 ${} 표현식을 사용해서 값을 사용할 수 있다.
    	 -->
    	<context:property-placeholder location="classpath:spring/app.properties"/>
    
    	<!-- 
    		Setter 메소드를 이용하는 의존성 주입 - <property /> 태그를 사용한다.
    		처리 순서
    			1. 객체 생성 및 기본 생성자 메소드 실행
    				PostController x = new PostController()을 실행해서 객체 생성
    			2. setter 메소드를 실행해서 의존성 주입
    				x.setSaveDirectory("c:/files");
    				x.setMaxUploadSize(10485760);
    				x.setPostDao(postMySQLDao);			   
    	 -->
    	<bean id="postController" class="com.sample.post.controller.PostController">
    		<property name="saveDirectory" value="${file.save.directory}"></property>
    		<property name="maxUploadSize" value="${file.max.uploadsize}"></property>
    		<property name="postDao" ref="postMySQLDao"></property>
    	</bean>
    	
    	<!-- 
    		생성자 메소드를 이용해서 의존성 주입 - <constructor-arg /> 태그를 사용한다.
    			 <constructor-arg />태그는 생성자 메소드의 매개변수와 대응되는 태그다.
    			 생성자 메소드에 매개변수가 2개다. -> <constructor-arg />태그를 2개 정의한다.
    			 생성자 메소드에 매개변수가 3개다. -> <constructor-arg />태그를 3개 정의한다.	
    		처리 순서
    			1. 객체를 생성하고, <constructor-arg />태그의 갯수와 매개변수 갯수가 일치하는 생성자 메소드를 실행한다.
    				ProductController x = new ProductController(productOracleDao, postMySQLDao);
    	 -->
    	<bean id="productController" class="com.sample.post.controller.ProductController">
    		<constructor-arg name="productDao" ref="productOracleDao"></constructor-arg>
    		<constructor-arg name="postDao" ref="postMySQLDao"></constructor-arg>
    	</bean>
    
    	<bean id="postOracleDao" class="com.sample.post.dao.PostOracleDao"></bean>
    	<bean id="postMySQLDao" class="com.sample.post.dao.PostMySQLDao"></bean>
    	<bean id="productOracleDao" class="com.sample.post.dao.ProductOracleDao"></bean>
    </beans>

    따라서, setter메소드를 사용하여 의존성을 주입할 때에는 <property /> 태그를 사용하고, 

    생성자메소드를 사용하여 의존성을 주입할 때에는 <constuctor-arg /> 태그를 사용해야 한다.

    수동으로 할 경우에는 setter메소드를 더 많이 사용하기는 한다.


    <context:annotation-config/>

    스프링이 객체로 만들어놓은 클래스 안에 정의되어 있는 다양한 어노테이션들을 활성화시킨다.

    어노테이션을 감지하고 해당 어노테이션이 수행하기로 약속된 일들을 수행하는 객체를 스프링 컨테이너의 빈(객체)으로 등록시킨다.

    context.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"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    	<context:property-placeholder location="classpath:spring/app.properties"/>
    
    	<context:annotation-config/>
        
    	<context:component-scan base-package="com.sample"></context:component-scan>	
    </beans>

    <context:annotation-config />
    - 스프링 컨테이너가 생성한 객체에 아래와 같은 어노테이션을 감지해서 
      해당 어노테이션에 대한 적절한 작업을 수행하는 객체를 스프링 컨테이너의 빈(객체)으로 등록시킨다.
    - 해당 어노테이션 
        @Autowired - 의존성 자동주입을 지원한다.
        @PostConstruct - 스프링 컨테이너가 객체 생성 후 실행할 초기화 작업이 구현된 메소드에 부착한다.
        @PreDestroy - 스프링 컨테이너가 생성한 객체를 폐기하기 전에 실행할 작업이 구현된 메소드에 부착한다. 
        @Transactional - 선언적 트랜잭션처리를 지원한다.

     

     <context:component-scan />
      - base-package="패키지경로" 지정된 패키지 및 그 하위 패키지에서 아래의 어노테이션이 부착된 클래스를 전부 스캔해서 스프링 컨테이너가 객체를 생성하고, 스프링 컨테이너의 빈으로 등록한다.
      - 해당 어노테이션
             @Component  - 아래의 모든 어노테이션의 부모 어노테이션이다. 
               이 어노테이션을 상속받아서 사용자 정의 어노테이션을 정의하면, 그 어노테이션 붙은 클래스도 스캔 대상이 된다. 
             @Repository  - 데이터베이스 엑세스 작업을 담당하는 객체에 부착한다.
             @Service  - 비즈니스 로직 수행을 담당하는 객체에 부착한다.
             @Controller  - HTTP 요청을 처리하는 컨트롤러 객체에 부착한다.
             @RestController  - Rest API를 담당하는 컨트롤러 객체에 부착한다. 
             @ControllerAdvice  - Controller 객체의 공통기능을 담당하는 객체에 부착한다.
             @RestControllerAdvice  - RestController 객체의 공통기능을 담당하는 객체에 부착한다.
             @Configuration  - Java 기반 빈 설정을 담당하는 객체에 부착한다.

    package com.sample.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    
    import com.sample.dao.PointHistoryDao;
    import com.sample.dao.UserDao;
    
    @Service
    public class UserService {
    
    	@Autowired     // setter메소드를 만들지 않아도 상관없음
    	private UserDao userDao; 
    	@Autowired
    	private PointHistoryDao pointHistoryDao;
    	
    	
    	@Value("${user.default.profile.filename}")
    	private String filename;
    	@Value("${user.discount.rate}")
    	private double discountRate;
    	
    	public void config() {
    		System.out.println(userDao);
    		System.out.println(pointHistoryDao);
    		
    		System.out.println("고객의 기본 할인율: " + discountRate);
    		System.out.println("고객의 기본 프로필이미지 파일명: " + filename);
    	}
    	
    }


    @Autowired
    - 의존성 자동 주입을 지원하는 어노테이션이다.
    - 멤버변수, Setter메소드, 생성자 메소드의 매개변수에 부착할 수 있다.

    - 스프링 컨테이너에서 @Autowired 어노테이션 처리하기
    1. 스프링 컨테이너가 생성한 객체의 클래스에 @Autowired 어노테이션이 있는지 조사한다.
    2. @Autowired 어노테이션이 지정된 멤버변수의 타입, Setter메소드 매개변수 타입, 생성자 메소드의 매개변수 타입을 조사한다.
    @Autowired
    private UserDao userDao;    // UserDao 타입의 객체에 주입해야 한다.
    @Autowired
    public void setUserDao(UserDao userDao) {    // UserDao 타입의 객체 주입해야 한다.
    his.userDao = userDao;
    }
    @Autowired
    public UserService(UserDao userDao, PointHistoryDao pointHistoryDao) {   // UserDao, PointHistoryDao 타입의 객체에 주입해야 한다.
    this.userDao = userDao;
    this.pointHistoryDao = pointHistoryDao;
    }
    3. 스프링 컨테이너가 생성한 객체 중에서 조사된 타입과 일치하는 객체 혹은 조사된 타입의 자식 객체를 찾아서 주입시킨다.
    4. 단, 조사된 타입과 일치하거나 조사된 타입의 자식 객체가 2개 이상 발견되면 오류가 발생한다. (예: ProductDao는 없지만 구현한 ProductOracleDao, ProductMySQLDao가 있을 경우)
    5. 하지만, 조사된 타입과 일치하거나 조사된 타입의 자식 객체가 2개 이상 발견되더라도 
    @Primary 어노테이션이 부착된 객체가 있으면 해당 객체를 주입시킨다.

     @Value
       - 기본자료형 타입, 문자열 타입의 값을 의존성 주입으로 전달받아서 변수에 대입시킨다.
       - 사용법
       1. properties 파일 생성하기
           src/main/resources/spring/app.properties 파일 생성, 파일 이름은 어떤 이름이던지 상관없음
          
           app.properties
           # app.properties 파일의 설정값
           user.discount.rate=0.002
           user.default.profile.filename=default.png


      2. 빈설정 파일에 <context:property-placeholder />태그 추가하기
           <context:property-placeholder location="classpath:spring/app.properties"/>

      3. properties 파일의 설정값을 객체의 멤버변수에 주입시키기 (스프링의 빈으로 등록된 것만 가능하다.)
          @Value("${user.discount.rate}")
           private double discountRate;

     

    자바기반 빈 설정

    자바기반 빈 설정

    - 스프링부트에서 사용할 예정임

    - .xml 안쓰고도 할 수 있게 하려고 사용한다.

    객체 7개를 찾았음.

    어노테이션 분석해서 적정한 작업을 수행해주는 클래스들이다.

    'internalConfigurationAnnotationProcessor'

    'internalAutowiredAnnotationProcessor' 

    UserDao와 PointHistoryDao에 자동주입이 되었음을 확인할 수 있다.

     

    의존관계

    브라우저이든 ATM기계이든 요청방식이나 응답방식은 다르지만 업무로직은 같기 때문에 서비스계층을 따로 만들어서 재사용할 수 있게 한다.

     

     

     

    댓글

Designed by Tistory.