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();
}

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




<?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'들이 가지고 있는 의존성을 엿볼 수 있다.

입출력 관련 데이터를 관리할 때 가장 신경쓰이는 요소중에 하나가 생성(생성자, 생성일시)과 최종수정(최종수정자, 최종수정일)이다.
JPA에는 Auditing 이라고 하여, 인터페이스로 선언된 기능을 구현해두면 자동으로 엔티티에 필요한 데이터를 입력하여 등록하는 작업을 JPA에서 처리해주는 것이 가능하다.

현재 프로젝트에서 SpringBoot를 사용중인데 설정과 관련된 부분들을 JavaConfig로 처리하다보니 이와 관련된 정보가 없다.
생각보다 설정이 간단하다.

● AuditableEntity

@MappedSuperclass
@EntityListeners(value = { AuditingEntityListener.class })
public class AuditableEntity extends AbstractAuditable<Member, Long> {
    private static final long serialVersionUID = 359326673134570560L;
}

● Auditing target Entity

@Entity
public class AuditingTargetEntity extends AuditableEntity {

}

● SpringSecurityAuditorAware

현재 프로젝트는 스프링시큐리티를 이용하여 접근제어를 하고 있다.

JPA에서 사용할 Auditor(현재 작업중인 대상)에 관한 정보를 가져오는 과정을 정의한 AuditorAware<T> 인터페이스를 이용하여 스프링시큐리티의 SecurityContextHolder에서 관련 사용자 정보를 가져오도록 구현한다.

public class SpringSecurityAuditorAware implements AuditorAware<Member> {
    @Override
    public Member getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (null == authentication || !authentication.isAuthenticated()) {
            return null;
        }
        return (Member) authentication.getPrincipal();
    }
}

● AuditingConfig 선언

@Configuration
@EnableJpaAuditing
public class AuditingConfig {
    @Bean
    SpringSecurityAuditorAware auditorAware() {
        return new SpringSecurityAuditorAware();
    }
}

● 정리

참 쉽죠?

● 참고


애플리케이션이 초기에 가동될 때 필요한 데이터를 입력하는 작업은 필수적인 기초작업이다. ㅡ_-)>
애플리케이션 개발이 완료된 단계에서는 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이 실행될 것이다(아마도…?).

스프링 부트 참고가이드

작성자

Phillip Webb, Dave Syer, Josh Long, Stéphane Nicoll, Rob Winch, Andy Wilkinson, Marcel Overdijk, Christian Dupuis, Sébastien Deleuze

1.2.0.BUILD-SNAPSHOT

Copyright © 2013-2014

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.


  • 목차

I. 스프링부트 문서

1. 문서에 대해

2. 조력 구

3. 첫걸음

4. 스프링부트 작동

5. 스프링부트 기능 학습

6. 출시

7. 고급기능들

II. 시작

8. 스프링부트 시작

9. 시스템 요구사항

9.1. 서블릿 컨테이너

10. 스프링부트 설치

10.1. 자바 개발자를 위한 설치법 소개

10.1.1. 메이븐 기반 설치법

10.1.2. 그레들 기반 설치법

10.2. 스프링부트 CLI를 이용한 설치

10.2.1. 규정 설치법

10.2.2. GVM을 이용한 설치법

10.2.3. OSX Homebrew 설치법

10.2.4. 커맨드라인 완성

10.2.5. 스프링 CLI로 빠르게 시작하는 예제

10.3. 이전버전의 스프링부트로부터 업그레이드

11. 초기 스프링부터 애플리케이션 개발

11.1. POM 파일 생성

11.2. classpath 의존성 추가

11.3. 코드 작성

11.3.1. @RestController와 @RequestMapping 에노테이션

11.3.2. @EnableAutoConfiguration 애노테이션

11.3.3. “main” 메서드

11.4. 예제 실행

11.5. 실행가능한 jar 생성

12. 다음 읽을거리

III. 스프링부트 사용

13. 시스템 빌드

13.1. 메이븐

13.1.1. 스프링부트 스타터 부모 상속

