본문 바로가기
Programming/Spring

Spring Layered Architecture

by TinKerBellBass 2017. 8. 9.
728x90
반응형
참고 도서
스프링 퀵 스타트
국내도서
저자 : 채규태
출판 : 루비페이퍼 2016.06.30
상세보기



1. Controller 와 DAO의 의존 관계 분리

(1) 컨트롤러가 직접 DAO 객체를 이용해서 클라이언트의 요청을 처리하는 경우
클라이언트 요청이 들어오면 서블릿 컨테이너는 DispatcherServlet 객체를 생성하고,
DispatcherServlet 객체의 init( ) 메소드는 presentation-layer.xml 을 로딩하여
XmlWebApplicationContext 스프링 컨테이너를 구동시킨다.
presentation-layer.xml 은 HnadlerMapping, Controller, ViewResolver 객체를 XML에 설정하거나
@Controller 로 설정된 클래스를 찾아서 객체를 생성시킨다.
그리고 이 객체들은 DAO를 사용해 클라이언트의 요청을 처리하고 응답에 필요한 모델과 뷰를 반환한다.

(2) DAO 클래스 교체
클라이언트의 요청을 처리할 DAO가 변경될 경우 컨트롤러를 수정해야만 한다.
유지보수의 관점에서 컨트롤러의 수정은 없어야 한다.
컨트롤러의 수정을 없애기 위해 클라이언트의 요청을 처리할 비지니스 컴포넌트를 만들고,
컨트롤러가 인터페이스를 통해 비지니스 컴포넌트를 사용하게 하면 된다.  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
public interface BoardService {
 
    // CRUD 기능의 메소드 구현
 
    // 글 등록
    void insertBoard(BoardVO vo);
 
    // 글 수정
    void updateBoard(BoardVO vo);
 
    // 글 삭제
    void deleteBoard(BoardVO vo);
 
    // 글 상세 조회
    BoardVO getBoard(BoardVO vo);
 
    // 글 목록 조회
    List<BoardVO> getBoardList(BoardVO vo);
 
}
 
 
@Service("boardService")
public class BoardServiceImpl implements BoardService {
 
    // DAO 객체를 주입 받아 컨트롤러의 요청 처리
    @Autowired
    private BoardDAO boardDAO;
 
    @Override
    public void insertBoard(BoardVO vo) {        
        boardDAO.insertBoard(vo); 
    }
 
    @Override
    public void updateBoard(BoardVO vo) {
        boardDAO.updateBoard(vo);
    }
 
    @Override
    public void deleteBoard(BoardVO vo) {
        boardDAO.deleteBoard(vo);
    }
 
    @Override
    public BoardVO getBoard(BoardVO vo) {
        return boardDAO.getBoard(vo);
    }
 
    @Override
    public List<BoardVO> getBoardList(BoardVO vo) {
        return boardDAO.getBoardList(vo);
    }
 
}
 
 
@Controller
@SessionAttributes("board")
public class BoardController {
 
    // boardService 객체를 주입받고, 이를 이용해 클라이언트 요청 처리
    @Autowired
    private BoardService boardService;
 
    // 글 등록
    @RequestMapping("/insertBoard.do")
    public String insertBoard(BoardVO vo) {
        boardService.insertBoard(vo);
        return "getBoardList.do";
    }
 
    // 글 수정
    @RequestMapping("/updateBoard.do")
    public String updateBoard(@ModelAttribute("board") BoardVO vo) {
        boardService.updateBoard(vo);
        return "getBoardList.do";
    }
 
    // 글 삭제
    @RequestMapping("/deleteBoard.do")
    public String deleteBoard(BoardVO vo) {
        boardService.deleteBoard(vo);
        return "getBoardList.do";
    }
 
    // 글 상세 조회
    @RequestMapping("/getBoard.do")
    public String getBoard(BoardVO vo, Model model) {
        model.addAttribute("board", boardService.getBoard(vo)); // Model 정보 저장
        return "getBoard.jsp"// View 이름 리턴
    }
 
    // 검색 조건 목록 설정
    @ModelAttribute("conditionMap")
    public Map<StringString> searchConditionMap() {
        Map<StringString> conditionMap = new HashMap<StringString>();
        conditionMap.put("제목""TITLE");
        conditionMap.put("내용""CONTENT");
        return conditionMap;
    }
 
    // 글 목록 검색
    @RequestMapping("/getBoardList.do")
    public String getBoardList(BoardVO vo, Model model) {
        model.addAttribute("boardList", boardService.getBoardList(vo)); // Model 정보
                                                                    // 저장
        return "getBoardList.jsp"// View 이름 리턴
    }
 
}
cs


