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가 사용되는 위치
- DI 예제를 위해서 Spring Bean Configuration File 생성
DI - 생성자 의존성 주입
- 생성자 의존성 주입
<constructor-arg ref="id"/>
<!-- 또는 -->
<constructor-arg>
<ref bean="id"/>
</constructor-arg>
- 생성자 의존성 주입 예
-
bean노드의 scope 속성은 객체 생성 방법
-
singleton(default)
- 객체가 하나만 생성됨
-
prototype
- 객체가 여러개 생성됨
-
request, session
- 웹 애플리케이션인 경우에만 사용가능
-
singleton(default)
<!-- 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); // 출력
}
}
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());
}
}
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);
}
}
DI - 기본형 데이터형, 문자열 데이터형 의존성 주입
- 기본형 데이터형과 문자열 데이터형 의존성 주입
class Test {
public Test(int i) { ... }
public Test(String s) { ... }
}
-
value의 type은 생략가능
- 위와 같은 클래스 객체에 DI할 경우 ‘23’을 값으로 넣고 type이 생략되어 있다면 String을 매개변수로 받는 생성자가 호출된다
- 노드 사이의 값은 문자열로 취급된다
- type에 들어가는 Wrapper, String class는 lang package에 존재하더라도 패키지명을 모두 기술해야 한다
- 위와 같은 클래스 객체에 DI할 경우 ‘23’을 값으로 넣고 type이 생략되어 있다면 String을 매개변수로 받는 생성자가 호출된다
<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);
}
}
- type을 int로 명시했을 경우
<!-- applicationContext.xml -->
...
<!-- 기본형 데이터형 의존성 주입 -->
<bean id="pti" class="date0423.PrimitiveTypeInjection">
<constructor-arg>
<value type="int">23</value>
</constructor-arg>
</bean>
...
- String을 value type으로 패키지명없이 명시하면 에러가 발생
<value type="String">23</value>
- java.lang package에 존재하는 class일지라도 무조건 패키지명을 기술해야 한다
<value type="java.lang.String">23</value>
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());
}
}
- 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());
}
}
- 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>
- 매개변수를 하나 더 줘서 에러를 막는다
// 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());
}
}
-
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());
}
}
DI - annotation을 사용한 의존성 주입
- annotation을 사용하면 설정용 xml의 역할이 줄어든다
-
@Component
- Bean으로 생성되어야 할 클래스 위에 선언
-
@Autowired
- 의존성 주입받을 객체 위에 선언
- 설정용 XML 생성 시 beans과 context가 선택된 상태에서 사용
-
사용법
- 설정파일에는 가급적 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로 의존성 주입
}
}
- Annotation을 이용한 DI를 사용하면 Controller에서 Service를, Service에서 DAO를 직접 객체화할 필요가 없다
-
이전에 만들었던 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>
...