13.1.2. 부모 POM 없이 스프링부트 사용

13.1.3. 자바 버전 변경

13.1.4. 스프링부트 메이븐 플러그인 사용

13.2. 그레들

13.3. 앤트

13.4. 스프링부트 스타터 POM 목록

14. 코드 구성

14.1. ‘default’ 패키지 이용

14.2. 메인 애플리케이션 클래스 위치

15. 설정 클래스들

15.1. 추가된 설정 클래스들 불러오기

15.2. XML 설정 불러오기

16. 자동설정(Auto-configuration)

16.1. 점진적으로 자동설정을 대체

16.2. 특정한 자동설정을 비활성화

17. 스프링 빈과 의존성 주입

18. @SpringBootApplication애노테이션 사용

19. 애플리케이션 실행

19.1. IDE에서 실행

19.2. 패키징된 애플리케이션 실행

19.3. 메이븐 플러그인을 이용

19.4. 그레들 플러그인을 이용

19.5. 핫스와핑

20. 출시를 위해서 애플리케이션 패키징

21. 다음 읽을거리

IV. 스프링부트 기능

22. 스프링애플리케이션

22.1. 배너 수정

22.2. 스프링애플리케이션 수정

22.3. 플루언트 빌더 API

22.4. 애플리케이션 이벤트와 리스너

22.5. 웹 환경

22.6. 커맨드라인러너 사용

22.7. 애플리케이션 종료

23. 외부설정

23.1. 커맨드라인 속성 접근

23.2. 애플리케이션 속성 파일들

23.3. 프로파일 지정 속성들

23.4. 속성 플레이스홀더(placeholder)

23.5. Properties 대신 YAML 사용

23.5.1. YAML 읽어오기

23.5.2. 스프링 환경에서 속성들을 YAML로 노출

23.5.3. 다중 프로파일 YAML 문서

23.5.4. YAML 의 단점

23.6. 타입세이프 설정 속성들

23.6.1. 느슨한 연결

23.6.2. @ConfigurationProperties 검증

24. 프로파일

24.1. 활성프로파일 추가

24.2. 프로파일 작성방법

24.3. 프로파일 상세 설정파일

25. 로깅

25.1. 로그 형식

25.2. 콘솔 출력

25.3. 파일 출력

25.4. 로그 레벨

25.5. 로그 설정 변경

26. 웹 애플리케이션 개발

26.1. ‘Spring Web MVC framework’

26.1.1. Spring MVC 자동설정

26.1.2. HttpMessageConverter

26.1.3. MessageCodesResolver

