* 사내 강의용으로 사용한 자료를 Blog에 공유합니다. Spring을 이용한 Web 개발에 대한 전반적인 내용에 대해서 다루고 있습니다.
기존 Spring 2.x 대의 Controller의 문제점은 다음과 같습니다.
2. bean name을 이용한 method - url mapping이 된다.
3. POST/GET/DELTE/PUT/HEAD 와 같은 http method에 적합하지 않다.
4. url 당 1개의 Controller 객체가 생성되기 때문에 Enterprise 개발에 적합하지 않다.
기존에 Controller Interface를 구현해야지 되었으나, 3.0대의 @MVC는 annotation을 이용해서 @Controller로 객체의 meta 정보를 적는 것으로 해결이 가능합니다.
@Controller
public class HelloController { @Autowired private HelloString helloString; @RequestMapping(value="index", method=RequestMethod.GET) public String index(@RequestParam(value="name") String name, ModelMap model) { model.addAttribute("message", helloString.say(name)); return "hello"; } }
URL을 지정해주는 pattern입니다. 예를 들어 다음과 같은 pattern들을 지정해주는 것이 가능합니다.
1. @RequestMapping("/hello")
2. @RequestMapping({"/hello", "/hello2"})
3. @RequestMapping("/hello1")
Path변수를 지정해주는 것이 가능합니다. "/hello/{name}" 과 같이 method의 input을 url에 같이 실어 주는것이 가능합니다.
1. @RequestMapping("/hello/{name}")
2. @RequestMapping("/hello/{name}/{number}")
method
RequestMethod를 지정하는 것 역시 가능합니다. GET, POST, DELETE, PUT, HEAD가 사용 가능합니다. 역시 method도 Path와 동일하게 중복 설정이 가능합니다.
params
url 요청 파라미터의 유무에 따라 호출되는 method를 결정할 수 있습니다.
1. RequestMapping(value="/user/edit", params="type=admin")
2. RequestMapping(value="/user/edit", params="type=member")
* /user/edit?type=admin
* /user/edit?type=member
method의 input값에 따라 method내에서의 if문을 만들지 않고 처리가 가능한 유용한 팁입니다.
headers
HTTP header정보에 따른 mapping역시 가능합니다. request type을 text/html, application/json 각각의 type에 따라 다른 method를 처리 가능합니다.
기본적으로 @Controller의 각 method는 다음과 같은 input parameter를 갖을 수 있습니다.
Type | Annotation | Description |
URL/POST parameter | @RequestParam | GET/POST/DELETE/PUT 등으로 호출될 때, 넘겨지는 Parameter값 |
Path value | @PathVariable | URL 에 포함된 특정 영역 문자열 |
Servlet | - | HttpServletRequest, HttpServletResponse를 직접 Handling 하는 기존 Servlet과 같은 코드 역시 사용 가능합니다. |
Cookie | @CookieValue | Cookie 값을 얻거나 설정할 수 있습니다. |
Session | @SessionAttributes | Session 값을 얻거나 설정할 수 있습니다. |
Body | @RequestBody | Request의 Body 부분을 모두 String으로 얻어내는 것이 가능합니다. |
2. void : 아무것도 return 하지 않습니다. 이는 method의 이름을 String 형태로 return 하는 것과 동일한 결과를 갖습니다.
3. Object : REST 형태의 Return값을 사용할 때 이용됩니다. 대체적으로 Object에 대한 Body Response를 덩어내는 것을 주 목적으로 하고 있습니다.
4. ModelAndView : Spring 2.X 형태로 Model과 View를 return 하는 것이 가능합니다.
여기서 ModelAndView 형태의 return값은 pdf, excel 과 같은 데이터를 다운받는 형태로 사용 가능합니다. 이 부분은 뒤에 View에서 다시 다루도록 하겠습니다.
간단하게 HelloController를 작성해보도록 하겠습니다. @Controller를 이용한 처리를 하더라도 Controller의 경우에는 Controller라는 이름을 붙여주는 것이 일반적입니다.
@Controller
public class HelloController { @Autowired private HelloString helloString; @RequestMapping(value="/index", method=RequestMethod.GET) public String index(@RequestParam(value="name") String name, ModelMap model) { model.addAttribute("message", helloString.say(name)); return "hello"; } }
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> <property name="order" value="1" /> </bean>
<context:component-scan base-package="com.xyzlast.mvc.spring02.controllers" />
web.xml을 구성해야지 됩니다. web.xml을 구성할 때, 앞으로 개발하기 좀 더 편하게 다음과 같은 설정을 추가하도록 하겠습니다. 모든 request에 .do가 붙는 경우에는 모두 spring에서 처리하도록 web.xml을 수정하도록 하겠습니다. 수정된 web.xml은 다음과 같이 구성됩니다. servlet-mapping에서 url-pattern이 구성된 방법을 확인해주세요. 이 pattern을 넣는것은 매우 중요한 의미를 갖습니다. .do가 아닌 .html과 잘 알려진 확장자 역시 처리가 가능합니다. 이러한 설정을 이용해서, spring을 통해서 작성된 web인지, 아니면 그냥 html인지 외부에서 착각을 유도하는 것도 가능합니다. 정부표준 프레임워크 등 대부분의 Framework에서는 .do를 사용하고 있습니다. 그리고 nhn에서는 .nhn을 붙여서 사용하고 있습니다. 다른 일반적인 file path들과 중복되지 않는 이름을 사용하는 것이 중요합니다.
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>mvc.spring02</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
</web-app>
<?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" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="helloString" class="com.xyzlast.mvc.spring02.entities.HelloString"></bean>
</beans>
<?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" 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-3.2.xsd"> <context:component-scan base-package="com.xyzlast.mvc.spring02.controllers" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> <property name="order" value="1" /> </bean></beans>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/applicationContext.xml", "file:src/main/webapp/WEB-INF/controller-servlet.xml"}) @WebAppConfiguration public class HelloControllerTest { @Autowired private WebApplicationContext context; private MockMvc mvc; @Before public void setUp() { mvc = webAppContextSetup(context).build(); assertThat(mvc, is(not(nullValue()))); } @Test public void index() throws Exception { MvcResult result = mvc.perform(get("/index").param("name", "ykyoon")).andExpect(status().isOk()).andReturn(); Object model = result.getModelAndView().getModel().get("message"); assertThat(model, is(not(nullValue()))); String message = (String) model; assertThat(message.endsWith("ykyoon"), is(true)); } }
구성된 project의 pom.xml은 다음과 같습니다.
<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>com.xyzlast</groupId> <artifactId>mvc.spring02</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>mvc.spring02 Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>3.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>3.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.2.1.RELEASE</version> </dependency> </dependencies> <build> <finalName>mvc.spring02</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.0</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <plugin> <plugin> <groupId> org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> </plugin> </plugin> </plugins> </build>
</project>
1. A string beginning with a ‘/’ character and ending with a ‘/*’ suffix is used for path mapping.
: '/'로 시작하고, '/*'로 끝나는 mapping의 경우에는 path mapping을 따르게 된다.
(path mapping : servlet container에서 정의되어 있는 mapping입니다.)
2. A string beginning with a ‘*.’ prefix is used as an extension mapping.
: *. 으로 시작하는 mapping의 경우에는 확장 mapping을 따르게 된다. 우리가 기존에 사용하는 것을 확인했던 *.do와 같은 방법입니다.
3. A string containing only the ’/’ character indicates the "default" servlet of the application. In this case the servlet path is the request URI minus the context path and the path info is null.
: '/'만 사용하게 되는 경우, 이는 application의 'default' servlet을 의미한다. 이 경우에는 request URI와 context Path가 완전히 일치하게 되고, servlet path는 null이 됩니다.
4. All other strings are used for exact matches only.
: 모든 request uri가 완전히 일치하는 경우
<servlet-mapping> <servlet-name>controller</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<mvc:default-servlet-handler/>
<?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/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 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-3.2.xsd"> <context:component-scan base-package="com.xyzlast.mvc.spring02.controllers" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> <property name="order" value="1" /> </bean> <mvc:default-servlet-handler />
</beans>
@Configuration @EnableWebMvc @ComponentScan(basePackages = { "com.xyzlast.mvc.spring02.controllers" }) public class ControllerConfiguration extends WebMvcConfigurerAdapter { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Bean public InternalResourceViewResolver internalResourceViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/jsp/"); viewResolver.setSuffix(".jsp"); viewResolver.setOrder(1); return viewResolver; } }
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.xyzlast.bookstore.domain.config.DomainConfiguration</param-value> </context-param> <servlet> <servlet-name>controller</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.xyzlast.bookstore.web.configs.WebMvcContexConfiguration</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>controller</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
public class BookStoreWebApplicationInitializer implements WebApplicationInitializer { private static final String DISPATCHER_SERVLET_NAME = "dispatcher"; @Override public void onStartup(ServletContext servletContext) throws ServletException { registerListener(servletContext); registerDispatcherServlet(servletContext); addEncodingFilter(servletContext); } private void registerListener(ServletContext servletContext) { AnnotationConfigWebApplicationContext rootContext = createContext(DomainConfiguration.class); ContextLoaderListener contextLoaderListener = new ContextLoaderListener(rootContext); servletContext.addListener(contextLoaderListener); servletContext.addListener(new RequestContextListener()); } private void registerDispatcherServlet(ServletContext servletContext) { AnnotationConfigWebApplicationContext dispatcherContext = createContext(WebMvcContexConfiguration.class); ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(dispatcherContext)); dispatcher.setLoadOnStartup(1); dispatcher.addMapping("/"); } private AnnotationConfigWebApplicationContext createContext(final Class<?>... annotatedClasses) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(annotatedClasses); return context; } private void addEncodingFilter(ServletContext servletContext) { CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter(); encodingFilter.setEncoding("UTF-8"); encodingFilter.setForceEncoding(true); FilterRegistration.Dynamic encodingFilterRestriction = servletContext.addFilter("characterEncodingFilter", encodingFilter); encodingFilterRestriction.addMappingForUrlPatterns(null, false, "/*"); } }
public class WebXmlConfiguration extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { DomainConfiguration.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { WebApp01Configuration.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } @Override protected boolean isAsyncSupported() { return true; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter(); encodingFilter.setEncoding("UTF-8"); encodingFilter.setForceEncoding(true); return new Filter[] { encodingFilter }; } }