스프링부트 기본배너

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   v1.3.0.BUILD-SNAPSHOT

스프링애플리케이션에서 배너와 관련된 설정

Class 정의

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MySpringConfiguration.class);
    app.setShowBanner(false);
    app.run(args);
}

외부설정 정의

spring.main.show_banner=false

배너 변경

SpringBoot가 실행될때 배너를 보여주게 되는데, 이를 변경하고 싶을 때는

  • banner.txt 생성
    • banner.txt 파일을 root classpath(src/main/resource)에 위치
    • 별도의 위치에 두고 외부설정(properties or yaml)으로 banner.location 을 선언하고 위치를 정의

배너변경 결과

11:26:18,781 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
11:26:18,783 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Naming appender as [STDOUT]
11:26:18,921 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - This appender no longer admits a layout as a sub-component, set an encoder instead.
11:26:18,921 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.
11:26:18,921 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - See also http://logback.qos.ch/codes.html#layoutInsteadOfEncoder for details
11:26:18,924 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.thymeleaf] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.springframework.web] to DEBUG
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.springframework] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.hibernate] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [org.eclipse.jetty] to INFO
11:26:18,931 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [jndi] to INFO
11:26:18,934 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Setting level of logger [ROOT] to DEBUG
11:26:18,937 |-INFO in ch.qos.logback.classic.gaffer.ConfigurationDelegate@1c4e4471 - Attaching appender named [STDOUT] to Logger[ROOT]

  _   _     U  ___ u  _   _   U _____ u __   __  __  __    U  ___ u  _   _     
 |'| |'|     \/"_ \/ | \ |"|  \| ___"|/ \ \ / /U|' \/ '|u   \/"_ \/ | \ |"|    
/| |_| |\    | | | |<|  \| |>  |  _|"    \ V / \| |\/| |/   | | | |<|  \| |>   
U|  _  |u.-,_| |_| |U| |\  |u  | |___   U_|"|_u | |  | |.-,_| |_| |U| |\  |u   
 |_| |_|  \_)-\___/  |_| \_|   |_____|    |_|   |_|  |_| \_)-\___/  |_| \_|    
 //   \\       \\    ||   \\,-.<<   >>.-,//|(_ <<,-,,-.       \\    ||   \\,-. 
(_") ("_)     (__)   (_")  (_/(__) (__)\_) (__) (./  \.)     (__)   (_")  (_/  


11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [servletConfigInitParams] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [servletContextInitParams] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [jndiProperties] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
11:26:19 DEBUG o.s.w.c.s.StandardServletEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence

위와 같은 결과를 볼 수 있다.

참고사항

SpringBoot

이미지 to ASCII CODE image


1. 문제발생

스프링부트 1.1.9.RELEASE 버전을 사용할 때, 정상적으로 동작하던 파일업로드 기능이 스프링부트 1.2.1로 업그레이드하면서 동작하지 않는 문제가 발생했다. 다음과 같은 형태로 multipartConfigElement와 multipartResolver 빈을 설정해두었고, ajax 파일업로드를 처리하기 위한 목적으로 jQuery Form plugin(https://github.com/malsup/form)을 사용하고 있다.

@Bean
MultipartConfigElement MultipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();

    factory.setMaxFileSize(getMaxUploadFileSize());
    factory.setMaxRequestSize(getMaxUploadFileSize());

    return factory.createMultipartConfig();
}

@Bean
public CommonsMultipartResolver multipartResolver() {
    return new CommonsMultipartResolver();
}

컨트롤러는 다음과 같이 메서드를 구현했다.

@RequestMapping(value = "/file-upload", method = RequestMethod.POST)
    public ModelAndView fileUpload(@RequestParam(value = "file") MultipartFile file) throws IOException {
}

다음과 같은 예외가 발생한다.

org.springframework.web.bind.MissingServletRequestParameterException: Required MultipartFile parameter 'file' is not present
    at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValue(RequestParamMethodArgumentResolver.java:253) ~[spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:94) ~[spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:77) ~[spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162) ~[spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:129) ~[spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) ~[spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777) ~[spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706) ~[spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943) [spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877) [spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966) [spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868) [spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) [javax.servlet-api-3.1.0.jar:3.1.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842) [spring-webmvc-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [javax.servlet-api-3.1.0.jar:3.1.0]
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:800) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:288) [spring-boot-actuator-1.2.0.RELEASE.jar:1.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:100) [spring-boot-actuator-1.2.0.RELEASE.jar:1.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration$MetricsFilter.doFilterInternal(MetricFilterAutoConfiguration.java:90) [spring-boot-actuator-1.2.0.RELEASE.jar:1.2.0.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577) [jetty-security-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1125) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515) [jetty-servlet-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1059) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.Server.handle(Server.java:497) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:248) [jetty-server-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540) [jetty-io-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:620) [jetty-util-9.2.4.v20141103.jar:9.2.4.v20141103]
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:540) [jetty-util-9.2.4.v20141103.jar:9.2.4.v20141103]
    at java.lang.Thread.run(Thread.java:745) [na:1.7.0_67]

