Spring 3 DI ( Dependency Injection, 의존성 주입 ) example
개요
스프링 프레임워크에서 Annotation과 DI 를 사용하면 더욱 간결한 코드를 작성할 수 있고 설정파일에 콘트롤러 클래스나 빈 클래스를 등록하지 않고도 MVC 패턴으로 프로그램을 완성할 수 있다
여기서는 아래와 같이 주로 사용되는 몇개의 Annotation으로 DI의 예를 보이고자 한다
빈 관리 : @Component, @Controller, @Service, @Repository,
빈 주입 : @Resource, @Autowired
Auto Detecting Annotation
특정 Bean의 기능 수행을 위해 다른 Bean의 인스턴스를 생성하고 참조해야 하는 경우 사용하는 Annotation으로는 @Autowired 또는 @Resource가 있다.
- @Autowired : Field, Constructor, Setter, 일반 메소드에 두루 적용가능, 빈의 타입(자료형)으로 빈을 찾음, Spring 프레임워크에서만 사용가능
- @Resource : Field, Setter 에만 적용가능, 컨테이너에 등록된 빈의 이름으로 빈을 찾음, Java Annotation
위의 설명에서 동일 타입의 빈이란?
동일한 클래스를 이용하여 설정파일에 여러개의 빈을 설정한 경우 (동일 클래스로부터 다수개의 인스턴스가 생성되어 사용되는 경우)
동일 클래스나 인터페이스를 상속한 경우에 그 부모형으로 빈을 검색하는 경우 (동일 클래스나 인터페이스의 자식 클래스가 다수개인 경우)
Bean Management Annotation
- @Component : 아래의 모든 빈 관리 Annotation의 부모이므로 모든 빈 관리가 가능하지만 빈의 레이어 별로 아래의 Annotation 사용을 권장함
- @Controller : 스프링 MVC 패턴의 콘트롤러 클래스
- @Service : 비즈니스 로직 클래스
- @Repository : 데이터 접근 로직 클래스
@Service를 사용하여 빈으로 등록하는 예
@Service("productService") public class ProductServiceImpl extends GenericServiceImpl<Product, String> implements ProductService { @Resource ApplicationContext context; }
@Service 을 사용하면 위의 클래스를 스프링에 'productService' 라는 이름으로 등록할 수 있고 @Autowired 를 사용하여 자동으로 주입 받을 수 있다
@Service 를 사용하여 빈을 등록할 때 이름을 지정하지 않으면 클래스의 첫자를 소문자로 바꾼 후에 빈의 이름으로 사용된다
@Service 에 이름을 지정한 경우에는 @Resource(name="myservice")나 다음과 같이 자바 코드를 이용하여 빈의 참조를 얻을 때 사용할 수 있다
@Service("myservice")
public class BoardService {
..........
..............
}
@Controller
public BoardController {
@RequestMapping("/board/list")
public String list(Model model) {
BoardService service = (BoardService)context.getBean("myservice");
model.addAttribute("list", service.getList());
return "listView";
}
}
@Autowired 를 사용하면 빈 클래스 이름(변수의 타입)으로 검색하여 빈을 찾아 주입해준다
@Autowired
BoardService boardSrvice;
@Autowired를 사용하면 BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, MessageSource 인터페이스와 하위 인터페이스들을 별도 설정 없이 바로 사용할 수 있게 해준다.
@Autowired(required=false) : 대상 빈이 없어도 오류는 발생하지 않는다
@Autowired 를 사용하면 아래와 같이 다양한 환경에 의존성 주입이 가능하다
멤버변수에 주입하는 경우
생성자의 파라미터 변수에 주입하는 경우
Setter 메소드의 파라미터 변수에 주입하는 경우
일반 메소드의 파라미터 변수에 주입하는 경우
@Resource는 클래스의 이름이 아니라 등록된 빈의 이름으로 검색하여 빈을 주입해준다
여기서 말하는 빈의 이름이란 @Resource(name="myservice") 와 같이 사용하여 이름을 지정하는 경우가 있는데, 이렇게 이름을 지정한 경우에는 해당 이름을 가진 빈이 없는 경우에 오류가 발생한다
그냥 이름 속성을 생략하고 @Resource를 사용하면 @Resource 가 적용된 변수명이나 Setter 메소드의 set를 제외한 나머지 메소드 이름으로 빈을 검색하고
이 방법으로 빈을 찾지 못하면 @Autowired 처럼 Bean의 타입을 사용하여 검색하게 된다.
@Resource // 빈의 등록된 이름이 'dao', 'boardDAO' 인것을 찾고 없으면 빈의 타입이 BoardDAO 인 것을 찾는다
BoardDAO dao;
@Resource(name="boardDAO") // 이름이 'boardDAO' 인것을 찾고 없으면 오류발생
BoardDAO dao;
아래와 같이 선언된 빈의 이름은 클래스 이름의 첫자가 소문자로 변경된 'boardService' 라는 이름으로 등록된다
@Service
public class BoardService { }
위의 빈을 검색하려면 @Resource(name="boardService") 으로 해야한다
@Resource(name="boardService")
BoardService boardService;
혹은 아래처럼 name 속성을 사용하지 않으면 클래스 이름 첫자를 소문자로 변경한 이름이 자동으로 적용된다
@Resource
BoardService boardService;
아래와 같이 선언된 빈은 'bservice' 라는 이름으로 등록된다
@Service("bservice")
public class BoardService { }
위의 빈을 검색하려면 아래처럼 @Resource(name="bservice") 으로 해야한다
@Resource(name="bservice")
BoardService boardService;
@Resource도 BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, MessageSource 인터페이스와 하위 인터페이스들을 별도 설정 없이 바로 사용할 수 있게 해준다.
@Resource 는 멤버변수와 Setter 메소드의 파라미터 변수에만 주입이 가능하다
명시적으로 name 속성에 이름을 지정하지 않는 경우, 검색할 Bean Name은 다음과 같은 규칙을 따른다.
@Resource가 멤버 변수에 정의되었을 때 : 멤버 변수의 이름
@Resource가 setter 메소드에 정의되었을 때 : 해당 setter 메소드의 이름에서 'set'을 제외한 이름(첫 글자는 소문자)
예) setFoo(...) --> 'foo'
해당하는 Bean Name으로 Bean을 찾지 못했을 경우에는 @Autowired 처럼 Bean의 type으로 검색한다.
Annotation을 이용해야 하므로 서블릿 설정파일에 다음과 같은 요소가 포함되어야 한다
servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
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.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="org.kdea.myspring" />
</beans:beans>
BoardController.java
package org.kdea.myspring; import javax.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class BoardController { private static final Logger logger = LoggerFactory.getLogger(BoardController.class); @Autowired private BoardService boardService; @RequestMapping("/board/controller") public String process(Model model) { //logger.info("BoardController.process()"); model.addAttribute("list", boardService.getList()); return "board"; } }
BoardService.java
package org.kdea.myspring; import java.util.List; import javax.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BoardService { @Autowired private BoardDAO dao; public List<BoardVO> getList() { return dao.getList(); } }
BoardDAO.java
package org.kdea.myspring; import java.util.*; import org.springframework.stereotype.*; @Service public class BoardDAO { public List<BoardVO> getList() { List<BoardVO> list = new ArrayList<BoardVO>(); list.add(new BoardVO(10, "Hello")); list.add(new BoardVO(20, "World")); return list; } }
BoardVO.java
package org.kdea.myspring; public class BoardVO { private int num; private String title; public BoardVO(int num, String title) { this.num = num; this.title = title; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
board.jsp
<%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta charset="EUC-KR"> <title>게시판 글 리스트</title> </head> <body> <c:forEach var="board" items="${list}"> ${board.num}. ${board.title}<br> </c:forEach> </body> </html>