• Home
  • About
    • Young's Github Pages photo

      一日不作一日不食

    • About
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Java EE 정리 40

23 Apr 2019

Reading time ~8 minutes

Java EE 정리 40 - Spring DI(2)


Spring DI

  • 약결합
  • Spring Container : ApplicationContext
    • 객체 생성
      • Bean - Spring Framework에서 생성하는 객체
    • 의존성 주입
    • 객체 생명주기를 관리
  • 설정파일 : XML
    • 대규모 프로그램에서 사용
    • 의존성 주입관계를 파악하기 쉽다
<!-- applicationContext.xml -->
<beans>
    <!-- 의존성 주입할 객체, 주로 DAO -->
    <bean id="이름1" class="패키지명.클래스명"/>
    ...

    <!-- 의존성 주입 받을 객체, 주로 DAO를 사용하는 Service -->
    <bean id="이름2" class="패키지명.클래스명">
        <constructor-arg ref="이름1"/>
    </bean>   
</beans>
// Container 생성
ApplicationContext ac = new ClasspathXmlApplicationContext("패키지명/설정파일");

// Bean 찾기(id 사용), 세 가지 경우로 찾게 된다
ac.getBean("id");                 // Spring ~2.5 , 사용 비권장
ac.getBean(클래스명.class);        // Spring 3.0~ , 권장, 동명 클래스 존재시 에러
ac.getBean("id",클래스명.class);   // 동명의 클래스를 식별하기 위해 사용
  • DI가 사용되는 위치

01

  • DI 예제를 위해서 Spring Bean Configuration File 생성

02

DI - 생성자 의존성 주입

  • 생성자 의존성 주입
<constructor-arg ref="id"/>
<!-- 또는 -->
<constructor-arg>
    <ref bean="id"/>
</constructor-arg>
  • 생성자 의존성 주입 예
  • bean노드의 scope 속성은 객체 생성 방법
    • singleton(default)
      • 객체가 하나만 생성됨
    • prototype
      • 객체가 여러개 생성됨
    • request, session
      • 웹 애플리케이션인 경우에만 사용가능
<!-- applicationContext.xml -->
...
      <!-- 의존성 주입할 객체 설정 -->
      <bean id="td" class="date0423.TestDAO" scope="singleton"/>
      
      <!-- 생성자 의존성 주입 -->
      <bean id="ts" class="date0423.TestService" scope="singleton">
            <constructor-arg ref="td"/>

            <!-- 이렇게 쓸 수도 있음
            <constructor-arg>
                  <ref bean="td"/>
            </constructor-arg> -->
      </bean>
</beans>
  • 생성자 의존성 주입을 위해선 생성자가 매개변수를 받아야 한다
public class TestService {
      
      // 의존성 주입받은 객체를 사용할 수 있도록
      // instance 변수로 선언
      private TestDAO td;
      
      // interface를 의존성 주입받으면 약결합이 됨
      // (이 예제에선 클래스를 받아서 강결합)
      public TestService(TestDAO td) {
            this.td = td;
            System.out.println("생성자 의존성 주입");
      }
}
public class Run {
      public static void main(String[] args) {
            // Spring Container 생성
            ApplicationContext ac
                  = new  ClassPathXmlApplicationContext("date0423/applicationContext.xml");
            
            // Bean 찾기
            TestService ts = ac.getBean(TestService.class);

            System.out.println(ts); // 출력
      }
}

03

DI - method 의존성 주입

  • method 의존성 주입
    • 메서드 파라미터로 외부값을 주입받는 것
    • 생성자가 없거나 생성자가 Overload된 경우, 기본생성자가 반드시 나와야 한다
<bean id="아이디" class="패키지명.클래스명">
    <property name="setter명" ref="객체명"/>
    <!-- 기본형 데이터형을 집어넣을 땐 ref말고 value를 사용 -->
    <property name="setter명" ref="기본형데이터형입력"/>
</bean>
  • method 의존성 주입 예