컨트롤러에서 파라메터로 지정한 @RequestParam(value = "file") MultipartFile file 받지 못하는 증상이 생긴 것이다. 이걸 보고 계속 헛다리를 짚은 것이 jetty 9.x 를 사용해서 그런 것인가 하는 말도 안되는 추측을 하면서 문제의 해결방법에서 멀어지는 상황에 놓이게 된다.

ㅡ_-);; 이때 조금만 더 곰곰히 생각해봤으면 어땠을까 하지만, 정작 문제가 발생했을 때는 ‘이런 상황이 왜 생기는거야?’라는 해결책을 찾는데에만 함몰되어 있어다.

2. 문제해결방법

난 이 문제가 Jetty에 문제이거나 스프링부트 1.2.0 의 문제라고 생각했다. 그래서 얼마전 출시된 1.2.1 로 업그레이드를 했는데도 동일한 문제가 발생하는 것을 접하면서 답답함을 금치못했다. 그런데, 딱히 이와 관련된 버그나 이슈는 없었다. 왜?
그래서 인터넷 검색을 들어간다.

이럴 떄는 발생한 예외로그를 그대로 복사해서 놓는 것도 하나의 방법이라면 방법이다.
세계 어디에서나 같은 개발환경을 기반으로 개발하는 개발자들이 겪는 오류는 같다. ㅎㅎ

그러다가 스택오버플로우에서 내가 겪고 있는 상황과 같은 문제를 겪고 있는 어느 개발자의 질문을 발견한다.
Spring mvc: HTTP Status 400 - Required MultipartFile parameter ‘file’ is not present - Stackoverflow

그 질문에 달린 답변은 간단했다.

<bean id="multipartResolver" class="**org.springframework.web.multipart.commons.CommonsMultipartResolver**" />

<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />

으로 변경하면 된다고 한다. 그래서 그렇게 했다.

@Bean
public MultipartResolver multipartResolver() {
    return new StandardServletMultipartResolver();
}

그랬더니 된다. 됐다. 문제해결!

3. 문제원인

스프링부트 1.2.0.RELEASE부터 서블릿 3.1이 적용되었다.

  • Spring Boot 1.2.0 released

    Spring Boot now uses Servlet 3.1 when running with an embedded servlet container. Tomcat 8, Jetty 9 and Undertow 1.1 are all supported options. In addition, WebSocket support has been improved and is now automatically configured for all supported servers. If you need to stick to Servlet 3.0, Tomcat 7 and Jetty 8 are still supported.

서블릿 3.1….
이 부분이 중요하다. +_+)
문제해결하는데 사용된 org.springframework.web.multipart.support.StandardServletMultipartResolver을 열어보면 다음과 같은 주석이 있다.

/**
 * Standard implementation of the {@link MultipartResolver} interface,
 * based on the Servlet 3.0 {@link javax.servlet.http.Part} API.
 * To be added as "multipartResolver" bean to a Spring DispatcherServlet context,
 * without any extra configuration at the bean level (see below).
 *
 * <p><b>Note:</b> In order to use Servlet 3.0 based multipart parsing,
 * you need to mark the affected servlet with a "multipart-config" section in
 * {@code web.xml}, or with a {@link javax.servlet.MultipartConfigElement}
 * in programmatic servlet registration, or (in case of a custom servlet class)
 * possibly with a {@link javax.servlet.annotation.MultipartConfig} annotation
 * on your servlet class. Configuration settings such as maximum sizes or
 * storage locations need to be applied at that servlet registration level;
 * Servlet 3.0 does not allow for them to be set at the MultipartResolver level.
 *
 * @author Juergen Hoeller
 * @since 3.1
 */

