스프링 MVC의 기본 구조
-스프링 MVC는 스프링의 서브(SUB)프로젝트 이다.
https://spring.io/projects를 보면 springFramework라는 메인 프로젝트 이외에도 여러 종류의 서브 프로젝트가
존재한다 ( ex- Spring Boot , Spring Framework, Spring Data , Spring Cloud , Spring Android.......)
스프링 MVC 역시 이러한 프로젝트 중 일부이다.
스프링은 하나의 기능을 위해서만 만들어진 프레임워크가 아니라 코어 라고 할수 있는 프레임워크에 여러 서브 프
로젝트를 결합해서 다양한 상황에 대처 할수 있도록 개발 되었다
서브 프로젝트라는 의미는 별도의 설정이 존재 할수 있다 라는 개념이다.
Spring Legacy Project 로 생성한 예제의 경우에도 servlet-context.xml과 root-context.xml 로 설정파일이 분리 된것을 볼 수 있다.
스프링 MVC가 서브 프로젝트이므로 구성방식이나 설정 역시 조금 다르다고 볼수 있다
해당 책의 예제에서 만드는 구조는 다음과 같은 구조가 된다.
XML 설정 방식의 경우 servlet-context.xml과 root-context.xml 을 이용하고
Java설정의 경우에는 ServletConfig.class 와 RootConfig.class를 이용한다.
스프링 MVC 프로젝트의 내부 구조
스프링 MVC프로젝트를 구성해서 사용한다는 의미는 내부적으로는 root-context.xml로 사용하는 일반 Java영역(흔히 POJO)과 serlvet-context.xml로 설정하는 Web 관련영역을 같이 연동해서 구동.
바깥쪽의 WebApplicationContext라는 구조는 기존의 구조에 MVC설정을 포함하는 구조로 만들어진다.
스프링은 원래 목적자체가 웹플리케이션을 목적으로 나온 프레인워크가 X 때문에 달라지는 영역에 대해서는 완전히 분리하고 연동하는 방식으로 구현 되어있다.
pom.xml에서 springframework 버전을 5.0.7. 로 바꿔준다
Lombok 기능을 이용해서 코드를 작성하고 테스트 할 것이기 때문에 다음코드를
추가해 준다.
<!-- lombok -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
<scope>provided</scope>
</dependency>
Spring Legacy Projet로 생성된 프로젝트는 서블릿 2.5버전을 사용하지만, Java설정 등을 이용하려면
2.5버전은 주석처리 또는 삭제하고 3.10 이상 버전을 사용하도록 수정한다.
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
메이븐 컴파일 옵션은 1.8로 변경
maven- updateProject 실행한다. (alt+f5)
이상이없다면 실행한다.
절대 경로를 이용하는 구조를 사용하기 위해 Tomcat의 Modules 메뉴를 이용해서 '/' 경로로 프로젝트가 실행 될수 있도록 아래와 같은 처리를 한다.
톰캣 재시작해서 확인한다.
Java 설정을 이용하는 경우
Java 설정을 이용하는 프로젝트의 경우
web.xml과 spring폴더(context.xml, serlvet-context)를 삭제해 준다.
pom.xml에서 web.xml이 없다는 설정을 추가해야한다.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
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 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.zerock</groupId>
<artifactId>controller</artifactId>
<name>jex01</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.8</java-version>
<org.springframework-version>5.0.7.RELEASE</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<!-- <dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency> -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Java 설정을 이용하는 경우 servlet-context.xml 이 없는 상태에서 프로젝트를 시작하게되므로
'org.zerock.config"패키지를 만들어주고 WebConfig와 RootConfig 클래스를 작성한다.
RootConfig는 part2에서 별다른 역할이 없기에
package org.zerock.config;
import org.springframework.test.context.ContextConfiguration;
@ContextConfiguration
public class RootConfig {
}
WebConfig.class는 다음과 같다
package org.zerock.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return null;
}
}
Spring MVC를 이용하는 경우 servlet-config.xml을 대신하는 별도의 ServletConfig 클래스를 작성한다.
package org.zerock.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@EnableWebMvc
@ComponentScan(basePackages = {"org.zerock.controller"})
public class ServletConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setViewClass(JstlView.class);
bean.setPrefix("/WEB-INF/views/");
bean.setSuffix(".jsp");
registry.viewResolver(bean);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
WebMvcConfigurer 는 스프링 MVC와 관련된 설정을 메서드로오버라이드하는 형태를 이용할때 사용
ServletConfig 클래스 역시 @ComponentScan을 이용해서 다른 패키지에 작성된 스프링의 객체(Bean)를 인식 할 수 있다.
작성된 ServltConfig 클래스를 정상적으로 실행하려면 WebConfig의 설정은 아래와 같이
ServletConfig를 이용하고, 스프링MVC의 기본 경로도 '/'로 변경되어야한다.
package org.zerock.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {ServletConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
이후 설정작업은 XML과 동일하게 Tomcat에서 실행하면 된다.
프로젝트 구동시 관여하는 XML is web.xml, root-context.xml, servlet-context.xml
web.xml - 구동과 관련된 설정
root-context.xml, servlet-context.xml - 스프링과 관련된 설정
프로젝트의 구동은 web.xml에서 시작
web.xml의 상단에는 Context Listener가 등록되어있다.
<context.xml>에는 root-contex.xml 경로가 설정되어있고
<listener>에는 MVC의 ContextLoadListener가 등록되어 있는것 을 볼 수 있다.
ContextLoadListener는 해당 웹 어플리케이션 구동 시 같이 동작하므로 해당 프로젝트를 실행하면
다음과 같이 먼저 로그를 출력하면서 기록하는것을 볼 수 있다.
root-context.xml 이 처리되면 파일에있는 Bean 설정들이 동작한다.
root-context.xml 에 정의된 객체(Bean)들은 스프링의 영역(context) 안에 생성되고,
객체들 간의 의존성이 처리된다. root-context.xml이 처리된 후에는 스프링 MVC에서 사용하는
DispatcherServlet이라는 서블릿과 관련된 설정이 동작한다.
org.springframework.web.servlet.DispatcherServlet클래스는 스프링 MVC의 구동조건에서 가장 핵심적 역할을 하는 클래스이다.
내부적으로 웹 관련 처리의 준비작업을 진행하는데 이때 사용하는 파일이 servlet-context.xml이다.
프로젝트가 실행될때 로그를 보자
DispatcherServlet에서 XmlWebApplicationContext를 이용해서 serlvet-context.xml 을 로딩하고 해석하기 시작한다.
이 과정에서 등록된 객체 (Bean)들은 기존에 만들어진 객체(Bean)들과 같이 연동되게 된다.
스프링 MVC의 기본 사상
스프링 MVC를 이용하게 되면 개발자 들이 직접적으로 HttpServletRequest/HttpServletResponse 등과 같이
Servlet/JSP등의 API를 사용할 필요성이 줄어든다.
스프링 2.5 버전부터 등장한 어노테이션 방식으로 인해 어노테잉션이나 XML 등의 설정만으로 개발이 가능하게 되었다.
모델 2와 스프링 MVC
스프링 MVC역시 내부적으로는 Servlet API를 활용한다. 스프링 MVC는 모델2 라는 방긱으로 처리되는 구조이다.
모델 2방식은 로직과 화면을 분리 하는 스타일의 개발 방식이다.
모델 2 방식은 MVC구조를 사용하는데 이를 그림으로 표현하면 다음과 같다.
모델2 방식에서 사용자의 Request는 특별한 상황이 아닌이상 먼저 Controller를 호출 하게 된다.
이렇게 설계하는 이유는 나중에 View를 교체하더라도 사용자가 호출하는 URL자체에 변화가 없게 만들어 주기 때문이다.
컨트롤러는 데이터를 처리하는 존재를 이용해서 데이터(Model)를 처리하고 Response 할 때 필요한 데이터(Model)를 View쪽으로 전달하게 된다.
1. 사용자의 Request는 Front-Controller인 DispatcherServlet을 통해 처리된다. 생성된 프로젝트의 web.xml을 보면 아래와 같이 모든 Request를 DispatcherServlet이 받도록 처리하고 있다.
2,3 HandlerMapping은 Request의 처리를 담당하는 컨트롤러를 찾기 위해서 존재한다.
HandlerMapping 인터페이스를 구현한 여러 객체들 중 RequestMappingHandlerMapping같은 경우는 개발자
가 @RequestMapping 어노테이션이 적용된것을 기준으로 판단하게 된다.
적절한 컨트롤러가 찾아졌다면 HandlerAdapter를 이용해서 해당 컨트롤러를 동작시킨다.
4. Controller는 개발자가 작성하는 클래스로 실제 Request를 처리하는 로직을 작성하게 된다.
이때 View에 전달해야하는 데이터는 주로 Model 이라는 객체에 담아서 전달한다. Controller는 다양한 타입의
결과를 반환하는데 이에 대한 처리는 ViewResolver를 이용하게 된다.
5. ViewResolver는 Controller가 반환한결과를 어떤 View를 통해서 처리하는 것이 좋을지 해석하는 역할이다.
가장 흔하게 사용하는 설정은 servlet-context.xml에 정의된 InternalResourceViewResolver이다.
6,7 View는 실제로 응답 보내야하는 데이터를 Jsp등을 이용해서 생성하는 역할, 만들어진 응답은 DispatcherServlet을 통해 전송된다.
'web' 카테고리의 다른 글
코드로 배우는 스프링 웹프로젝트 - 6.5 Controller의 리턴타입 (0) | 2020.10.20 |
---|---|
코드로 배우는 스프링웹프로젝트 - 06 스프링 MVC의 Controller (1) | 2020.10.19 |
코드로 배우는 스프링 웹프로젝트 - log4jdbc-log4j2 설정 (0) | 2020.10.01 |
코드로 배우는 스프링 웹프로젝트 - 스프링과의 연동처리 (0) | 2020.09.30 |
코드로 배우는 스프링 웹프로젝트 - 04 MyBatis와 스프링 연동 (0) | 2020.09.30 |