이제 컨트롤러는 비지니스 컴포넌트로 부터 필요한 서비스 객체를 주입받아 
클라이언트의 요청을 서비스 객체를 이용해 처리할 수 있다.
만약 boardDAO 를 이용해 DB 처리를 하는 BoardService 클래스와 
springDAO 를 이용해 DB 처리를 하는 SpringBoardService 클래스가 있다고 하면,
컨트롤러는 이 두 클래스가 구현하고 있는 인터페이스 객체를 주입받고,
비지니스 컴포넌트는 컨트롤러가 필요로 하는 서비스 클래스를 선택하고 
@Service 를 설정해서 주입하거나, 
또는 서비스 클래스에 주입되는 DAO 객체를 교체해 주면 된다.
객체지향의 다형성을 이용할 수 있게 된 것이다.
그리고 이런 경우도 생각해 볼 수 있다.
서비스 클래스에 서로 다른 DAO 객체를 @Autowired 로 주입받고,
메소드 별로 적절한 DAO 객체를 선택해서 사용할 수도 있다.

2. 비지니스 컴포넌트 객체를 생성하는 스프링 컨테이너

컨트롤러와 DAO의 의존관계를 분리하고, 비지니스 컴포넌트에서 DAO를 처리하게 한 후의

서블릿 컨테이너와 스프링 컨테이너의 상황은 위 그림과 같다.


클라이언트 요청이 들어왔을 때의 상황을 다시 정리해 보면,

① 클라이언트 요청을 들어오면 서블릿 컨테이너가 DispatcherServlet 객체를 생성

② DispatcherServlet은 스프링 설정 파일인 presentation-layer.xml 을 로딩하여 스프링 컨테이너를 구동


컨트롤러는 클라이언트의 요청을 처리하기 위해 비지니스 컴포넌트의 서비스 객체를 주입 받아야 한다.

그러나 컨트롤과 DAO를 분리하기 전에는 컨트롤 객체만 있으면 클라이언트 요청을 처리할 수 있었기 때문에,

presentation-layer.xml 에는 컨트롤 객체들만 컴포넌트 스캔하도록 설정되어 있고, 서비스 객체는 생성되지 않는다. 


컨트롤 객체가 생성되기 전에 비지니스 컴포넌트의 서비스 객체를 생성시키기 위해 

스프링 컨테이너를 구동하는 XML 스프링 설정 파일(applicationContext.xml)을 하나 더 만들고, 

presentation-layer.xml 보다 먼저 구동하게 만들어서 컨트롤러 객체에 주입할 수 있게 만들어야 한다.


3. Spring Layered  Architecture

클라이언트의 요청을 받아 생성되는 DispatcherServlet 객체, 

요청을 매칭하고 비지니스 컴포넌트에 처리를 요청하는 컨트롤러 객체,

비지니스 컴포넌트에서 처리된 모델과 뷰 정보를 가지고 클라이언트에게 응답하는 View 객체를

묶어서 프레젠테이션 레이어(Presentation Layer)라고 하며, MVC Architecture 가 여기에 포함된다.

DispatcherServlet이 XML 스프링 설정파일(presentation-layer.xml)을 로딩하여  

XmlWebApplicationContext 스프링 컨테이너(컨트롤러를 위한 스프링 컨테이너)를 구동하여 컨트롤러 객체를 생성한다..


그리고, 컨트롤러의 요청을 받아 DAO를 이용해 실제로 비지니스 로직을 수행하는 비지니스 컴포넌트를

비지니스 레이어(Business Layer) 또는 서비스 레이어(Service Layer)라고 한다.

그리고 비지니스 컴포넌트의 객체를 생성하기 위해서는

컨트롤러 객체를 생성하는 XmlWebApplicationContext 스프링 컨테이너 구동 전에,

또 다른 XML 스프링 설정파일(applicationContext.xml)이 로딩되어

XmlWebApplicationContext 스프링 컨테이너(비지니스 레이어를 위한 스프링 컨테이너) 구동되어야 한다.


마지막으로 비지니스 레이어에서 분리하여 ,

데이터베이스에 값을 저장하거나 가져오기 위해

JDBC Template, Mybatis, JPA 등을 사용해서 구현한 DAO를 

데이터 액세스 레이어(Data Access Layer) 또는 리파지토리 레이어(Repository Layer)라고 한다.


그리고 데이터베이스, 비지니스 객체, 뷰 객체에서 가져온 값을 저장하거나 데이터베이스, 비지니스 객체, 뷰 객체에 

보낼 값을 저장하는 객체를 도메인 객체(Domain Object) 또는 도메인 모델(Domain Model)이라 한다.


Layered Architecture 전체 구조