정리하면,

  • Servlet 3.0 Part API를 바탕으로 한 MultipartResolver 인터페이스 구현체
  • bean 을 선언할 때 별다른 확장설정을 하지 않고 “multipartResolver”로 선언
  • multipart와 관련된 업로드 파일 최대크기, 저장위치 등 설정을 하고 싶다면
    • web.xml에서 하거나
    • Servlet 등록하는 과정에서 MultipartConfigElement을 선언하면서 설정
    • @MultipartConfig라는 애노테이션 사용
  • Servlet 3.0 에서는 MultipartResolver 레벨에서 설정하는 것을 허용치 않는다.

이다. 결국은

@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();

    factory.setMaxFileSize(getMaxUploadFileSize());
    factory.setMaxRequestSize(getMaxUploadFileSize());

    return factory.createMultipartConfig();
}

@Bean
public MultipartResolver multipartResolver() {
    return new StandardServletMultipartResolver();
}

의 형태로 정의하면 된다는 것이다. ㅇㅋ~


  1. 2blikecaesar 2016.04.11 15:56

    너무 감사합니다!
    덕분에 해결했네요^^

  2. lebe 2016.07.06 14:53

    정보 감사합돠~~

  3. 초보 개발자 2017.08.22 23:00

    와 정말 감사드립니다. 해당 버그는 찾기도 어렵고... 한시간 정도 해맸는데 감사드립니다.
    좋은 하루 되세요

  4. 황인빈 2019.08.14 17:11

    많은 도움이 되었습니다.



<?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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.0.BUILD-SNAPSHOT</version>
    </parent>

    <!-- Additional lines to be added here... -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <!-- (you don't need this if you are using a .RELEASE version) -->
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
            <snapshots><enabled>true</enabled></snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <url>http://repo.spring.io/snapshot</url>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <url>http://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>
</project>

위의 pom.xml을 만들고나서

$ mvn dependency:tree

를 실행하면,

honeymon@test $ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building myproject 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-dependency-plugin:2.9:tree (default-cli) @ myproject ---
[INFO] com.example:myproject:jar:0.0.1-SNAPSHOT
[INFO] \- org.springframework.boot:spring-boot-starter-web:jar:1.2.0.BUILD-SNAPSHOT:compile
[INFO]    +- org.springframework.boot:spring-boot-starter:jar:1.2.0.BUILD-SNAPSHOT:compile
[INFO]    |  +- org.springframework.boot:spring-boot:jar:1.2.0.BUILD-SNAPSHOT:compile
[INFO]    |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.2.0.BUILD-SNAPSHOT:compile
[INFO]    |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.2.0.BUILD-SNAPSHOT:compile
[INFO]    |  |  +- org.slf4j:jcl-over-slf4j:jar:1.7.7:compile
[INFO]    |  |  |  \- org.slf4j:slf4j-api:jar:1.7.7:compile
[INFO]    |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.7:compile
[INFO]    |  |  +- org.slf4j:log4j-over-slf4j:jar:1.7.7:compile
[INFO]    |  |  \- ch.qos.logback:logback-classic:jar:1.1.2:compile
[INFO]    |  |     \- ch.qos.logback:logback-core:jar:1.1.2:compile
[INFO]    |  \- org.yaml:snakeyaml:jar:1.14:runtime
[INFO]    +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.2.0.BUILD-SNAPSHOT:compile
[INFO]    |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.0.15:compile
[INFO]    |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.0.15:compile
[INFO]    |  +- org.apache.tomcat.embed:tomcat-embed-logging-juli:jar:8.0.15:compile
[INFO]    |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.0.15:compile
[INFO]    +- com.fasterxml.jackson.core:jackson-databind:jar:2.4.4:compile
[INFO]    |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.4.4:compile
[INFO]    |  \- com.fasterxml.jackson.core:jackson-core:jar:2.4.4:compile
[INFO]    +- org.hibernate:hibernate-validator:jar:5.1.3.Final:compile
[INFO]    |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO]    |  +- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile
[INFO]    |  \- com.fasterxml:classmate:jar:1.0.0:compile
[INFO]    +- org.springframework:spring-core:jar:4.1.3.RELEASE:compile
[INFO]    +- org.springframework:spring-web:jar:4.1.3.RELEASE:compile
[INFO]    |  +- org.springframework:spring-aop:jar:4.1.3.RELEASE:compile
[INFO]    |  |  \- aopalliance:aopalliance:jar:1.0:compile
[INFO]    |  +- org.springframework:spring-beans:jar:4.1.3.RELEASE:compile
[INFO]    |  \- org.springframework:spring-context:jar:4.1.3.RELEASE:compile
[INFO]    \- org.springframework:spring-webmvc:jar:4.1.3.RELEASE:compile
[INFO]       \- org.springframework:spring-expression:jar:4.1.3.RELEASE:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.397s
[INFO] Finished at: Mon Jan 05 23:11:55 KST 2015
[INFO] Final Memory: 12M/152M
[INFO] ------------------------------------------------------------------------
honeymon@test $