// TestService
...
      public TestService() {
            System.out.println("기본 생성자를 사용하여 객체 생성");
      }   
   
      // method 의존성 주입, 반드시 기본 생성자가 존재해야 한다.
      public void setTd(TestDAO td) {
            this.td = td;
      }

      public TestDAO getTd() {
            return td;
      }
}
  • 기본 생성자로 객체가 생성된 이후 setter method가 호출되어 의존성 주입하는 방식
<!-- applicationContext.xml -->
...
      <bean id="td" class="date0423.TestDAO" scope="singleton"/>
...
      <!-- method 의존성 주입 -->
      <bean id="ts1" class="date0423.TestService">
            <property name="td" ref="td"></property>
            <!-- name td는 setter명, ref는 아까 만든 TestDAO 객체 -->
      </bean>
...
// Run
...
            System.out.println("---------------- method 의존성 주입  -------------------");
            TestService ts1 = ac.getBean("ts1",TestService.class);
            System.out.println(ts1.getTd());
      }
}

04

DI - 생성자 매개변수가 여러개일 때 의존성 주입

  • 호출되는 객체의 생성자 매개변수 만큼 constructor-arg노드를 순서에 맞게 기술
public class TestService2 {
      private TestDAO td;
      private TestDAO2 td2;
      
      public TestService2(TestDAO td, TestDAO2 td2) {
            this.td = td;
            this.td2 = td2;
            System.out.println("매개변수가 여러개인 생성자 의존성 주입");
      }
}
<!-- applicationContext.xml -->
...
      <!-- 의존성 주입할 객체 설정 -->
      <bean id="td" class="date0423.TestDAO" scope="singleton"/>
      <bean id="td2" class="date0423.TestDAO2" scope="singleton"/>
...
      <!-- 생성자의 매개변수가 여러개일 때 의존성 주입 -->
      <bean id="ts2" class="date0423.TestService2" scope="singleton">
            <constructor-arg ref="td" />
            <constructor-arg ref="td2" />
      </bean>
...
// Run
...
            System.out.println("---------------- 생성자의 매개변수가  여러개일 때 의존성 주입 -------------------");
            TestService2 ts2 = ac.getBean(TestService2.class);
            System.out.println(ts2);
      }
}

05

DI - 기본형 데이터형, 문자열 데이터형 의존성 주입

  • 기본형 데이터형과 문자열 데이터형 의존성 주입
class Test {
    public Test(int i) { ... }
    public Test(String s) { ... }
}
  • value의 type은 생략가능
    • 위와 같은 클래스 객체에 DI할 경우 ‘23’을 값으로 넣고 type이 생략되어 있다면 String을 매개변수로 받는 생성자가 호출된다
      • 노드 사이의 값은 문자열로 취급된다
    • type에 들어가는 Wrapper, String class는 lang package에 존재하더라도 패키지명을 모두 기술해야 한다
<bean id="아이디" class="패키지명.클래스명">
    <constructor-arg>
        <value type="데이터형">값</value> 
    </constructor-arg>
</bean>
  • 기본형 데이터형, 문자열 데이터형 의존성 주입 예
public class PrimitiveTypeInjection {
      public PrimitiveTypeInjection(int i) {
            System.out.println("기본형 데이터형을 매개변수로 Injection  i="+i);
      }
      
      public PrimitiveTypeInjection(String s) {
            System.out.println("문자열 데이터형을 매개변수로 Injection  s="+s);
      }
}
  • value노드에 type속성을 기술하지 않으면 문자열의 매개변수가 정의된 생성자를 호출하여 Injection 수행
<!-- applicationContext.xml -->
...
      <!-- 기본형 데이터형 의존성 주입 -->
      <bean id="pti" class="date0423.PrimitiveTypeInjection">
            <constructor-arg>
                  <value>23</value>
            </constructor-arg>
      </bean>
...
// Run
...
            System.out.println("---- 생성자에 기본형 데이터형, 문자열  데이터형 의존성 주입 ---");
            PrimitiveTypeInjection pti =  ac.getBean(PrimitiveTypeInjection.class);
            System.out.println(pti);
      }
}