① Client 에서  요청이 들어오면 먼저 Presentation Layer 에서 

     DispatcherServlet 이 HandelerMapping 을 통해서 Controller 에게 Client 요청이 무엇인지 알리고,

     Controller 는 Client 요청을 처리를 Business Layer 에게 요구한다.

     이 때, Business Layer 에 넘겨줄 데이터(Client 가 요청한 데이터)가 있으면 Domain Object 에 담는다.

 

② Business Layer 는 Presentation Layer 와 Interface 를 통해서 통신하며,

     Client 요청을 적절히 처리한 후 데이터베이스에 데이터를 저장하거나 데이터를 꺼내기 위해 Data Access Layer 에 요청한다.

     이 때, 비지니스 로직을 수행하기 위해 데이터가 필요하면 Domain Object 에서 가져오고, 

     Data Access Layer 넘겨줄 데이터가 있으면  Domain Object 에 담는다.


③ Data Access Layer 역시 Business Layer 와 Interface 를 통해서 통신하며,

     Business Layer 의 요청을 처리한다.

     이 때, 데이터베이스에 저장하기 위해 필요한 데이터를 Domain Object 에서 가져오고,

     데이터베이스에서 데이터를 가져와 반환할 데이터가 있다면 Domain Object 에 저장한다.


④ 모든 처리가 끝나면 Controller 는 Client 요청이 처리된 데이터와 사용할 View 정보를 

     Domain Object 에서 가져와서 ModelAndView에 담는다. 

     그리고 ModelAndView 객체를 DispatcherServlet 에 넘긴다.


⑤ ModelAndView 객체가 DispatcherServlet 에 전달되면, 

     DispatcherServlet 은 ViewResolver 를 통해서 View 를 선택하고 

     Client 에게 요청이 처리된 데이터를 화면에 출력한다.


Presentation Layer 는 웹의 경우는 MVC 디자인패턴을 사용하지만, 

웹이 아닌 어플리케이션의 경우에는 다른 디자인패턴으로 작성할 수 있다.

MVC Architecture 의 M(Model) 은 데이터를 담는 객체, V(View) 는 화면 출력에 사용할 JSP 나 Thymeleaf,

C(Controller) 는 DispatcherServlet 를 통해 들어온 Client 의 요청을 처리할 비지니스 로직을 선택하거나

Client 에게 응답하기 위한 데이터와 화면에 출력한 View 를 선택해서 DispatcherServlet 에 알리는 객체를 뜻한다.


그리고, Presentation Layer 는 외부에 노출 되지만, Business Layer, Data Access Layer 는 외부에 노출 되지 않는다.


5. 비지니스 컴포넌트 객체 생성을 위한 스프링 컨테이너 설정

WAS는 web.xml 파일을 읽어서 서블릿 컨테이너를 구동하기 때문에,  

web.xml 파일에 비지니스 컴포넌트 객체를 생성하는 XML 스프링 설정파일을 로딩되게 하면,

컨트롤을 위한 스프링 컨테이너 보다 비지니스 컴포넌트를 위한 스프링 컨테이너가 먼저 구동된다.

이를 위해 스프링에서는 ContextLoaderListener 클래스를 제공하고 있다.


 web.xml 에 listener-class 로 ContextLoaderListener 를 설정하면,

ContextLoaderListenr 는 서블릿 컨테이너가 web.xml 파일을 읽어서 DispatcherServlet 객체를 생성할 때,

자동으로 지정된 XML 스프링 설정파일을 로딩하여 XmlWebApplicationContext  스프링 컨테이너를 구동시킨다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- web.xml -->
<listener-class>
    <!-- XML 스프링 설정파일을 로딩하여 스프링 컨테이너 구동 -->
    <!-- 기본적으로 WEB-INF 폴더 안의 서블릿명-servlet.xml 파일을 로딩 -->
    org.springframework.web.context.ContextLoaderListener
</listener-class>
 
<!-- ContextLoaderListener 가 로딩할 XML 파일 지정 -->
<!-- ContextLoaderListener 객체는 context-param 으로 등록된 -->
<!-- contextConfigLocation 파라미터 정보를 읽어서 스프링 컨테이너를 구동한다 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
cs


참고: classapth가 의미하는 것

톰캣 같은 WAS(Web Application Server)는 새롭게 폴더와 파일들의 위치를 설정하여 웹 어플리케이션을 배포한다.

배포될 때의 폴더와 파일들이 설정되어 있는 것이 classpath 이다.

이클립스에서 프로젝트의 properties 를 열어서, Web Deployment Assembly (웹 배치 규정)를 보면 

classpath가 설정되어 있다.



WAS는 Source 에 설정된 폴더의 파일을 Deploy Path 에 설정된 폴더로 복사해서 웹 애플리케이션을 배포한다.