26.1.4. ```Static Content````

26.1.5. Template engines

26.1.6. 오류 제어, 웹스피어

애플리케이션 서버에서 오류 제어

26.2. JAX-RS 그리고 Jersey

26.3. 내장형 서블릿 컨테이너 지원

26.3.1. 서블릿 그리고 필터

26.3.2. EmbeddedWebApplicationContext

26.3.3. 내장형 서블릿 컨테이너 변경

변경 작성방법

ConfigurableEmbeddedServletContainer 직접 변경

26.3.4. JSP 제약사항

27. 보안

28. SQL 데이터베이스 작업

28.1. 데이터베이스 설정

28.1.1. 내장형 데이터베이스 지원

28.1.2. 외부 데이터베이스 연결

28.1.3. JNDI 데이터베이스 연결

28.2. JdbcTemplate 사용

28.3. JPA 그리고 ‘Spring Data’

28.3.1. 엔티티 클래스

28.3.2. Spring Data JPA 레파지토리

28.3.3. JPA 데이터베이스 생성 및 삭제

29. NoSQL 기술 작업

29.1. 레디스Redis

29.1.1. 레디스 연결

29.2. 몽고DBMongoDB

29.2.1. 몽고DB 연결

29.2.2. MongoTemplate

29.2.3. Spring Data 몽고DB 레파지토리

29.3. Gemfire

29.4. Solr

29.4.1. Solr 연결

29.4.2. Spring Data Elasticsearch 레파지토리

30. 메시징

30.1. JMS

30.1.1. HornetQ 지원

30.1.2. ActiveMQ 지원

30.1.3. JNDI ConnectionFactory 사용

30.1.4. 메시지 전송

30.1.5. 메시지 수신

31. 이메일 전송

32. JTA를 이용한 트랜잭션 분산

32.1. Atomikos 트랜잭션 매니저 사용

32.2. Bitronix 트랜잭션 매니저 사용

32.3. Java EE 에서 관리하는 트랜잭션 매니저 사용

32.4. XA 그리고 non-XA JMS 연결 혼합

32.5. 대안적인 내장형 트른잭션 매니저 지원

33. 스프링 통합

34. JMX를 통해서 모니터링과 관리

35. 테스팅

35.1. 테스트 스코프 의존성

35.2. 스프링 애플리케이션 테스트

35.3. 스프링부트 애플리케이션 테스트

35.3.1. 스팍Spock을 사용하여 스프링 부트 애플리케이션 테스트

35.4. 테스트 유틸리티

35.4.1. ConfigFileApplicationContextInitializer

35.4.2. EnvironmentTestUtils

35.4.3. OutputCapture

35.4.4. TestRestTemplate

36. 자동설정으로 개발와 상황에 맞춰 사용

36.1. 자동설정 빈 이해

36.2. 자동설정 위치 후보지

36.3. 상황 애노테이션

36.3.1. 클래스 상황

36.3.2. 빈Bean 상황

36.3.3. 리소스 상황

36.3.4. 웹 애플리케이션 상황

36.3.5. SpEL 표현식 상황

37. 웹소켓

38. 다음 읽을거리

V. 스프링부트 액츄에에터: 출시준비 기능들

39. 사용가능한 출시준비 기능들

40. 엔드포인트

40.1. 엔드포인트 변경

40.2. 상태 정보 변경

40.3. 애플리케이션 정보 안내 변경

40.3.1. 빌드 시간에 관한 속성 확장 자동화

메이븐을 이용하여 속성 확장 자동화

그레들을 이용하여 속성 확장 자동화

40.3.2. 깃 커밋 정보

41. HTTP를 통해서 모니터링 및 관리

41.1. 세밀한 엔드포인트 노출

41.2. 관리 서버컨텍스트패스 변경

41.3. 관리 서버포트 변경

41.4. 관리 서버주소 변경

41.5. HTTP 엔드포인트 비활성화

41.6. 상태 엔드포인트에 대한 무기명 접근 제한

42. JMX를 통한 모니터링 및 관리

42.1. MBean 이름 변경

42.2. JMX 엔드포인트 비활성화

42.3. JMX용 Jolokia를 HTTP를 통해서 사용

42.3.1. Jolokia 변경

42.3.1. Jolokia 비활성화

43. 리모트쉘을 사용하여 모니터링 및 관리

43.1. 리모트쉘 연결

43.1.1. 리모트쉘 자격credentials

43.2. 리모트쉘 확장

43.2.1. 리모트쉘 명령어

43.2.2. 리모트쉘 플러그인

44. 측정

44.1. 데이터소스 측정

44.2. 측정 기록

44.3. 공개 측정 추가

44.4. 측정 레파지토리

44.5. Coda Hale 측정

44.6. 메시지 채널 통합

45. 오디팅auditing

46. 추적Tracing

46.1. 추적 변경

47. 프로세스 모니터링

47.1. 설정 확장

47.2. 작성

48. 다음 읽을거리

VI. 클라우드 배포

49. Cloud Foundry

49.1. 서비스 연결

50. Heroku

51. CloudBees

52. Openshift

53. Google App Engine

54. 다음 읽을거리

VII. 스프링부트 CLI

55. CLI 설치

56. CLI 사용

56.1. CLI를 이용해서 애플리케이션 실행

56.2. CLI에 의존성 추가

56.2.1. “grab” 의존성 추정

56.2.2. “grab” 협력 추정

“grab” 메타데이터 변경

56.2.3. 기본 불러오기 문장

56.2.4. 자동 main 메서드

56.3. 코드 테스트

56.4. 다양한 소스파일을 가진 애플리케이션

56.5. 애플리케이션 패키징

56.6. 새로운 프로젝트 준비

56.7. 내장형 쉘 사용

57. 그루비 빈즈 DSL을 통해서 애플리케이션 개발

58. 다음 읽을거리

VIII. 빌드툴 플러그인

59. 스프링부트 메이븐 플러그인

59.1. 플러그인 추가

59.2. 실행가능한 jar 와 war 파일 패키징

60. 스프링부트 그레들 플러그인

60.1. 플러그인 추가

60.2. 버전 없이 의존성 정의

60.2.1. 버전 관리 변경

60.3. 기본적인 배제 원칙

60.4. 실행가능한 jar 와 war 파일 패키징

60.5. 프로젝트 바로 실행

60.6. 스프링부트 플러그인 설정

60.7. 리패키징 설정

60.8. 변경된 그레들 설정으로 리패키징

60.8.1. 설정 사항

60.9. 그레들 플러그인의 동작방식 이해

60.10. 그레들을 이용해서 메이븐 레파지토리에 아티팩트 배포

60.10.1. 그레들 설정을 이용한 상속적 의존성 관리 POM 제작

60.10.2. 그레들 설정을 이용한 imports 의존성 관리 POM 제작

61. 다른 빌드 지원 시스템 지원

61.1. 리패키징 아카이브

61.2. 내포된 라이브러리

61.3. 메인 클래스 탐색

61.4. repackage 구현 예제

62. 다음 읽을 거리

IX. ‘어떻게How-to’ 가이드

63. 스프링부트 애플리케이션

63.1. 자동설정 문제해결

63.2. 시작 전 Environment 혹은 ApplicationContext 변경

63.3. ApplicationContext 계층 빌드(부모 혹은 루트 컨텍스트 추가)

63.4. non-web 애플리케이션 생성

64. 속성 및 설정

64.1. 스프링애플리케이션의 설정 확장

64.2. 애플리케이션의 외부 속성 위치 변경

64.3. ‘간략한’ 커맨드라인 인자 사용

64.4. 외부 속성을 YAML로 정의

64.5. 활성 스프링 프로파일 설정

64.6. 환경 의존적 설정 변경

64.7. 외부 속성들의 빌트인 항목 살펴보기

65. 내장형 서블릿 컨테이너

65.1. Servlet, Filter 혹은 ServletContextListener 를 애플리케이션에 추가

65.2. HTTP 포트 변경

65.3. HTTP 포트를 지정하지 않고 무작위로 사용

65.4. 실행시 HTTP Port 살펴보기

65.5. SSL 설정

65.6. 톰캣 설정

65.7. 톰캣의 다중커넥터 활성화

65.8. 톰캣을 프론트엔드 프록시 서버로 사용

65.9. 톰캣 대신 제티 사용

65.10. 제티 설정

65.11. 톰캣 대신 언더토우Undertow 사용

65.12. 언더토우 설정

65.13. 톰캣 7 사용

65.14. 제티 8 사용

65.15. @ServerEndpoint를 사용해서 웹소켓 엔드포인트 생성

66. 스프링 MVC

66.1. JSON REST 서비스 작성

66.2. XML REST 서비스 작성

66.3. Jackson ObjectMapper 변경

66.4. @ResponseBody 렌더링 변경

66.5. Multipart 파일 업로드 제어

66.6. Spring MVC DispatcherServlet 끄기

66.7. 기본 MVC 설정 끄기

66.8. ViewResolver 변경

67. 로깅

67.1. 로깅을 위한 Logback 설정

67.2. 로깅을 위한 Log4j 설정

68. 데이터 접근

68.1. 데이터소스 설정

68.2. 복수 데이터소스 설정

68.3. 스프링 데이터 레파지토리 사용

68.4. 스프링 설정으로 부터 @Entity 정의 분리

68.5. JPA 속성 설정

68.6. EntityManagerFactory 변경

68.7. 복수 엔티티매니저 사용

68.8. 전통적인 persistence.xml 사용

68.9. 스프링데이터 JPA와 몽고 레파지토리 사용

69. 데이터베이스 초기화

69.1. JPA 사용하여 데이터베이스 초기화

69.2. Hibernate를 사용하여 데이터베이스 초기화

69.3. Spring JDBC를 사용하여 데이터베이스 초기화

69.4. 스프링 배치 데이터베이스 초기화

69.5. 고차원 데이터베이스 마이그레이션 도구 사용

69.5.1. 시작시 Flyway 실행하여 데이터베이스 마이그레이션

69.5.2. 시작시 Liquibase를 실행하여 데이터베이스 마이그레이션

70. 배치 애플리케이션

70.1. 시작시 스프링 배치 작업 실행

71. 액츄에이터Actuator

71.1. 액츄에이터 엔드포인트의 주소 혹은 HTTP 포트 변경

71.2. ‘whitelabel’ 오류 페이지 변경

72. 시큐리티

72.1. 스프링부트 시큐리티 설정 끄기

72.2. AuthenticationManager를 변경하고 사용자 계정 추가

73. 핫스와핑

73.1. 정적컨텐츠 다시 읽기

73.2. 컨테이너 재시작없이 타임리프Thymeleaf 템플렛 다시 읽기

73.3. 컨테이너 재시작없이 프리마크FreeMarker 템플렛 다시 읽기

73.4. 컨테이너 재시작없이 그루비Groovy 템플렛 다시 읽기

73.5. 컨테이너 재시작없이 벨로시티Velocity 템플렛 다시 읽기

73.6. 컨테이너 재시작없이 자바 클래스 다시 읽기

73.6.1. 메이븐을 이용한 Spring Loaded 설정

73.6.2. 그레들과 IntelliJ를 이용한 Spring Loaded 설정

74. 빌드

74.1. 메이븐으로 의존성 버전 변경

74.2. 메이븐으로 실행가능한 JAR 생성

74.3. 추가적인 실행가능한 JAR 생성

74.4. 실행가능한 jar 동작에 필요한 지정된 라이브러리 추출

74.5. 배제를 통한 실행할 수 없는 JAR 생성

74.6. 메이븐을 이용해서 스프링부트 애플리케이션 원격 디버그 시작

74.7. 그레들을 이용해서 스프링부트 애플리케이션 원격 디버그 시작

74.8. 앤트를 이용해서 실행가능한 아카이브 빌드

75. 전통적 배포

75.1. 배포가능한 war 파일 생성

75.2. 오래된 서블릿 컨테이너에 배포가능한 war 파일 생성

75.3. 기존의 애플리케이션을 스프링부트로 변환

75.4. 웹로직을 위한 war 배포

75.5. 오래된(Servlet 2.5) 컨테이너에 war 배포

X. 부록

A. 일반적인 애플리케이션 속성

B. 메타데이터 설정

B.1. 메타데이터 형식

B.1.1. 그룹 어트리뷰트

B.1.2. 속성 어트리뷰트

B.1.3. 반복적인 메타데이터 아이템

B.2. 애노테이션 프로레서를 사용하여 메타데이터 생성

B.2.1. 내부 속성

B.2.2. 추가적인 메타데이터 추가

C. 자동설정 클래스

C.1. “spring-boot-autoconfigure” 모듈

C.2. “spring-boot-actuator” 모듈

D. 실행가능한 jar 형식

D.1. 내부 JARs

D.1.1. 실행가능한 jar 파일 구조

D.1.2. 실행가능한 war 파일 구조

D.2. 스프링부트의 “JarFile” 클래스

D.2.1. 표준 자바 “JarFile” 의 호환성

D.3. 실행가능한 jars 실행

D.3.1. 매니페스트 실행

D.3.2. 아카이브 확장

D.4. PropertiesLauncher 기능들

D.5. 실행가능한 jar 제약사항

D.5.1. Zip 엔트리 압축

D.5.2. System ClassLoader

D.6. 단독 jar 솔루션 대안


+ Recent posts