06

  • type을 int로 명시했을 경우
<!-- applicationContext.xml -->
...
      <!-- 기본형 데이터형 의존성 주입 -->
      <bean id="pti" class="date0423.PrimitiveTypeInjection">
            <constructor-arg>
                  <value type="int">23</value>
            </constructor-arg>
      </bean>
...

07

  • String을 value type으로 패키지명없이 명시하면 에러가 발생
<value type="String">23</value>

08

  • java.lang package에 존재하는 class일지라도 무조건 패키지명을 기술해야 한다
<value type="java.lang.String">23</value>

09

DI - JCF 의존성 주입

  • JCF - Java Collection Framework
    • list는 중복을 허용, 검색의 기능
    • set은 중복을 허용하지 않고, 입력되는 값이 순차적으로 들어가지 않음
    • map은 KVP
class Test {
    public Test(List<String>, Set<String>) { ... }
}
<bean id="이름" class="패키지명.클래스명">
    <constructor-arg>
        <list>
            <value type="">값</value>
            <value type="">값</value>
            ...
            <value type="">값</value>
        </list>
    </constructor-arg>
    <constructor-arg>
        <set>
            <value type="">값</value>
            <value type="">값</value>
            ...
            <value type="">값</value>
        </set>
    </constructor-arg>
</bean>
  • List, Set 의존성 주입 예
public class JCFInjection {
      private List<String> list;
      private Set<String> set;
      
      public JCFInjection(List<String> list) { // List 주입
            this.list = list;
            System.out.println("List Injection");
      }
      
      public JCFInjection(Set<String> set) { // Set 주입
            this.set = set;
            System.out.println("Set Injection");
      }
      
      public List<String> getList() {
            return list;
      }
      
      public Set<String> getSet() {
            return set;
      }
}
  • List 의존성 주입
<!-- applicationContext.xml -->
...
      <!-- List 의존성 주입 -->
      <bean id="jiList" class="date0423.JCFInjection" scope="singleton">
            <constructor-arg>
                  <list>
                        <value type="java.lang.String">의</value>
                        <value type="java.lang.String">존</value>
                        <value type="java.lang.String">성</value>
                        <value type="java.lang.String">주</value>
                        <value type="java.lang.String">입</value>
                  </list>
            </constructor-arg>
      </bean>
...
// Run
...
            System.out.println("---- List 의존성 주입 ---");
            JCFInjection ji = ac.getBean("jiList",JCFInjection.class);
            System.out.println(ji.getList());
      }
}

10

  • Set 의존성 주입
<!-- applicationContext.xml -->
...
      <!-- Set의 의존성 주입 -->
      <bean id="jiSet" class="date0423.JCFInjection" scope="singleton">
            <constructor-arg>
                  <set>
                        <value type="java.lang.String">중</value>
                        <value type="java.lang.String">중</value>
                        <value type="java.lang.String">복</value>
                        <value type="java.lang.String">복</value>
                        <value type="java.lang.String">데이터</value>
                        <value  type="java.lang.String">들어가랏얍</value>
                  </set>
            </constructor-arg>
      </bean>
...
// Run
...
            System.out.println("---- Set 의존성 주입 ---");
            JCFInjection ji = ac.getBean("jiSet",JCFInjection.class);
            System.out.println(ji.getSet());
      }
}

11

  • VO를 Generic으로 사용한 List 주입
public class TestVO {
      private String name;
      private int age;
      // 인자있는 생성자, getter, setter 생성
...
  • VO를 객체(Bean)로 생성
<!-- applicationContext.xml -->
...
      <!-- VO를 Generic으로 사용한 List -->
      <bean id="tv1" class="date0423.TestVO">
            <constructor-arg>
                  <value type="java.lang.String">이재찬</value>
            </constructor-arg>
            <constructor-arg>
                  <value type="int">27</value>
            </constructor-arg>
      </bean>
...
  • VO를 객체(Bean)로 생성
<!-- applicationContext.xml -->
...
      <!-- VO를 Generic으로 사용한 List -->
      <bean id="tv1" class="date0423.TestVO">
            <constructor-arg>
                  <value type="java.lang.String">이재찬</value>
            </constructor-arg>
            <constructor-arg>
                  <value type="int">27</value>
            </constructor-arg>
      </bean>
...
  • constructor-arg노드는 아래처럼 줄일 수 있다
<bean id="tv2" class="date0423.TestVO">
      <constructor-arg type="java.lang.String" value="오영근"/>
      <constructor-arg type="int" value="30"/>
</bean>
<bean id="tv3" class="date0423.TestVO">
      <constructor-arg type="java.lang.String" value="정택성"/>
      <constructor-arg type="int" value="27"/>
</bean>

12