applicationContext.xml 파일을 /src/main/resources 에 만들면, 배포될 때 WEB/INF/classes 에 복사된다.

classpath 는 Deploy Path 이므로,

classpath:applicationContext.xml 은 WEB-INF/classes/applicationContext.xml 을 의미한다.

그리고 applicationContext.xml 파일은 /src/main/resources 폴더에서 만들어 쓸 수 있다.


이렇게 설정하고 나서 WAS 를 구동하고 콘솔창의 INFO 부분을 확인해 보자

ContextLoader 에 의해 classpath 의 resource 인 applicationContext.xml 이 로딩되고,

XmlWebApplicationContext 스프링 컨테이너가 구동되었다. 

그리고 applicationContext.xml 에 설정된 컴포넌트 스캔으로 autowiring 이 실행된다.

즉, applicationContext.xml 에 비지니스 컴포넌트를 스캔하도록 설정하면,

WAS 가 실행되어 서블릿 컨테이너가 구동될 때 비지니스 컴포넌트의 객체들이 생성된다.


그리고, web.xml 에 설정된 DispatcherServelt 객체가 생성되도록 클라이언트에서 요청을 보내고 

콘솔창의 INFO 부분을 확인해 보자.

DispatcherServlet 객체가 생성되고, 

presentation.xml 이 로딩되어 XmlWebApplicationContext 스프링 컨테이너가 구동되었다.


정리하면, 

WAS 가 구동될 때 비지니스 컴포넌트 객체를 생성하는 스프링 컨테이너가 구동되고,

DispatcherServlet 객체가 생성될 때 컨트롤 객체를 생성하는 스프링 컨테이너가 구동된다.

컨트롤 객체가 생성되기 전에 비지니스 컴포넌트 객체가 생성되기 때문에,

컨트롤 객체는 비지니스 컴포넌트의 객체를 주입받아 쓸 수 있다.


5. 스프링 컨테이너의 관계

WAS 구동 -> web.xml 을 로딩하여 서블릿 컨테이너 구동 -> web.xml 에 등록된 ContextLoaderListener 객체 생성

-> ContextLoaderListener 객체가 applicationContext.xml 을 로딩, XmlWebApplicationContext 스프링 컨테이너(ROOT) 구동

-> 비지니스 컴포넌트의 service, DAO 객체 생성 -> 서블릿 컨테이너가 DispatcherServlet 객체 생성

-> DispatcherServlet 객체가 presentation-layer.xml 을 로딩, XmlWebApplicationContext 스프링 컨테이너 구동

-> Controller 객체 생성 


비지니스 컴포넌트를 위한 스프링 컨테이너와 컨트롤러를 위한 스프링 컨테이너는 다른 컨테이너이다.

스프링 MVC 에서 스프링 컨테이너는 두 개가 생성된다.

6. 스프링 설정 파일

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!-- presentation-layer.xml -->
 
<mvc:annotation-driven></mvc:annotation-driven> 
 
<context:component-scan base-package="com.springbook.view"></context:component-scan>
 
 
 
<!-- applicationContext.xml -->
    
<!-- DataSource 설정 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.h2.Driver" />
    <property name="url" value="jdbc:h2:tcp://localhost/~/test" />
    <property name="username" value="sa" />
    <property name="password" value="" />
</bean>
 
<!-- Spring JDBC 설정 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- Transaction 설정 -->
<!-- Spring JDBC -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="dataSource" /> 
</bean>
 
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true" />
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>
 
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.springbook.biz..*(..))" />
    <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" />
</aop:config>
 
    
<context:component-scan base-package="com.springbook.biz"></context:component-scan>
 
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
cs


7. AOP 설정

트랜잭션 같은 횡단 관심은 어디에 설정해야 할까?

presentation.xml 에 설정하여 컨트롤의 메소드에서 실행시켜도 되지만,

보통은 실제로 비지니스 로직이 이루어지는 applicationContext.xml 에 설정하여 비지니스 컴포넌트 메소드에서 횡단 관심을 실행시킨다. 


8. Class Diagram



9. 폴더와 파일 위치

           


실제 배포 WAS 에 배포될 때는 Web Deployment Assembly 에 따라서 src 폴더의 파일들이 모두 WEB-INF 폴더에 복사된다.


WAR 파일로 Export 했을 때의 폴더 구조




728x90
반응형

'Programming > Spring' 카테고리의 다른 글

JPA (Java Persistence API) 기본 개념  (2) 2017.08.13
Mybatis  (0) 2017.08.13
Spring MVC (annotation 기반)  (0) 2017.08.08
Spring MVC (XML 설정 기반)  (0) 2017.08.08
Spring JDBC (JdbcTemplate class)  (0) 2017.08.05

댓글