2-Layerd 아키텍처 스타일
2-Layerd 아키텍처 스타일은 소프트웨어 시스템을 두 개의 주요 계층 또는 레이어로 나누는 설계 패턴입니다. 이러한 레이어는 일반적으로 클라이언트 레이어(또는 프론트엔드 레이어)와 서버 레이어(또는 백엔드 레이어)로 구분됩니다. 각 레이어는 특정 역할과 책임을 갖고 있으며 상호작용하는 방식이 정해져 있습니다.
이전 방식
- 브라우저(Client, 사용자)에서 서버로 요청
- request(내장객체 X, 키워드 O)
- DispatcherServlet 서블릿 생성
- DI필요 (Spring에서 기본 제공하는 DispatcherServlet 사용)
- DispatcherServlet-servlet.xml을 참고하여 DI
- 스프링 컨테이너 구동
- 프레젠테이션 레이어
- 요청에 맞는 Controller 객체 호출하여 사용
- Controller, DAO, VO, Model, ...
- Command 객체
현재 유지보수 불리한 상태
1) 서버에서 DBMS 변경이 자주 발생하며, 이에 따라 DAO 변경도 빈번히 발생합니다. 직접 DAO를 사용하고 있어 결합도가 높아 유지보수가 어렵습니다. 또한, 다른 DAO로 변경하기가 어렵습니다.
2) AOP 적용이 제한되어 있어서 xxxService와 관련된 부분에만 적용할 수 있습니다. 이로 인해 반복적인 로그, 보안, 권한 확인과 같은 작업을 매번 처리해야 하므로 응집도가 낮아지고 유지보수가 어려워집니다.
비즈니스 레이어 추가
DAO를 직접 사용하지 않고 Service가 대신 사용하게 됩니다.
package com.spring.view.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.spring.biz.board.BoardService;
import com.spring.biz.board.BoardVO;
import com.spring.biz.member.MemberVO;
@Controller
public class BoardController {
@Autowired
private BoardService boardService;
@ModelAttribute("searchMap")
public Map<String,String> searchMap(){
Map<String,String> map=new HashMap<String,String>();
map.put("제목", "TITLE");
map.put("작성자", "WRITER");
return map;
}
@RequestMapping(value="/main.do")
public String main(@ModelAttribute("mem")MemberVO mVO, BoardVO bVO, Model model) {
System.out.println("searchCondition: "+bVO.getSearchCondition());
System.out.println("searchContent: "+bVO.getSearchContent());
mVO.setMid("test");
mVO.setMpw("1234");
System.out.println("MainController 로그");
// model.addAttribute("mem", mVO);
model.addAttribute("datas", boardService.selectAll(bVO));
return "main.jsp";
}
@RequestMapping(value="/board.do")
public String selectBoard(BoardVO bVO, Model model) {
System.out.println("BoardController 로그");
model.addAttribute("data", boardService.selectOne(bVO));
boardService.update(bVO);
return "board.jsp";
}
@RequestMapping(value="/updateBoard.do")
public String updateBoard(BoardVO bVO) {
System.out.println("BoardController 로그");
boardService.update(bVO);
return "board.do";
}
@RequestMapping(value="/deleteBoard.do")
public String deleteBoard(BoardVO bVO) {
System.out.println("BoardController 로그");
if(boardService.delete(bVO)){
return "redirect:main.do";
}
else{
return "board.do";
}
}
@RequestMapping(value="/insertBoard.do", method=RequestMethod.GET)
public String insertBoardPage() {
System.out.println("InsertBoardPageController 로그");
return "redirect:insertBoard.jsp";
}
@RequestMapping(value="/insertBoard.do", method=RequestMethod.POST)
public String insertBoard(BoardVO bVO) {
System.out.println("InsertBoardController 로그");
if(boardService.insert(bVO)){
return "redirect:main.do";
}
else{
return "redirect:insertBoard.jsp";
}
}
}
BoardController.java
Controller의 메서드 매개변수로 존재하던 DAO를 제거하고 DAO를 대신 사용해줄 Service를 멤버변수로 추가합니다. BoardDAO를 사용하지 않음으로써 DAO가 변경되더라도 Service 내부의 멤버변수만 변경하면 되기 때문에 결합도가 낮아집니다.
package com.spring.biz.board;
import java.util.List;
public interface BoardService {
public BoardVO selectOne(BoardVO bVO);
public List<BoardVO> selectAll(BoardVO bVO);
public boolean insert(BoardVO bVO);
public boolean update(BoardVO bVO);
public boolean delete(BoardVO bVO);
}
BoardService.java
package com.spring.biz.board;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("boardService")
public class BoardServiceImpl implements BoardService {
// Service 레이어가 관념적으로 존재하는데, 그것을 구현한 클래스
// Service 레이어에서는 DAO를 사용함
// == C 파트
// : DAO를 사용할것이기때문에
// DAO와 메서드 시그니처를 맞추면 유리함
// 메서드 시그니처를 강제하고싶다!
// => 인터페이스
@Autowired
private BoardDAO boardDAO;
// 의존관계 -> DI
@Override
public BoardVO selectOne(BoardVO bVO) {
return boardDAO.selectOne(bVO);
}
@Override
public List<BoardVO> selectAll(BoardVO bVO) {
return boardDAO.selectAll(bVO);
}
@Override
public boolean insert(BoardVO bVO) {
return boardDAO.insert(bVO);
}
@Override
public boolean update(BoardVO bVO) {
return boardDAO.update(bVO);
}
@Override
public boolean delete(BoardVO bVO) {
return boardDAO.delete(bVO);
}
}
BoardServiceImpl.java
멤버변수로 추가한 Service가 메서드 수행 주체로 사용되기 때문에 의존관계가 발생합니다. 그러므로 @Autowired를 사용하여 DI(의존주입)을 해야합니다.
루트 컨테이너 설정
의존주입이 정상적으로 되지 않았을 때 BeanCreateException이 발생합니다. BEC(BeanCreateException)는 객체가 생성되기 전에 즉, 메모리에 해당 자료형의 객체가 없는데 의존 주입을 시도하였기 때문에 발생합니다.
BeanCreationException
Spring 프레임워크에서 발생할 수 있는 예외 중 하나로, 스프링 컨테이너가 빈(Bean)을 생성하려고 할 때 문제가 발생했을 때 발생합니다. 이 예외는 다양한 이유로 인해 빈을 생성하는 동안 발생할 수 있으며, 주로 구성 오류, 의존성 문제, 빈의 생성 메서드에서 발생한 예외 등으로 인해 발생합니다.
1) 의존성 주입 문제 : 빈을 생성하려고 할 때 해당 빈이 필요로 하는 다른 빈의 의존성 주입에 실패한 경우. 예를 들어, 필요한 빈을 찾을 수 없거나, 의존성 주입 시에 모호성이 발생하는 경우.
2) 빈의 생성 메서드 예외 : 빈의 생성 메서드(예: 생성자, 팩토리 메서드)에서 예외가 발생하는 경우. 이 예외는 빈을 초기화하는 동안의 문제를 나타냅니다.
3) 빈 구성 오류 : 스프링 설정 파일(XML 또는 JavaConfig)에서 빈을 구성하는 중에 오류가 발생한 경우. 잘못된 빈 이름, 빈의 클래스 경로가 올바르지 않는 경우 등이 포함됩니다.
boardService 빈이 메모리에 없는 문제는 @Service 어노테이션이 동작하지 않았던 것으로 추측됩니다. 이 문제를 해결하고 특정 시점에 객체를 생성하려면 스프링의 리스너와 루트 컨테이너를 사용할 수 있습니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="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-4.2.xsd">
<context:component-scan base-package="com.spring.biz" />
</beans>
applicationContext.xml
applicationContext.xml에서 DAO와 Service 빈들을 정의합니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="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-4.2.xsd">
<context:component-scan base-package="com.spring.view.controller" />
</beans>
DispatcherServlet-servlet.xml
DispatcherServlet-servlet.xml에서 Controller 빈들을 정의합니다.
<?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>characterEncoding</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>characterEncoding</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<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>
</web-app>
web.xml
NOT POJO인 리스너는 web.xml에서 설정합니다.
서버 시작 구동 순서
- 루트 컨테이너 (applicationContext.xml) : 서버가 시작될 때 루트 컨테이너인 applicationContext.xml이 구동됩니다. 여기에서 비즈니스 레이어와 데이터 액세스 레이어의 빈들을 메모리에 생성합니다. 이러한 빈들은 애플리케이션 전체에서 공유됩니다.
- 서블릿 컨테이너 (web.xml) : 웹 애플리케이션의 구성을 정의하는 web.xml이 로딩됩니다. 이 때, 서블릿 컨테이너가 DispatcherServlet (DS)을 메모리에 생성합니다. DS는 클라이언트의 요청을 받고 처리할 준비를 합니다.
- 스프링 컨테이너 (DS-servlet.xml) : DS-servlet.xml 파일에 정의된 설정으로 스프링 컨테이너가 생성됩니다. 이 컨테이너는 주로 프레젠테이션 레이어와 관련된 빈들을 관리합니다. Controller와 같은 웹 컴포넌트들이 여기에서 초기화됩니다.
- Controller 호출 및 요청 처리 : 클라이언트로부터의 요청이 들어오면 DS(DispatcherServlet)는 스프링 컨테이너 (DS-servlet.xml)에 정의된 Controller를 호출하여 해당 요청을 처리합니다. Controller는 서비스 계층 (비즈니스 레이어)의 빈들을 활용하여 요청을 처리하고 결과를 생성합니다.
이러한 과정을 통해 서버가 시작될 때 루트 컨테이너와 웹 애플리케이션 컨테이너가 초기화되고, 클라이언트의 요청에 따라 Controller가 호출되어 비즈니스 로직이 처리됩니다.
GitHub
https://github.com/Qkrwnsgus0522/Spring