  • 매개변수를 하나 더 줘서 에러를 막는다
// JCFInjection
...
      private List<TestVO> voList;
      public JCFInjection(List<TestVO> voList, int i) {
            this.voList = voList;
            System.out.println("voList Injection");
      }
...
      public List<TestVO> getVoList() {
            return voList;
      }
}
  • JCFInjection 생성자에 VO를 갖는 리스트를 주입
<!-- applicationContext.xml -->
...
      <bean id="jiVoList" class="date0423.JCFInjection"  scope="singleton">
            <constructor-arg>
                  <list>
                        <ref bean="tv1"/>
                        <ref bean="tv2"/>
                        <ref bean="tv3"/>
                  </list>
            </constructor-arg>
            <constructor-arg type="int" value="0"/><!-- 오버로딩을 위한 임의의 값 -->
      </bean>
...
// Run
...
            System.out.println("---- List VO 의존성 주입 ---");
            JCFInjection ji = ac.getBean("jiVoList",JCFInjection.class);
            System.out.println(ji.getVoList());
      }
}

13

  • Map 의존성 주입
    • 키와 값의 쌍, KVP(Key Value Pair) == entry
class Test {
    public Test(Map<String, String> map) { ... }
}
<bean id="이름" class="패키지명.클래스명">
    <constructor-arg>
        <map>
            <entry>
                <key><value type="키의데이터형">키값</value></key>
                <value type="값의데이터형">값</value>
            </entry>
            <entry>
                <key><value type="키의데이터형">키값</value></key>
                <value type="값의데이터형">값</value>
            </entry>
            ...
            <entry>
                <key><value type="키의데이터형">키값</value></key>
                <value type="값의데이터형">값</value>
            </entry>
        </map>
    </constructor-arg>
</bean>
  • Map 의존성 주입 예
// JCFInjection
...
      private Map<String, String> map;
      
      public JCFInjection(Map<String,String> map) {
            this.map = map;
            System.out.println("Map Injection");
      }
...
      public Map<String,String> getMap() {
            return map;
      }
...
<!-- applicationContext.xml -->
...
      <!-- Map 의존성 주입 -->
      <bean id="jiMap" class="date0423.JCFInjection" scope="singleton">
            <constructor-arg>
                  <map>
                        <entry>
                              <key><value  type="java.lang.String">노진경</value></key>
                              <value>안녕하세요? 쓰앵님</value>
                        </entry>
                        <entry>
                              <key><value  type="java.lang.String">김정윤</value></key>
                              <value>디잘잘, 도타, 철권</value>
                        </entry>
                        <entry>
                              <key><value  type="java.lang.String">김정운</value></key>
                              <value>네트워크, 토익, 홍익인간</value>
                        </entry>
                  </map>
            </constructor-arg>
      </bean>
...
// Run
...
            System.out.println("---- Map 의존성 주입 ---");
            JCFInjection ji = ac.getBean("jiMap",JCFInjection.class);
            System.out.println(ji.getMap());
      }
}

14

DI - annotation을 사용한 의존성 주입

  • annotation을 사용하면 설정용 xml의 역할이 줄어든다
  • @Component
    • Bean으로 생성되어야 할 클래스 위에 선언
  • @Autowired
    • 의존성 주입받을 객체 위에 선언
  • 설정용 XML 생성 시 beans과 context가 선택된 상태에서 사용

