spring

day4 class03 프레젠테이션 레이어와 비즈니스 레이어 통합

일상다반ㅅㅏ 2019. 2. 10. 18:11

지금까지 Spring MVC를 기반으로 개발한 게시판 프로그램의 구조와 실행 순서는

브라우저에서 버튼이나 하이퍼 링크를 클릭하여 서버에 요청을 전송하면

모든 요청을 서블릿 컨테이너가 생성한 DispatcherServlet이 받는다.

그리고 DispatcherServlet은 스프링 컨테이너가 생성한 Controller에게 요청을 전달하고,

Controller는 매개변수를 통해 전달된 DAO 객체를 이용하여 사용자가 요청한 로직을 처리한다.


지금 실습에서는 Controller가 DAO 객체를 직접 이용했지만 이 방식은 옳지 않고 비즈니스 컴포넌트를 이용해야 한다.


1 비즈니스 컴포넌트 사용

- Spring IoC를 학습하면서 만들었던 비즈니스 컴포넌트는 VO 클래스, DAO 클래스, Service 인터페이스, Service 구현 클래스 등 네 개의 파일로 구성되어 있다. 

- 하지만 지금까지 게시판 프로그램을 구현하면서 BoardService 인터페이스나 BoardServiceImpl 클래스를 사용한 적은 없다.


1-1. DAO 클래스 교체하기

- Controller 메소드에서 DAO의 메소드를 호출하면 안되는 첫 번째 이유는 유지보수 과정에서 DAO 클래스를 다른 클래스로 쉽게 교체하기 위해서이다. 지금은 BoardController의 모든 메소드가 BoardDAO 객체를 매개변수로 받아서 DB 연동을 처리하기 때문에 BoardDAOSpring으로 변경하거나 다른 클래스로 변경 된다면 BoardController의 모든 메소드를 수정해야 한다.

- boardService 멤버변수를 선언하고, 변수 위에 @Autowired를 설정하여 BoardService 타입의 BoardServiceImpl 객체가 의존성 주입된다.

- 그리고 모든 메소드에서 매개변수로 선언된 BoardDAO를 제거하고, 대신 boardService 변수를 이용해서 비즈니스 컴포넌트를 사용하도록 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
public class BoardController {
 
    @Autowired
    private BoardService boardService;
    
    // 글 등록
    @RequestMapping(value="/insertBoard.do")
    public String insertBoard(BoardVO vo) {
//        System.out.println("글 등록 처리");
        boardService.insertBoard(vo);
        return "getBoardList.do";
    }
cs

- 해당 부분만 변경하면 얼마든지 다른 DAO 클래스로 변경 할 수 있다.

1
2
3
4
5
@Service("boardService")
public class BoardServiceImpl implements BoardService {
 
    @Autowired
    private BoardDAO boardDAO;
cs


1-2. AOP 설정 적용하기

- 횡단 관심에 해당하는 어드바이스가 동작하려면 반드시 Service 구현 클래스(BoardSericeImpl)의 비즈니스 메소드가 실행되어야 한다. 

- 포인트컷을 설정할 때 DAO 클래스의 메소드를 지정한 것이 아니라 Service 구현 클래스의 메소드에 설정했다. 따라서 여기서 구현한 모든 어드바이스는 반드시 비즈니스 클래스의 메소드가 호출될 때 동작한다. 

- 현재의 상태로는 BoardService 타입의 객체가 메모리에 없어서 의존성 주입을 할 수 없다는 에러 메세지가 나온다. (NoSuchBeanDefinitionException)

1) 클라이언트로부터 ".do" 요청이 들어오면 서블릿 컨테이너는 DispatherServlet을 생성한다.

2) DispatcherServlet은 스프링 설정 파일인 presentation-layer.xml을 로딩하여

3) 스프링 컨테이너를 구동한다.

4) 이때 BoardController 객체만 메모리에 생성되고 @Autowired로 의존성 주입할 BoardServiceImpl 객체는 생성되지 않는다.

-> 결론은 BoardController보다 의존성 주입될 BoardServiceImpl 객체가 머넞 생성되어 있어야 한다. 하지만 presentation-layer.xml 파일에는 Controller 객체만 컴포넌트로 스캔하도록 설정했기 때문에 BoardServiceImpl 객체는 생성되지 않는다. 결국, Controller보다 의존성 주입 대상이 되는 비즈니스 컴포넌트를 먼저 생성해줄 또다른 스프링 컨테이너가 필요하다.




2. 비즈니스 컴포넌트 로딩

2-1. 2-Layered 아키텍처

- 일반적으로 프레임워크 기반의 웹 프로젝트를 보면 이 아키텍처 스타일을 사용한다.

- 현재 실습한 게시판 프로그램의 구조로 보면 이에 해당한다.

Presentation Layer(MVC) : presentation-layer.xml

Business Layer : applicationContext.xml

- applicationContext.xml를 presentation-layer.xml보다 먼저 로드하여 비즈니스 컴포넌트들을 메모리에 생성한다.


2-2. ContextLoaderListener 등록

- 스프링에서 제공하는 ContextLoaderListener를 이용하여 비즈니스 컴포넌트를 메모리에 생성한다.

- <listener>태그를 이용하여 ContextLoaderListener 클래스를 등록하고, 서블릿 컨테이너가 web.xml 파일을 읽어서 구동될 때 자동으로 메모리에 생성된다.

- ContextLoaderListener는 클라이언트의 요청이 없어도 컨테이너가 구동될 때 Pre-Loading 되는 객체다.

- ContextLoaderListener가 기본적으로 /WEB-INF/applicationContext.xml 파일을 읽어 스프링 컨테이너를 구동하기 때문에 FileNotFoundException이 발생하는데 src/main/resources 소스 폴더에 있는 applicationContext.xml 파일을 WEB-INF 폴더에 복사하면 정상적인 구동이 된다.

- 하지만 일단은 실습을 위해 src/main/resources에 applicationContext.xml 파일을 위치시키고 <context-param>태그를 이용하여 applicationContext.xml 파일을 로드한다.

1
2
3
4
5
6
7
8
9
10
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
  
<listener>
      <listener-class>
          org.springframework.web.context.ContextLoaderListener
      </listener-class>
</listener>
cs




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


톰캣 서버를 처음 구동하면

1) web.xml 파일을 로딩하여 서블릿 컨테이너가 구동된다.

2) 서블릿 컨테이너는 web.xml 파일에 등록된 ContextLoaderListener 객체를 생성(Pre Loading)한다. 이때 ContextLoaderListener 객체는 src/main/resources 소스 폴더에 있는 applicationContext.xml 파일을 로딩하여 스프링 컨테이너를 구동하는데 이를 'Root 컨테이너'라고 한다. 그리고 이때 Service 구현 클래스나 DAO 객체들이 메모리에 생성된다. 그리고 사용자가 로그인 버튼을 클릭하여 ".do" 요청을 서버에 전달하면 서블릿 컨테이너는 DispatcherServlet 객체를 생성하고 

3) DispatcherServlet 객체는 /WEB-INF/config 폴더에 있는 presentation-layer.xml 파일을 로딩하여 두 번째 스프링 컨테이너를 구동한다. 이 두 번째 스프링 컨테이너가 Controller 객체를 메모리에 생성한다.