[Spring] 2-Layered Architecture (Presentation Layer, Business Layer)
지금까지의 동작 과정
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가 미리 되어있어야함