15

  • 사용법
    • 설정파일에는 가급적 bean노드로 의존성 주입을 하지 않는다
<context:annotation-config/>
<context:component-span base-package="패키지명.*"/>
<context:component-span base-package="패키지명"/>
<!-- 위에 두 줄을 써줘야 Component 어노테이션을 가진 클래스를 탐색가능 -->
@Component
class Test { // 의존성을 주입하는 클래스
    
}
@Component
class TestService() { // 의존성을 주입 받는 클래스

    @Autowired
    private 의존성주입받을객체;

    // 생성자를 만들지 않는다

}
  • required가 true
    • 의존성 주입받을 객체가 반드시 생성된 후 의존성 주입을 받게 되는 경우
    • 객체 주소 할당
  • required가 false
    • 의존성 주입받을 객체가 생성되지 않은 상태에서 의존성 주입 받을 경우
    • null 할당
      • method로 의존성 주입을 받게되는 경우
@Autowired(required=true|false)
  • annotation을 사용한 의존성 주입 예
    • @Autowired를 사용하기 위해선 annotation-config, component-scan이 설정되어 있어야 된다
<!-- applicationContext2.xml -->
...
      <context:annotation-config/>
      <context:component-scan base-package="date0423.*"/>
      <context:component-scan base-package="date0423"/>
</beans>
@Component
public class TestDAO3 { // 의존성을 주입하는 클래스
      
}
@Component
public class TestService3 { // 의존성을 주입받는 클래스
      
      // 의존성을 주입받을 객체 위에 annotation 선언
      @Autowired
      private TestDAO3 td3; 
      // @Component로 선언된 클래스 객체를 @Autowired로 받음

      public TestDAO3 getTestDAO3() {
            return td3;
      }
}
public class RunAnnotation {
      public static void main(String[] args) {

            // Spring Container 생성
            ApplicationContext ac = new  ClassPathXmlApplicationContext("date0423/applicationContext2.xml");
            TestService3 ts3 = ac.getBean(TestService3.class);
            
            System.out.println(ts3);            
            System.out.println(ts3.getTestDAO3()); // autowired로 의존성 주입
      }
}

16

  • Annotation을 이용한 DI를 사용하면 Controller에서 Service를, Service에서 DAO를 직접 객체화할 필요가 없다

17

  • 이전에 만들었던 spring_mv 예에서 DI를 사용해보자
    • annotation을 사용하기 위해 servlet-context.xml에 context 설정을 해줌
<!-- servlet-context.xml -->
...
      <!-- @Component, @Autowired, @Controller를 Spring Framework가 찾을  수 있도록 설정 -->
      <context:annotation-config/>
      <context:component-scan base-package="kr.co.sist" />
      <context:component-scan base-package="kr.co.sist.dao.*" />
      <context:component-scan base-package="kr.co.sist.dao" />
      <context:component-scan base-package="kr.co.sist.service.*" />
      <context:component-scan base-package="kr.co.sist.service" />
...
@Component
public class MyBatisDAO {
      ...
  • DI를 사용하기 위해 @Component, @Autowired를 사용
    • @Autowired를 사용하면 instance화를 개발자가 직접하지 않는다
@Component
public class MainService {
      @Autowired
      private MyBatisDAO mb_dao;
      
      public List<NoticeDomain> noticeList() {
            List<NoticeDomain> list = mb_dao.selectMainNotice();
            
            return list;
      }
}
@Controller
public class MainController {
      
      @Autowired
      private MainService ms;

      @RequestMapping(value="/index.do",method=GET)
      public String indexPage(Model model) {
            model.addAttribute("notice", ms.noticeList());
            return "index";
      }
      ...


<!-- index.jsp -->
...
<ul>
      <li>공지사항</li>
      <c:forEach var="notice" items="${ notice }">
            <li><c:out value="${ notice.subject }" escapeXml="false"/></li>
      </c:forEach>
      <c:if test="${ empty notice }">
            <li>작성된 공지사항이 없습니다</li>
      </c:if>
</ul>
...

18



Java EESpring