java-school logo

Maven으로 Spring MVC 실습

아래 글은 워크스페이스를 C:\www로 정하고 진행했다.

아키타입 생성

아키타입은 원형이란 사전적 의미를 가지는데, 자바에선 프로젝트 프로토타입을 뜻한다.

C:\ Command Prompt
C:\www>mvn archetype:generate -Dfilter=maven-archetype-webapp
Choose archetype:
1: remote -> org.apache.maven.archetypes:maven-archetype-webapp (An archetype which contains a sample Maven Webapp project.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
Choose a number: 5: ↵
Define value for property 'groupId': : net.java_school
Define value for property 'artifactId': : spring-bbs
Define value for property 'version':  1.0-SNAPSHOT: : ↵
Define value for property 'package':  net.java_school: : ↵
Confirm properties configuration:
groupId: net.java_school
artifactId: spring-bbs
version: 1.0-SNAPSHOT
package: net.java_school
 Y: : ↵

빌드가 완료되면 C:\www에 spring-bbs라는 폴더가 생긴다. C:\www\spring-bbs가 메이븐 프로젝트의 루트 디렉터리다.

그다음 톰캣 컨텍스트 파일을 만들어야 하는데, /WEB-INF 바로 위인 src/main/webapp가 도큐먼트베이스(DocumentBase)이므로 아래와 같이 톰캣 컨텍스트를 생성한다.

spring-bbs.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context
    docBase="C:/www/spring-bbs/src/main/webapp"
    reloadable="true">
</Context>

{TOMCAT_HOME}/conf/Catalina/localhost 폴더에 위에서 작성한 spring-bbs.xml 파일을 복사한다. 톰캣을 재실행한 후, http://localhost:port/spring-bbs를 방문하여 웹 애플리케이션이 동작하는지 확인한다.

Spring MVC 테스트

pom.xml 파일을 다음과 같이 수정한다.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/maven-v4_0_0.xsd">
    
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.java_school</groupId>
  <artifactId>spring-bbs</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>spring-bbs Maven Webapp</name>
  <url>http://maven.apache.org</url>

  <properties>
    <spring.version>4.3.9.RELEASE</spring.version>
    <jdk.version>1.8</jdk.version>
  </properties>
  
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>    
    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.10</version>
    </dependency>
    <!-- Oracle JDBC Driver -->
    <dependency>
      <groupId>com.oracle</groupId>
      <artifactId>ojdbc6</artifactId>
      <version>11.2.0.2.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->                
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.5</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.2</version>
    </dependency>    
    <!-- Servlet JSP JSTL -->
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.1</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/jstl/jstl -->
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
  </dependencies>
  
	<build>
		<finalName>spring-bbs</finalName>
		<pluginManagement>
			<plugins>
				<plugin>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.1</version>
					<configuration>
						<source>${jdk.version}</source>
						<target>${jdk.version}</target>
						<compilerArgument></compilerArgument>
						<encoding>UTF-8</encoding>
					</configuration>
				</plugin>
				<plugin>
					<artifactId>maven-clean-plugin</artifactId>
					<version>2.4.1</version>
					<configuration>
						<filesets>
							<fileset>
								<directory>src/main/webapp/WEB-INF/classes</directory>
							</fileset>
							<fileset>
								<directory>src/main/webapp/WEB-INF/lib</directory>
							</fileset>
						</filesets>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>

</project>

pom.xml은 Spring MVC 과정의 최종 결과물이다. 의존성 설정 중 오라클 JDBC는 어떤 이유에서인지 모르겠지만, 메이븐 저장소에서 제공하지 않고 있다. 따라서 오라클이 설치된 디렉터리에서 ojdbc6.jar를 찾거나 오라클 공식 웹사이트에서 내려받아서 로컬 저장소에 저장한 다음 빌드를 실행해야 빌드가 성공한다. 오라클 JDBC 드라이버를 로컬 저장소에 설치하는 방법은 다음 문서를 참고한다. 오라클 JDBC 드라이버를 로컬 저장소에 설치하기

web.xml를 수정한다.

web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
 Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->


<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0"
  metadata-complete="true">
  
  <display-name>Spring BBS</display-name>
	
  <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>
    
  <servlet>
    <servlet-name>spring-bbs</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
    
  <servlet-mapping>
    <servlet-name>spring-bbs</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  
  <error-page>
    <error-code>404</error-code>
    <location>/WEB-INF/views/400.jsp</location>
  </error-page>

  <error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/views/500.jsp</location>
  </error-page> 
 
</web-app>

모든 요청에 대해 setCharacterEncoding("UTF-8");를 호출하는 필터가 작동하도록 설정했다. 비영어권 웹 사이트에선 이 필터가 필요하다. 이 필터는 다른 필터보다 먼저 작동하도록 순서에 주의한다.

스프링 MVC의 DispatcherServlet이라는 서블릿을 등록하고 매핑했다. DispatcherServlet의 이름을 spring-bbs라고 정했기에, 스프링 설정파일의 이름은 sprng-bbs-servlet.xml으로 결정된다. spring-bbs-servlet.xml 파일을 web.xml과 같은 위치인 /WEB-INF에 아래 내용으로 생성한다.

spring-bbs-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:mvc="http://www.springframework.org/schema/mvc"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc.xsd">
	
</beans>

Spring MVC 테스트를 위해 다음 자바 소스를 작성한다. src/main/java 폴더가 없으면 만들고 진행한다.

Player.java
package net.java_school.soccer;

public class Player {
	private String id;
	private String passwd;
	private String name;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
}
PlayerDao.java
package net.java_school.soccer;

public class PlayerDao {

	public Player selectOne(String id) {
		Player player = new Player();
		if (id != null && id.equals("1")) {
			player.setName("Lionel Messi");
		} else if (id != null && id.equals("2")) {
			player.setName("Cristiano Ronaldo");
		} else {
			player.setName("Neymar");
		}
		return player;
	}

}
PlayerService.java
package net.java_school.soccer;

public class PlayerService {
	private PlayerDao dao;
	
	public void setDao(PlayerDao dao) {
		this.dao = dao;
	}

	public Player getPlayer(String id) {
		return dao.selectOne(id);
	}
	
}
PlayerController.java
package net.java_school.soccer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class PlayerController implements Controller {
	
	private PlayerService service;
	
	public void setService(PlayerService service) {
		this.service = service;
	}
	
	public ModelAndView handleRequest(HttpServletRequest req,
			HttpServletResponse res) throws Exception {
		
		String id = req.getParameter("id");
		Player player = service.getPlayer(id);
		
		// 모델 생성
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("player", player);
		
		//반환값인 ModelAndView 인스턴스 생성
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.setViewName("/WEB-INF/views/player/test.jsp");
		modelAndView.addAllObjects(model);
		
		return modelAndView;
		
	}
	
}

/src/main/webapp/WEB-INF/views/player 디렉토리를 만들고, test.jsp 파일을 만들어 위치시킨다.

test.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>TEST</title>
</head>
<body>
${player.name }
</body>
</html>

빌드를 한다.

C:\ Command Prompt
C:\www\sprng-bbs>mvn clean compile war:inplace

톰캣을 재실행한 후, http://localhost:8080/spring-bbs/player/test?id=1를 방문한다.

SimpleUrlHandlerMapping

핸들러매핑 설정을 SimpleUrlHandlerMapping를 이용하는 것으로 변경한다. spring-bbs-servlet.xml 파일을 열고 아래 코드를 참조하여 수정한다. 이때 id가 playerController인 bean 엘리먼트의 name 속성과 값을 지워야 한다. <bean id="playerController" name="/player/test"

spring-bbs-servlet.xml
<!-- 중간 생략 -->

<!-- HandlerMapping -->
<bean id="handlerMapping" 
  class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
	
  <property name="mappings">
    <value>
	/player/test=playerController
    </value>
  </property>
	
</bean>

<bean id="playerController" 
  class="net.java_school.soccer.PlayerController"
  p:service-ref="playerService" />
	
<!-- 중간 생략 -->

톰캣을 재실행하고 http://localhost:port/spring-bbs/player/test?id=1를 방문한다.

InternalResourceViewResolver

InternalResourceViewResolver는 직관적이라 이해하기 쉬운 뷰리졸버이다. spring-bbs-servlet.xml 파일을 열고 <!-- ViewResolver --> 부분에 다음을 추가한다.

spring-bbs-servlet.xml
<!-- ViewResolver -->
<bean id="internalResourceViewResolver" 
	class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="viewClass">
		<value>org.springframework.web.servlet.view.JstlView</value>
	</property>
	<property name="prefix">
		<value>/WEB-INF/views/</value>
	</property>
	<property name="suffix">
		<value>.jsp</value>
	</property>
</bean>

PlayerController.java을 열고 강조된 부분을 수정한다.

PlayerController.java
//반환값인 ModelAndView 인스턴스 생성
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("player/test");
modelAndView.addAllObjects(model);

return modelAndView;

InternalResourceViewResolver 설정대로 player/test는 /WEB-INF/views/player/test.jsp로 해석될 것이다. 자바 소스가 변경되었으므로 빌드를 한다. 톰캣을 재실행하고 http://localhost:port/spring-bbs/player/test?id=1를 방문한다.

RequestMappingHandlerMapping

SimpleUrlHandlerMapping은 요청마다 컨트롤러가 하나씩 있어야 한다. 프로젝트가 규모가 커질수록 이런 모양은 좋아 보이지 않는다. 이상적인 것은 유즈 케이스별로 컨트롤러를 하나씩 두는 것이다. RequestMappingHandlerMapping으로 핸들러 매핑을 하면 그렇게 할 수 있다. RequestMappingHandlerMapping으로 핸들러 매핑을 한다는 것은 어노테이션과 자동 스캔 기능을 이용하게 됨을 의미한다. spring-bbs-servlet.xml 파일을 열고 강조된 부분을 추가한다. <!-- HandlerMapping -->부분은 모두 제거한다.

spring-bbs-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"
	xmlns:p="http://www.springframework.org/schema/p"
	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="net.java_school.soccer" />
	
	<!-- ViewResolver -->
	<bean id="internalResourceViewResolver" 
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="viewClass">
			<value>org.springframework.web.servlet.view.JstlView</value>
		</property>
		<property name="prefix">
			<value>/WEB-INF/views/</value>
		</property>
		<property name="suffix">
			<value>.jsp</value>
		</property>
	</bean>
	
	<bean id="playerService" class="net.java_school.soccer.PlayerService">
		<property name="dao" ref="playerDao" />
	</bean>
	
	<bean id="playerDao" class="net.java_school.soccer.PlayerDao" />
	
</beans>

<context:component-scan base-package="net.java_school.soccer" /> 설정은 스프링으로 하여금 어노테이션이 적용된 컴포넌트(컴포넌트는 Dao, Service, 컨트롤러를 의미한다)를 찾아 자동으로 등록하게 한다. 이 설정으로 구체적인 핸들러 매핑 클래스에 대한 설정을 스프링 설정 파일에서 생략할 수 있는 것이다. <mvc:annotation-driven />은 스프링 MVC에서 필요한 어노테이션 기반의 모든 기능을 사용할 수 있도록 하는 설정이다. <context:component-scan /><mvc:annotation-driven />과 함께 쓰지 않으면 작동하지 않는다. 스프링이 스캔해서 컨트롤러를 찾을 수 있도록 컨트롤러에 어노테이션을 적용해야 한다. PlayerController.java 파일을 아래와 같이 다시 작성한다.

PlayerController.java
package net.java_school.soccer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/player")
public class PlayerController {
	
	@Autowired
	private PlayerService service;
	
	@RequestMapping(value="/test", method={RequestMethod.GET, RequestMethod.POST})
	public String pickPlayer(String id, Model model) {
			
		Player player = service.getPlayer(id);
		
		model.addAttribute("player", player);
		
		return "player/test";
		
	}
	
}

빌드하고 톰캣을 재실행한 후, http://localhost:8080/spring-bbs/player/test?id=1를 방문한다.

PlayerController.java에 쓰인 어노테이션
클래스 선언부의 @Controller는 클래스가 컨트롤러 컴포넌트임을 표시한다.
클래스 선언부의 @RequestMapping("/player")는 컨트롤러가 "/player"를 포함하는 모든 요청을 담당함을 표시한다.
메소드 선언부의 @RequestMapping(value="/test", method={RequestMethod.GET, RequestMethod.POST}):는 메소드가 GET 방식이나 POST 방식의 "/player/test" 요청에 호출됨을 표시한다.
@Autowired를 종속변수에 적용하면 setter 없이도 종속객체가 주입된다. 종속변수 접근자가 private여도 상관없다.

컴포넌트 스캔 기능이 동작하므로 서비스와 Dao에 어노테이션을 적용하면 spring-bbs-servlet.xml에서 이들의 설정을 생략할 수 있다.

PlayerDao.java
package net.java_school.soccer;

import org.springframework.stereotype.Repository;

@Repository
public class PlayerDao {

	public Player selectOne(String id) {
		Player player = new Player();
		if (id != null && id.equals("1")) {
			player.setName("Lionel Messi");
		} else if (id != null && id.equals("2")) {
			player.setName("Cristiano Ronaldo");
		} else {
			player.setName("Neymar");
		}
		return player;
	}

}
PlayerService.java
package net.java_school.soccer;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class PlayerService {
	@Autowired
	private PlayerDao dao;
	
	public Player getPlayer(String id) {
		return dao.selectOne(id);
	}
	
}

어노테이션을 적용하면 스프링 설정 파일은 다음과 같이 간단해진다.

spring-bbs-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"
	xmlns:p="http://www.springframework.org/schema/p"
	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="net.java_school.soccer" />
		
	<!-- ViewResolver -->
	<bean id="internalResourceViewResolver" 
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="viewClass">
			<value>org.springframework.web.servlet.view.JstlView</value>
		</property>
		<property name="prefix">
			<value>/WEB-INF/views/</value>
		</property>
		<property name="suffix">
			<value>.jsp</value>
		</property>
	</bean>
	
</beans>

이클립스 작업환경 구축

이클립스를 시작하고 워크스페이스를 C:\www로 선택한다. Project Explorer 뷰에서 마우스 오른쪽 버튼을 사용하여 컨텍스트 메뉴를 보이게 한다. Import를 사용하여 spring-bbs 프로젝트를 이클립스로 불러온다.
컨텍스트 메뉴에서 Import
이클립스에서 메이븐 프로젝트 Import
진행하면서 pom.xml 파일이 바뀌면 이클립스 설정과 동기화를 해주어야 한다.
pom과 이클립스 설정 동기화

참고