⚠️ 빌드 과정을 자세하게 다시 포스팅했다. (링크)
지금까지 스프링 부트의 Starter를 사용해서 편리한 빌드만 해 봤는데 이번에 듣게 된 강의에서 스프링 부트 없는 스프링 프로젝트인 Spring legacy proejct로 빌드를 해보게 되었다.
강의에서는 전자정부 표준프레임워크를 통해 쉽게 레거시 프로젝트의 기본적인 구조를 만들었는데, 나는 M2 맥북이라 전자정부 프레임워크를 설치할 수 없었다. 대신 이클립스에 STS4를 설치해 보았지만 어쩐지 레거시 프로젝트 생성 버튼은 뜨지 않았고 ... 어차피 나는 인텔리제이를 쓰고 싶었기 때문에 인텔리제이를 통한 레거시 프로젝트 빌드를 시도해 보았다.
.
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── kr
│ │ │ └── board
│ │ ├── resources
│ │ └── webapp
│ │ ├── WEB-INF
│ │ │ ├── spring
│ │ │ │ ├── appServlet
│ │ │ │ │ └── servlet-context.xml
│ │ │ │ └── root-context.xml
│ │ │ └── views
│ │ ├── resources
│ │ └── web.xml
│ └── test
│ ├── java
│ └── resources
└── target
만들어야 할 기본 구조는 위와 같다.
처음에는 이 구조가 스프링에서 정의한 표준이라고 생각해서 열심히 스프링 문서를 뒤졌는데 아무리 뒤져보아도 내가 원하는 설명이 없었다. 알고보니 이것은 스프링이라기 보다는 메이븐의 구조라고 보는 것이 더 적절했다.
메이븐은 약속된 디렉토리 구조와 pom.xml
라는 핵심 설정 파일을 기반으로 프로젝트를 빌드해주는 빌드 자동화 도구이다.
핵심 디렉토리와 설정 파일들을 살펴보자. 참고로 이 프로젝트는 게시판을 만드는 프로젝트이며 MyBatis를 사용한다.
.
├── SpringMVC01.iml //IntelliJ
├── mvnw //Maven
├── mvnw.cmd //Maven
├── pom.xml --> Maven 프로젝트 설정 파일
├── src
│ ├── main
│ │ ├── java --> Java 소스 코드
│ │ │ └── kr
│ │ │ └── board
│ │ │ ├── controller
│ │ │ │ └── BoardController.java
│ │ │ ├── entity
│ │ │ │ └── Board.java
│ │ │ └── mapper --> 매퍼 인터페이스 & 매핑 XML
│ │ │ ├── BoardMapper.java
│ │ │ └── BoardMapper.xml
│ │ ├── resources --> 설정 파일
│ │ └── webapp --> 웹 애플리케이션 관련
│ │ ├── WEB-INF
│ │ │ ├── spring --> 스프링 설정
│ │ │ │ ├── appServlet
│ │ │ │ │ └── servlet-context.xml
│ │ │ │ └── root-context.xml
│ │ │ └── views
│ │ │ └── template.jsp
│ │ ├── resources --> 웹앱 정적 리소스(js, css)
│ │ └── web.xml --> 웹앱 배포 설명자 파일
│ └── test --> 테스트 코드 및 리소스
│ ├── java
│ └── resources
└── target --> 빌드, 패키징 관련 (빌드 도구에 의해 자동 생성)
pom.xml
:pom.xml
이다. 프로젝트 정보, 의존성, 플러그인 등의 정보를 갖고 있다.<properties>
, <dependencies>
, <plugins>
src/main/java
: 소스 코드(.java
)src/main/java/kr/board/mapper
디렉토리의 경우 매퍼 인터페이스 소스 코드와 매핑 XML 파일이 위치할 수 있다.src/main/resources
/src/main/webapp
: 웹 애플리케이션 관련 디렉토리/src/main/webapp/WEB-INF
: 웹 애플리케이션 관련 메타 데이터와 리소스 포함WEB-INF
내부에 접근할 수 있다./src/main/webapp/WEB-INF/spring
: 스프링 관련 디렉토리 root-context.xml
: 루트 웹 애플리케이션 컨텍스트에 관한 스프링 빈 설정을 포함한다. 데이터소스, 서비스, 리포지토리 등 애플리케이션 전반에 걸친 설정과 관련이 있다../appServlet/servlet-context.xml
: 웹 계층(서블릿 컨텍스트)에 관한 스프링 빈 설정을 포함한다. 대표적으로 뷰 리졸버가 있다./src/main/webapp/WEB-INF/views
: 뷰 디렉토리/src/main/webapp/resources
: 웹 애플리케이션의 정적 리소스 파일.js
, .css
파일을 포함한다./src/main/webapp/web.xml
: 웹 애플리케이션의 배포 설명자 파일DispatcherServlet
설정이 들어간다.src/target
: 빌드, 배포 관련 디렉토리.java
) 컴파일된 자바 바이트코드 파일(.class
)이 위치한다.New Project
에서 Jakarta EE
를 선택한다.
Template
→ Web application
Language
→ Java
Build system
→ Maven
JDK
→ JDK 1.8
참고) Jakarta EE
는 자바의 엔터프라이즈 에디션(EE) 플랫폼이다. 표준인 SE
보다 확장된 범위를 다루며 이전에는 J2EE
, Java EE
라고 불렸으나 2017년 오라클에서 이클립스 재단으로 관할이 넘어가면서 Jakarta EE
로 명칭이 변경되었다.
나는 Java 8
(JDK 1.8
)을 사용했으므로 Version
→ Java EE 8
을 선택했다. Dependencies
에 기본 선택되어 있는 Servlet
설정 그대로 생성해준다.
이렇게 webapp
, WEB-INF
디렉토리를 가진 올드한 구조가 만들어졌다. 톰캣 WAS 연결은 이전에 해둔게 있어서 자동 연결되었다.
만약 연결해 둔 것이 없다면 Run/Debug Configuration에서 +
버튼 → Tomcat Server - local 선택하고 톰캣이 설치된 경로를 찾아 libexec
디렉토리를 선택해주면 된다.
$ brew info tomcat@8
명령어로 설치 경로를 조회했다.한 번 실행해 보니 다음과 같은 오류가 뜬다. IDE에서 포트가 충돌했을 수 있다고 경고가 나온다. 터미널에서 $ sudo lsof -i :8080
로 8080 포트를 사용하고 있는 프로세스를 조회해 보았더니 java
*:http-alt
에서 사용중이라고 한다.
$ kill -9 PID
로 포트를 강제 종료해보아도 계속 다시 생성된다. 🤔 아마도 다른 프로젝트에서 사용한 스프링 부트에 내장된 톰캣인 것 같다.
톰캣 연결 설정에서 포트를 8080
→ 8081
로 변경해보았다.
프로젝트를 생성할 때 만들어져 있던 기본 파일인 index.jsp
가 실행되었다.
그런데 경로가 /
가 아니라 /demo_war_exploded
이다.
톰캣 연결 설정의 Deployment - Application context를 SpringMVC01_war_exploded
→ /
로 바꿔준다.
잘 적용되었다.
아직 스프링 프레임워크는 추가한 적이 없기 때문에 라이브러리를 확인해보면 처음 프로젝트 생성 때 기본으로 삽입된 것만 존재한다.
MVN Repository에 가서 Sring Web MVC를 검색한다. 나는 최신 릴리즈 버전인 5.2.25.RELEASE
로 선택했다.
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.25.RELEASE</version>
</dependency>
복사한 코드를 pom.xml
에 붙여넣기 해 주고 빌드를 해 준다.
빌드가 완료된 뒤 라이브러리를 확인해보면 spring-webmvc
뿐 아니라 core
와 기타 필요한 스프링 프레임워크가 들어와있는 것을 확인할 수 있다.
스프링 프레임워크가 성공적으로 추가되면 Spring Config
파일을 추가할 수 있게 된다.
웹 애플리케이션 디렉토리인 webapp
을 만들어주고 하위 디렉토리와 XML 설정 파일들을 만들어주자.
resources
, spring
, WEB-INF
디렉토리spring/appServlet
디렉토리, spring/root-context.xml
appServlet/servlet-context.xml
WEB-INF/views
디렉토리, WEB-INF/web.xml
template.jsp
는 강의에서 만든 JSP 템플릿 파일이다.<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>encodingFilter</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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
디스패처 서블릿 관련 설정과 root-context.xml
, servlet-context.xml
경로에 관한 설정이 있다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
아직 추가한 설정은 없다.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="kr.board.controller" />
</beans:beans>
뷰 리졸버와 컴포넌트 스캔 설정이 있다.
이제 프로젝트가 MVC 구조에 따라 잘 동작하는지 테스트해보자.
게시글의 정보를 담는 객체인 Board
를 만들고 getter, setter도 만들어준다. 아직 DB 연결은 하지 않았으므로 컨트롤러에서 모델에 전달한 값인 List<Board> list
를 만들어준다. 그리고 list
를 모델에 담아 boardList
뷰에 전달하자.
@Controller //스프링에게 이 클래스가 컨트롤러임을 알려줌
public class BoardController {
@RequestMapping("/boardList.do")
public String boardList(Model model) {
System.out.println("boardList.do 실행");
Board vo = new Board(); //getter, setter 필수
vo.setIdx(1);
vo.setTitle("제목");
vo.setContent("내용");
vo.setWriter("작성자");
vo.setIndate("2021-01-01");
vo.setCount(0);
List<Board> list = new ArrayList<>(); //임의 리스트
list.add(vo);
list.add(vo);
list.add(vo);
model.addAttribute("list", list); //모델에 넣기
return "boardList"; //뷰의 논리적 이름
}
}
모델을 받아서 출력할 뷰 파일(JSP)을 작성해준다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Spring MVC 01</title>
<meta charset="utf-8">
<%-- 디바이스 크기 별로 화면 조정 --%>
<meta name="viewport" content="width=device-width, initial-scale=1">
<%-- 부트스트랩 제공 CSS 사용 --%>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h2>Panel Heading</h2>
<div class="panel panel-default">
<div class="panel-heading">Spring MVC 01</div>
<div class="panel-body">
<table class="table table-bordered table-hover">
<tr>
<td>번호</td>
<td>제목</td>
<td>작성자</td>
<td>작성일</td>
<td>조회수</td>
</tr>
<c:forEach var="vo" items="${list}">
<tr>
<td>${vo.idx}</td>
<td>${vo.title}</td>
<td>${vo.writer}</td>
<td>${vo.indate}</td>
<td>${vo.count}</td>
</tr>
</c:forEach>
</table>
</div>
<div class="panel-footer">SP 1 </div>
</div>
</div>
모델에 list
라는 이름으로 담아 전달한 값을 ${list}
로 받아 반복문으로 필드 값을 출력한다. 디자인은 부트스트랩을 사용했다.
컨트롤러에서 지정한 @RequestMapping
URL을 입력해보면 위와 같이 잘 출력된다. 적용한 스프링 MVC 프레임워크가 정상적으로 동작하는 것을 확인할 수 있다. 👍
시간이 꽤 많이 걸려서 그냥 이클립스로 방법을 찾는게 빨랐겠다 싶어서 후회할 뻔 했지만... 이 삽질 속에서 배운게 많았다. 스트링 부트로 쉽게 빌드할 때는 전혀 신경쓰지 않았던 것들이었는데 직접 하나하나 세팅하려니 '이거는 왜 필요한 거지?' 라는 궁금증이 들 수밖에 없었다. 덕분에 스프링을 더 잘 이해할수 있게 된 것 같다. 😀