[프로젝트1] 1. 스프링 셋팅하기

rin·2020년 4월 26일
5
post-thumbnail

목표
1. 프로젝트 생성
2. 톰캣 연동
3. web.xml 셋팅 후 서버 띄워서 Hello world 확인하기
4. Controller 와 View 를 등록하여 페이지 이동하기

🤔intelliJ에서 제공하는 프레임워크 서포팅 설정 중 springMVC를 추가해서 진행하려고 했으나, 라이브러리 경로 충돌로 인한 .xml과 .java의 인식문제 등 다양한 이슈때문에 다른 방법으로 진행한다. 원인을 파악해서 고치려고 했으나 어제 하루 동안 삽질만 열심히 하여서(...)^^; 맨 마지막에 어떤 문제가 있는지 정리하도록 하겠다.

삽질하는 분들이 적어지길 바라며 프로젝트 생성부터 차례대로 진행하도록 하겠다.

1. 프로젝트 생성

⭐️주의
해당 프로젝트를 처음 만들며 free-board-web01 프로젝트 명을 사용하였다. tomcat을 등록하면서 free-board-web01.war라는 artifact를 등록해도 -(슬러시) 때문인지(추측..) 패스가 먹히지 않는 것을 발견하였다.
freeboard01이라는 이름으로 프로젝트를 다시 생성해서 진행했으며 따라서 마지막 테스트 화면에서의 URL도 localhost:8080/freeboard01로 나타나있다. (이는 톰캣 설정에서 변경가능하다.)


빌드 툴은 그래들을 사용하며, 맨 위의 자바만 선택하고 적절한 이름으로 프로젝트를 생성한다.

preferences에 들어가 github을 검색하면(Version Control-GitHub) 본인 레파지토리에 로그인 할 수 있다. 우측에서 버튼을 누르면 위와 같은 로그인 창이 뜨는데 본인 아이디와 패스워드를 입력하면 연결된다.

상단의 VCS - Import into Version Control - Share Project on GitHub에 들어가면 우측처럼 처음 레파지토리를 생성할 때 입력할 정보를 채워넣을 수 있다. 이 다음 Share를 클릭하면 GitHub에 새로운 레파지토리가 생성된다.
현재 프로젝트에 존재하는 파일을 Add 할것이냐는 경고 메세지가 뜰 텐데 전부 Add해도 무관하다. (어짜피 .gitignore 설정으로 필요없는 부분은 레파지토리에 올라가지 않도록 설정할 것이다.)

Share에 성공하면 .gitignore 파일이 새로 생기는데 본인이 원하는 내용을 적어주면 된다. 구글에 검색하면 해당 파일을 자동으로 만들어 주는 좋은 사이트들이 많다.
내가 추가한 내용은 아래와 같음.

# IDE
.idea/
*.iml
*.ipr
*.iws
out/*
*/out

# gradle
.gradle/
gradle/
build/
*/build/
gradlew*
*/tokens/
tokens/
.DS_Store

# Mac OSX
DS_Store

# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

