Programming/Spring

[Spring] Spring 기반 MVC 웹 프로젝트 구현

Isaac S. Lee 2023. 11. 21. 14:02

Spring은 Controller, View 등을 사용하기 편하도록 MVC 패턴을 모두 구현을 해 두었다.

Servlet/JSP에서는 웹 요청 처리와 통제를 Controller 역할로 지정해 주었지만, Spring MVC에서는 Controller라는 이름이 전용으로 있으므로 이를 사용한다.

 

🌿Controller


Ex01Controller.java: jsp 연결

package com.test.spring.controller;

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 Ex01Controller implements Controller {

	//doGet/doPost == handleRequest
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

		//업무 진행..
		
		//뷰 호출(JSP)
		//RequestDispatcher > forward()
		
		//ModelAndView 사용
		//- Model > 데이터 전송
		//- View > JSP
		
		ModelAndView mv = new ModelAndView(); //jsp를 부르기 위한 객체 생성
		
		//mv.setViewName("JSP 경로");
		//mv.setViewName("/WEBAPP/views/ex01.jsp");
		mv.setViewName("ex01");
		
		return mv;
	}
	
}

이 컨트롤러는 서블릿은 아니지만 서블릿의 컨트롤러 같은 역할을 한다.
Spring은 컨트롤러 역할을 하는 것을 Controller를 상속받아서 사용하도록 하는데, 서블릿이 아니기 때문에 가상주소 어노테이션이 없다.
전부 스프링 환경 하에서 관리되므로 <bean>으로 관리해야 하며, 이를 위해 xml이 필요하다.
컨트롤러는 웹 요청과 관련되어 있기 때문에 servlet-context.xml에서 작업한다.

 

servlet-context.xml: jsp 연결

<?xml version="1.0" encoding="UTF-8"?>

<!-- beans:beans == beans 빈과 동일하지만 네임스페이스를 안 적으면 안 되는 상황으로 생각하면 된다. -->
<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>
	
	<!-- 
		InternalResourceViewResolver > ViewResolver
	-->
	
	<context:component-scan base-package="com.test.spring" />
	
	<!-- 컨트롤러 인식 + 가상 주소 매핑 -->
	<beans:bean name="/ex01.do" class="com.test.spring.controller.Ex01Controller"></beans:bean>
	
</beans:beans>

id는 안 쓰고 name을 쓰는데, 이때 가상 주소를 붙인다. 이게 컨트롤러의 가상 주소를 만드는 방법이다. 이제 다시 컨트롤러로 간다.

 

Multiple 에러 발생

Multiple Contexts have a path of "/spring".

현재 아파치 톰캣에 동일한 Path가 2개가 있어서 Multiple 오류가 발생한다.

처음 MVC 프로젝트를 만들 때 메인 패키지를 적도록 했다. 그런데 하필이면 두 프로젝트 모두 com.test.spring이라고 적었는데, 마지막 3번째 단어가 Root Context Path로 적히기 때문에 오류가 발생한 것이다.

 

HTTP 상태 404-찾을 수 없음

  • http://localhost:8090/spring/WEB-INF/classes/com/test/spring/controller/Ex01Controller.java

컨트롤러 그대로 자바 주소가 뜬다. 스프링은 자동으로 가상 주소가 붙지 않기 때문이다.

그래서 spring/뒤를 모두 지우고 http://localhost:8090/spring/ex01.do로 직접 주소를 작성해야 한다.

 

주소 변경

//mv.setViewName("/WEBAPP/views/ex01.jsp");
mv.setViewName("ex01");

  • http://localhost:8090/spring/ex01.do

그런데 이번에는 확장자가 이상해서 오류가 발생한다.

스프링은 장치가 하나 더 들어 있어서 이름을 ex01로 파일 이름만 작성해 주어야 한다.

이제 ex01.jsp 파일이 제대로 실행된다.

 

InternalResourceViewResolver > ViewResolver

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

<!-- 
    InternalResourceViewResolver > ViewResolver
-->

스프링이 이름 앞에 ViewResolver를 통해서 "/WEB-INF/views/" +"ex01" + ".jsp"로 접두어를 붙이기 때문에 이러한 에러가 발생했던 것이다.

이 기능은 필수로 사용해야 하는 기능이 아니므로, 사용하지 않을 경우 주석 처리하면 된다.

 

위 그림에서 Ex01Controller는 제일 앞에 있는 컨트롤러라고 해서 Front-Controller라고도 부른다.

클라이언트로부터 어떤 주소가 하나 들어오면 그것과 똑같이 생긴 <bean>태그를 Server에서 찾는다.

요청이 들어온 /ex01.do와 똑같이 생긴 <bean>을 호출하면 해당 클래스 인스턴스를 찾아서 호출하며, 실행의 권한이 컨트롤러 클래스로 넘어가게 된다.

