Spring

[Spring] 2-Layered Architecture (Presentation Layer, Business Layer)

코딩 수달 2022. 9. 27. 00:04
반응형

지금까지의 동작 과정 

 

1. 클라이언트가 *.do 요청

2. 서블릿 컨테이너(DispatcherServlet)가 구동됨

->  현재의 계층(layer)을  프레젠테이션 레이어(Presentation Layer)

3. 스프링 컨테이너가 구동되면서 Controller 객체들 생성 

4. Controller 의 Command 객체로 DAO객체를 사용함 

 

이때! 사용자가 DAO가 아닌 DAO를 사용하고 싶으나 @Autowired가 안되어있어서 사용이 불가! 

Controller 의 모든 메서드는 DAO 객체를 직접 이용하고있다! 
☆ Spring에서는 DAO객체를 직접 이용하지않고, 
반드시 "비즈니스 컴포넌트(ServiceImpl)"를 이용해서 DAO 객체를 다룰 수 있게끔 구성해야한다! 

 

왜 "비즈니스 컴포넌트(ServiceImpl)" 사용해야할까? 

1) DAO 클래스 교체 등의 유지보수 유리 
비즈니스 컨포넌트 입장에서는 자신을 이용하는 클라이언트가 Controller 임 
클라이언트인 Controller 는 Service를 멤버변수로 사용하면 Service가 변경되어도 Controller 자체는 변화가 없음 
== 낮은 결합도
2) AOP 적용 용이 
횡단관심(어드바이스)이 동작하려면 Service클래스의 비즈니스메서드가 실행되어야함 

 

결론) C 클래스는 비즈니스 컨포넌트를 멤버변수로 사용해야하며, 그 객체에게 의존성주입(DI) 해야함 


1. Controller에서 DAO 코드 비즈니스 컴포넌트(ServiceImpl) 변경 

 

ServiceImpl를 멤버변수로 선언하고, 각 비즈니스 메서드 마다 인자에 들어있던 DAO는 삭제 후 DAO을 선언한 멤버변수로 

변경한다. 

package com.kim.biz.controller;


import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;

import com.kim.biz.board.BoardService;
import com.kim.biz.board.BoardVO;
import com.kim.biz.member.MemberVO;
import com.kim.biz.member.impl.MemberDAO;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;

@Controller
@SessionAttributes("data")//"data"라는 이름의 데이터가 Model 객체에 세팅이 된다면, 그것을 sessio에 기억시키게싿. 
//개발자가 디테일한 시점을 지정할 수 없기때문에 디테일하게 사용시점마다 꺼내쓸 수 있는 session.setattibute사용 
// 다른곳에서 data로 선언한게 있다면 매번 바뀌게 된다 
public class BoardController {
	
	@Autowired
	private BoardService boardService; //비즈니스 컴포넌트. DAO를 직접 이용 XXX 
	
	@ModelAttribute("scMap")
	public Map<String,String> searchConditionMap(){
		Map<String,String> scMap =new HashMap<String,String>();
		scMap.put("제목", "TITLE");
		scMap.put("작성자", "WRITER");
		return scMap;
	}

	@RequestMapping("/main.do")//command 맴버변수로 serch에 관려된게 없어서 검색조건, 검색어 자동 맵핑이 안됨 
	//-> 해결하기 위해서 자바에만 사용하기 위해 vo에 멤버변수로 추가한다. 
	//-> 다른 해결방법 : requestparam  
	public String main(@RequestParam(value="searchCondition",defaultValue="",required=false)String searchCondition,@RequestParam(value="searchContent",defaultValue="",required=false)String searchContent, BoardVO bVO,MemberVO mVO,MemberDAO mDAO, Model model) {
		if(searchCondition.equals("TITLE")) {
			bVO.setTitle(searchContent);
		}else if(searchCondition.equals("WRITER")) {
			bVO.setWriter(searchContent);
		}
		
		mVO = mDAO.selectOneMember(mVO);
		System.out.println("검색조건: "+searchCondition);
		System.out.println("검색어: "+searchContent);
		List<BoardVO> datas=boardService.selectAllBoard(bVO); 
		model.addAttribute("datas", datas);
		return "main.jsp";
	}
	
	@RequestMapping("/board.do")
	public String board(BoardVO bVO, Model model) {
		bVO=boardService.selectOneBoard(bVO);
		model.addAttribute("data", bVO);
		return "board.jsp";
	}
		
	@RequestMapping("/updateBoard.do")
	public String updateBoard(@ModelAttribute("data")BoardVO bVO) {	
		boardService.updateBoard(bVO);
		return "redirect:main.do";
	}
	