의 형태로 추가된 의존성을 확인할 수 있다. WEB MVC를 사용하여 웹 애플리케이션을 만드는데 필요한 스프링부트의 기본적인 요소들을 살펴볼 수 있다. 스프링부트 web 에서는 내장형 컨테이너로 톰캣을 기본탑재하고 있다.

요런식으로 스프링부트에 추가되는 'Starter POMs'들이 가지고 있는 의존성을 엿볼 수 있다.

애플리케이션이 초기에 가동될 때 필요한 데이터를 입력하는 작업은 필수적인 기초작업이다. ㅡ_-)>
애플리케이션 개발이 완료된 단계에서는 Flyway를 이용해서 DB 마이그레이션을 진행하면 되지만, 한창 개발중인 와중인지라 엔티티가 변경될 가능성도 높고해서 생성에 필요한 부분들을 당장 Flyway로 적재할 필요가 없어서 설정만 해두었다.

SpringBoot(Hibernate + Spring Data JPA)를 활용할 때 데이터베이스를 초기화 하는 방법은

  • JPA를 이용해서 데이터베이스를 초기화
    • spring.jpa.generate-ddl (boolean) switches the feature on and off and is vendor independent.
    • spring.jpa.hibernate.ddl-auto (enum) is a Hibernate feature that controls the behavior in a more fine-grained way. See below for more detail.
  • Hibernate를 이용해서 데이터베이스 초기화
    • import.sql를 루트클래스 경로에 놓아두면 시작시 실행된다.
  • Spring JDBC를 이용해서 데이터베이스 초기화
    • 설정파일에서 spring.datasource.initialize를 추가해두고
    • schema.sql 를 사용하면 JPA에서 설정해두면 JPA에서 테이블 생성할 때 schema.sql에 동일한 테이블이 있으면 문제가 생긴다.
    • ddl-auto=create-drop 으로 설정해두고 새로운 기초데이터를 넣기 위한data.sql을 사용할 수 있다.

      지금 사용하는 프로젝트에서는 spring.datasource.initialize설정을 통해서 data.sql을 이용하는 방법이 편해보여서 그렇게 했다.

  • Spring Batch database를 이용한 초기화
    • 가장 범용적으로 사용되는 SQL 초기화 스크립트를 이용한 방법이다.
  • 혹은 높은 수준의 데이터베이스 마이그레이션 도구를 사용
    • Flyway
    • Liquibase

Spring JDBC를 이용해서 데이터베이스 초기화 설정방법

spring:
  datasource:
    initialize: true
    driverClassName: org.h2.Driver
    url: jdbc:h2:file:./h2database;AUTO_SERVER=TRUE
    username: user
    password:

위와 같이 설정해두고
project/java/main/resource 경로에 data.sql을 넣어두어 프로젝트 초기화 데이터를 넣어두었다. 최초 실행시 initialize true로 두고 구동하면 JPA에 의해 테이블이 생성된 후에 data.sql이 실행되면서 데이터가 입력된다. 이후에는 initialize false로 변경하면 된다.

혹은,

spring:
  datasource:
    initialize: false

로 둔 상태에서 ./gradlew bootRepackage로 실행가능한 아카이브 파일로 만들어두고

$ java -jar archive.war --spring.datasource.initialize=true

로 실행하면 최초에 data.sql이 실행될 것이다(아마도…?).

+ Recent posts