JSP로 호출할 때 방식은 request를 가지고 데이터를 담아서 전송해도 되지만, Spring 방식으로 데이터를 담는 역할과 전송하는 역할을 동시에 하는 ModelAndView로 return mv() forsward 한다

이 과정은 면접에서 자주 나오는 질문이기도 하다.

 

전체 코드

Ex01Controller.java

package com.test.spring.controller;

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 Ex01Controller implements Controller {

	//doGet/doPost == handleRequest
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

		//업무 진행..
		
		//뷰 호출(JSP)
		//RequestDispatcher > forward()
		
		//ModelAndView 사용
		//- Model > 데이터 전송
		//- View > JSP
		
		ModelAndView mv = new ModelAndView(); //jsp를 부르기 위한 객체 생성
		
		//mv.setViewName("JSP 경로");
		//mv.setViewName("/WEBAPP/views/ex01.jsp");
		mv.setViewName("ex01");
		
		request.setAttribute("name", "Isaac");
		mv.addObject("age", "23"); //권장
		//기존 방식으로도 전달은 되지만 ModelAndView를 사용하는 것을 권장한다.
		
		return mv;
	}
	
}

 

ex01.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>Ex01</h1>
	
	<div>이름: ${name}</div>
	<div>나이: ${age}</div>
</body>
</html>

 

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- beans:beans == beans 빈과 동일하지만 네임스페이스를 안 적으면 안 되는 상황으로 생각하면 된다. -->
<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>
	
	<!-- 
		InternalResourceViewResolver > ViewResolver
	-->
	
	<context:component-scan base-package="com.test.spring" />
	
	<!-- 컨트롤러 인식 + 가상 주소 매핑 -->
	<beans:bean name="/ex01.do" class="com.test.spring.controller.Ex01Controller"></beans:bean>
	
</beans:beans>

 

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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 https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- 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> <!-- 서블릿이라는 객체가 xml객체를 읽어들였기 때문에 어떤 작업을 했는지 알고 있다. -->
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern> <!-- 모든 url이라는 의미이다. -->
	</servlet-mapping>

</web-app>

 

🌿DB 작업


Ex02Controller.java

package com.test.spring.controller;

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

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

import com.test.spring.persistence.SpringDAO;
import com.test.spring.persistence.SpringDAOImpl;

public class Ex02Controller implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

		//업무 중 일부 > DB 작업 > DAO 위임
		SpringDAO dao = new SpringDAOImpl();
		
		int count = dao.getCount();
		
		ModelAndView mv = new ModelAndView();
		
		mv.addObject("count", count);
		mv.setViewName("ex02");
		
		return mv; //null > mv
	}

}

 

servlet-context.xml

<beans:bean name="/ex02.do" class="com.test.spring.controller.Ex02Controller"></beans:bean>

 

SpringDAO.java

package com.test.spring.persistence;

public interface SpringDAO {

	int getCount();
	
}

 

SpringDAOImpl.java

package com.test.spring.persistence;

public class SpringDAOImpl implements SpringDAO {

	@Override
	public int getCount() {

		return 100; //select 한 값
		
	}
	
}

 

ex02.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>Ex02</h1>
	
	<div>count: ${count}</div>
</body>
</html>

 

🍃Ex02Controller.java

package com.test.spring.controller;

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 Ex01Controller implements Controller {

	//doGet/doPost == handleRequest
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

		//업무 진행..
		
		//뷰 호출(JSP)
		//RequestDispatcher > forward()
		
		//ModelAndView 사용
		//- Model > 데이터 전송
		//- View > JSP
		
		ModelAndView mv = new ModelAndView(); //jsp를 부르기 위한 객체 생성
		
		//mv.setViewName("JSP 경로");
		//mv.setViewName("/WEBAPP/views/ex01.jsp");
		mv.setViewName("ex01");
		
		request.setAttribute("name", "Isaac");
		mv.addObject("age", "23"); //권장
		//기존 방식으로도 전달은 되지만 ModelAndView를 사용하는 것을 권장한다.
		
		return mv;
	}
	
}

DAO를 직접 만들고 있었던 것을  Spring에게 위임하여 Spring이 만들게 한다.

멤버 변수와 생성자를 만든 뒤, 의존 주입을 하도록 한다.

 

🍃servlet-context.xml

<beans:bean id="dao" class="com.test.spring.persistence.SpringDAOImpl"></beans:bean>

<beans:bean name="/ex02.do" class="com.test.spring.controller.Ex02Controller">
	<beans:constructor-arg ref="dao"></beans:constructor-arg>
</beans:bean>

xml을 고치면 반드시 서버를 재시작해야 한다.