#restdocs
/*/src/main/resources/docs/v1/*
/*/src/main/resources/docs/v2/*
/*/src/main/webapp/*

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# nodejs
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# source generated
/*/src/main/generated/
/*/src/test/resources/perptest/

다음은 디렉토리를 만드는 것인데, 아마 이 단계에서 많은 분들이 Add Framework Support의 SpringMVC를 추가하려 할 것이다. 여기서 많은 삽질을 겪었으므로 다른 방식으로 생성하도록 하겠다.

maven 디렉토리 구조 사용

intelliJ 를 사용해보았다면 위와 같은 구조를 볼텐데, main 하위에 webapp이라는 폴더를 추가할 것이다.
추가할 폴더와 파일이 많으므로 커맨드 라인에서 작성하였다.

mkdir src/main/webapp
mkdir src/main/webapp/WEB-INF
touch src/main/webapp/WEB-INF/applicationContext.xml
touch src/main/webapp/WEB-INF/dispatcher-servlet.xml
touch src/main/webapp/WEB-INF/web.xml
mkdir src/main/webapp/WEB-INF/view
touch src/main/webapp/WEB-INF/view/index.hbs

프론트는 아주 간단하게 만들것이므로 hbs를 선택하였다. 아래 application-context 설정 파일에 viewResolver를 설정할 것인데 다른 뷰 템플릿을 원한다면 해당하는 설정으로 바꿔주면된다.
📌 아래 이미지에 index.jsp를 index.hbs로 변경하여 진행함

이 프로젝트에서는 xml과 Java annotation 설정을 섞어서 사용할 것이다.
다음의 종속성을 build.gradle에 추가하였다.

    compile 'javax.servlet:servlet-api:2.5'
    compile 'org.springframework:spring-webmvc:4.3.18.RELEASE'
    
    compile group: 'com.github.jknack', name: 'handlebars', version: '4.0.6'
    compile group: 'com.github.jknack', name: 'handlebars-springmvc', version: '4.0.6'

프론트 템플릿으로 핸들바를 사용할 것이므로 위와같이 핸들바 관련 종속성이 필요하다.

이로써 필요한 디렉토리 구조와 라이브러리 추가는 완료하였다.

📌Directory Root 설정

자동으로 Web root를 설정하라는 메세지가 뜰것인데 OK하면된다.

2. 톰캣 연동

톰캣 설치

  • 톰캣은 mac os 기준 homebrew를 이용하여 설치하도록 하겠다.
    (필자는 이미 설치된 상태이므로 간단하게 순서만 보여주겠음)

homebrew 등 툴을 사용하여 설치를 진행할 경우 항상 search를 통해 버전을 확인한 뒤 이를 명시하여 설치하도록한다.
📌버전을 명시하지 않으면 (필자가 이미 설치한 걸로 나오는) 가장 최신 버전이 깔린다. 현재 최신 버전은 9.X이다.

IntelliJ에 톰캣 추가

좌측 상단의 프로젝트 실행바의 Add Configuration을 클릭한다.
버튼을 눌러서 아래로 내려 Tomcat Server - Local을 선택한다.

TomcatHome을 등록해야하는데, 이 때 주의 할 점이 있다.

📌homebrew를 이용해 설치할 시 위와 같은 구조로 설치가 되는데 (홈페이지에서 직접 다운 받을 때와 구조가 다르다.) 하위에 bin, logs, webapps 등을 포함하고 있는 libexec를 경로로 선택하여야한다. 위와같이 Tomcat Version이 올바르게 자동으로 표시되면 성공

Server 바로 오른편의 Deployment를 클릭하면 우측 아래에 [Fix] 버튼이 있다. 이를 클릭하면
Artifact를 수동으로 생성할 수 있는데, 우리는 war파일을 사용할 것이므로 Web Application: Archive를 클릭한다.

Fix 버튼이 나오지 않는 경우에는 Apply를 먼저 눌러 서버만 설정된 Tomcat을 Run/Debug Configuration에 먼저 등록한 다음 다시 클릭해서 들어가 보도록한다.

생성할 war파일의 이름을 지정하고, 우측에서 WEB-INF/lib폴더에 추가할 라이브러리를 더블클릭해준다. 이는 tomcat이 어플리케이션을 구동할 때 라이브러리를 WEB-INF/lib폴더에서 찾기 때문이므로 설정하지 않으면 tomcat에서 에러가 나는 경우가 종종 있다.

tomcat이 잘 등록됐다면 위와 같이 나타날 것이다.
그럼 우측의 실행 버튼을 눌러 프로젝트가 실행되는지 확인해보자.

서버에 연결됐다는 말이 커멘드라인에 나타나면 localhost:8080으로 서버에 접속해보자

404 에러가 나타나도 당황하지말자. 어짜피 우리는 뷰를 만들지도, 설정하지도 않았기때문에 이는 당연한 것이다. 중요한 것은 우리의 프로젝트가 Apache Tomcat을 통해 잘 돌아가고 있는 것인지 확인하는 것이다.

2. DispatcherServlet 등록하기

web.xml

코드

우선 진행하는 프로젝트에 기본적으로 들어가는 코드를 보자.

<?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">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--<init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <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>
</web-app>

web.xml이란

ref. https://cloud.google.com/appengine/docs/standard/java/config/webxml?hl=ko

  • 한마디로 deployment discriptor(배포 설명자)이다.
  • Web Application이라면 해당 파일이 반드시 필요하다.
  • WAS(톰캣)는 클라이언트 요청이 수신되면 배포 설명자를 사용해 요청의 URL을 해당 요청을 처리해야하는 코드에 매핑한다.
<web-app ... >
    <servlet>
        <servlet-name>homeServlet</servlet-name>
        <servlet-class>servlets.HomeServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>homeServlet</servlet-name>
        <url-pattern>/home</url-pattern>
    </servlet-mapping>
</web-app>
  • 위 예제에서 homeServlet이라는 이름의 servlets.HomeServlet 클래스는 /home이라는 Uri에 매핑된다. e.g) localhost:8080/home
  • 즉, 모든 요청을 어디로 보내서 처리하는가는 이 web.xml에 달려있다.
  • 이렇듯 자바 웹 어플리케이션은 URL이 서블릿에 매칭되는 방법, 인증이 필요한 URL 정보 등을 표시한다.

하지만 이와 같이 하나의 서블릿과 하나의 URL을 매칭하는 것은 여간 힘들일이 아니기 때문에 DispatcherServlet을 이용하여 이를 쉽게 사용할 수 있는 것이다.
위 쪽에서 미리 보여준 코드를 다시 보자.

DispaterServlet 매핑

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--<init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
        </init-param>-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

스프링 프레임워크에서 제공하는 org.springframework.web.servlet.DispatcherServlet 클래스를 서블릿 클래스로 사용하고 있으며 이름은 dispatcher로 설정하였다.
내부 파라미터를 사용(주석처리된 부분, <init-param>)하여 해당 서블릿이 필요로하는 context의 위치정보를 전달하는데 {servlet-name}-servlet.xml로 자동 설정되므로, 양식에 맞게 context file 이름을 설정한 경우 이는 없어도 되는 설정이다.

이 디스패처 서블릿은 모든 경로에 대해 작동한다.

root-context, listener

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

<servlet>이 init-param에 명시된 context 파일만 참조하는 것에 비해 전역으로 설정된 <context-param>모든 서블릿(jsp 포함)과 필터에서 사용되는 루트 설정이 있는 파일이 명시된다.

<listener>로 등록된 ContextLoaderListener 클래스는 Context Loader가 로드하는 설정 파일을 여러개 작성할 수 있게 만든다. 즉, context-param에 선언된 xml 파일을 로드하는데 사용되는 것이며 이를 명시하지 않으면 디폴트로 /WEB-INF/applicationContext.xml 만을 사용하게 된다.

filter

    <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>

클라이언트 요청과 디스패처 서블릿 사이에는 필터가 존재하며 이를 이용해 HTTP 요청과 응답을 변경할 수 있다. 이 또한 클래스의 일종으로서 객체의 형태이다. 필터는 원하는 만큼 여러개를 사용해 필터 체인으로 만들어 쓰는 것도 가능하다.

위에서는 CharacterEncodingFilter를 이용하여 한글을 변환할 수 있도록 설정하였다.
/*를 명시하여 모든 요청에 대해 해당 필터를 사용함을 나타냈다.

applicationContext.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>

보통 데이터 베이스와 관련한 설정이 들어가며, 모든 서블릿과 필터에 우선적으로 필요한 내용을 포함한다. 현재는 따로 설정할 필요가 없으므로 비워둔다.

Dispatcher-servlet.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <mvc:annotation-driven />
    <context:component-scan base-package="com.freeboard01.controller" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <bean class="com.github.jknack.handlebars.springmvc.HandlebarsViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".hbs"/>
    </bean>
</beans>

web.xml에서 dispatcher라는 이름으로 선언한 디스패처 서블릿의 컨텍스트 파일이다.

  • <mvc:annotation-driven /> : @Controller, @Repository 등 자바 어노테이션을 이용한 스프링 설정을 사용할 수 있도록 해준다.
  • <context:component-scan base-package="com.freeboard01.controller" /> : 위의 어노테이션 사용 설정과 함께, base-package에 포함된 모든 클래스를 기준으로 어노테이션을 확인하며 이에 맞는 Bean을 생성하게 하는 설정이다. include-filter를 이용해 @Controller 어노테이션을 가진 클래스만 빈으로 등록할 것을 명시한다.
  • HandlebarsViewResolver : 컨트롤러에서 어떤 뷰를 사용할 것인지 문자열로 반환해주면 이를 이용하여 적절한 템플릿을 꺼내온다. prefix와 suffix사이에 반환된 문자열이 들어간다. (e.g. /WEB-INF/view/index.hbs)

3. Controller 생성

위에서 만들었던 index.hbs에 다음과 같은 html 마크업을 작성한다.

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Hello</title>
</head>
<body>
    <h1>{{msg}}</h1>
    <button onclick='location.href="/freeboard01/anotherpage"'>페이지 이동하기</button>
</body>
</html>

src/main/java 하위에 폴더를 생성하고 HomeController 클래스를 만들어 다음과 같이 작성한다.

package com.freeboard01.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model){
        model.addAttribute("msg", "Hello World!");
        return "index";
    }

    @GetMapping("/anotherpage")
    public ModelAndView buttonTest(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("index");
        modelAndView.addObject("msg", "다른 페이지로 이동하였습니다!!!");
        return modelAndView;
    }

}

dispatcher-servlet.xml<context:component-scan base-package="com.freeboard01.*" />를 작성했으므로 @Controller 어노테이션에 의해 해당 컨트롤러는 빈으로 등록된다.

home 메소드와 buttonTest 메소드는 각기 다른 방식으로 작성하였다.
home은 Model을 파라미터로 받은 뒤 view에서 사용할 attribute를 셋해주고 반환하는 것은 View name에 해당하는 문자열이다. 👉이는 dispatcher-servlet.xml에 작성한 viewResolver설정으로 인해 /WEB-INF/view/index.hbs로 변환돼 찾아진다.

buttonTest는 modelAndView를 return 타입으로 사용하여 View name 또한 함께 객체 담에서 반환해 주었다.

톰캣을 재실행해주면 아래와 같은 화면을 볼 수 있다.

버튼도 클릭해보자!

성공!!

다음 시간부터는 본격적으로 게시판을 만들어보도록 하겠다.

📌Jira와 연동하기 Preference - Tools - Tasks - Servers 에 들어가 버튼을 클릭하여 JIRA를 찾는다.
위와 같이 지라주소, 이메일, 토큰을 기입한 후 연결하면 활성 스프린트의 지라 티켓을 사용할 수 있다.

삽질

처음에 말했던 SpringMVC를 추가하였을 경우 자동으로 프로젝트 최상위 경로에 lib 폴더가 생성되며 관련된 .jar 라이브러리가 다운받아진다.
web 디렉토리가 src 디렉토리와 동일한 댑스에 생성되며 하위에 WEB-INF를 비롯한 .xml 설정 파일들도 함께 생성된다.
여기서 설정을 하는 것은 문제가 되지 않으나, java 파일에서 어노테이션을 사용하고자하면 lib 폴더 내의 라이브러리를 찾아내지 못하는 오류가 발생한다.
gradle의 dependency로 필요한 라이브러리를 추가하면 .xml에서 참조하는 라이브러리 경로가 깨지게 된다..🤦🏻
인텔리제이 설정 상의 문제인지 내가 코드에 뭔갈 추가하지 않은 것인지 모르겠음

profile
🌱 😈💻 🌱

0개의 댓글