	@RequestMapping("/insertBoard.do")
	public String insertBoard(BoardVO bVO) {
		boardService.insertBoard(bVO);
		return "redirect:main.do";
	}
	
	@RequestMapping("/deleteBoard.do")
	public String deleteBoard(BoardVO bVO) {
		boardService.deleteBoard(bVO);
		return "main.do";
	}
}

2.  2-Layered 발생 가능 문제

 

위와 같이 비즈니스 컴포넌트를 반영 후 서버를 실행하게되면 , 하기와 같이 NoSuchBeanDefinitionException 이 발생하는데 

이 오류 내용은 어노테이션으로 의존성 주입을 하려고할 때,

객체에 관련 타입의 메모리가 없어서 의존성 주입을 할 수 없다는 내용이다. 

즉, Controller에 의존성 주입이 될 예정이기 때문에 Controller보다 ServiceImpl가 먼저 생성되어있어야 하고

ServiceImpl객체를 생성해줄 스프링 컨테이너가 1개 더 필요! 

근데 Controller를 생성해주는 스프링 컨테이너 보다는 먼저 실행이 되어야 한다. 

기존의 Presentation Layer(MVC Layer) + Business Layer  =>  [ 2-Layered 아키텍처 ] 


3. Spring 설정 파일이 불리는 순서를 변경

 

위와 같은 NoSuchBeanDefinitionException  오류를 방지하기 위해서는 Business Layer 를 먼저 실행하게 해야한다. 

Business Layer 먼저 부르기 위해서는 ContextLoaderListener 라는 스프링 프레임워크에서 제공하는 리스너 클래스를 사용 !

이는 클라이언트의 요청 없이도 컨테이너가 실행될 때, Pre-Loading 객체를 로딩 시키는 것! 

 

기존에 존재하는 web.xml 에 아래의 코드를 작성해서 클래스를 등록 한다. 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://Java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
  <filter>
     <filter-name>characterEncodingFilter</filter-name>
     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
       <param-name>encoding</param-name>
       <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
     <filter-name>characterEncodingFilter</filter-name>
     <url-pattern>*.do</url-pattern>
  </filter-mapping>
  
  <!-- ContextLoaderListener 리스너 등록 -->
   <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

4. ContextLoaderListener클래스가 필요하다는 xml 파일 참조 

 

위 3번까지의 내용만 작성하고 실행시키게 된다면 하기와 같이 오류가 발생된다. 

오류가 발생되는 이유는 ContextLoaderListener를 Listener 등록해두면 /WEB-INF/applicationContext.xml 탐색하는데 

이때 applicationContext.xml 파일을 찾지 못하면 발생되는 오류이다. 

 

해결방법 

1. /WEB-INF/applicationContext.xml 생성 및 설정

2. web.xml 내에  <context-param></context-param> 으로 ~Context.xml 을 지정

 

만약에 1번을 선택하게 된다면 applicationContext.xml  복사해서 WEB-INF 하위에 넣으면 되지만 동일한 파일이 2개가 되면서 

유지보수에 어려움을 발생하기 때문에 2번을 선택하여 깔끔하게 해결! 

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://Java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
  <filter>
     <filter-name>characterEncodingFilter</filter-name>
     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
       <param-name>encoding</param-name>
       <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
     <filter-name>characterEncodingFilter</filter-name>
     <url-pattern>*.do</url-pattern>
  </filter-mapping>
  <!-- /WEB-INF/applicationContext.xml 설정파일이 필요 -> 다른 파일에 이미 있으니까 참조하도록 하는 코드 -->
    <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <!-- ContextLoaderListener 리스너 등록 -->
   <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  
</web-app>

[정리]
1. 서버(톰캣) 시작
2. web.xml 파일 로딩, 서블릿컨테이너 구동
3. 서컨은 web.xml에 등록된 ContextLoaderListener 객체를 가장먼저 생성
-> pre-loading
4. CLL 객체는 src/main/resources의 applicationContext.xml을 로딩하여 스컨을 구동
-> 먼저 실행되는 스컨 == Root 컨테이너
5. ServiceImpl, DAO 객체들을 메모리에 생성
6. xxx.do 요청
7. 서블릿컨테이너 구동됨
DispatcherServlet을 생성
DS-servlet.xml을 로드해서 생성
현재의 계층(layer)을 "프레젠테이션 레이어"라고한다!
8. 스프링컨테이너가 구동됨
Controller 객체들 생성
C 객체들이 Command 객체로 DAO 객체를 사용함
 -> DAO2를 사용하고싶었으나 잘안됨. @Autowired가 미리 되어있어야함

 

 

반응형