현재 회사에서는 Spring Boot + AWS Beanstalk 조합으로 서비스를 운영하고 있다. 빈즈톡을 이용하여 로드밸런싱 처리를 할 때 nginx 를 사용하고자 하는 경우 빈즈톡에서 사용하는 인스턴스 내에서 nginx 설정을 변경해도 반영되지 않는 문제가 발생한다.

AWS 의 개발가이드가 완전히 한국어화가 되지 않았기 때문에 찾아보기도 이해하기도 쉽지 않다.

이에 프로젝트 내에 빈즈톡과 관련된 설정파일을 두어 배포시 빈즈톡 내에 위치한 nginx 설정 파일 /etc/nginx/conf.d 에 위치하도록 할 수 있다.

적용방법

  1. 프로젝트 내에 aws 설정파일(.conf) 를 둘 디렉토리를 지정하라.

    • ex) $ mkdir boot-spring-boot/aws

  2. boot-spring-boot/aws 하위에 .ebextensions/nginx/conf.d/elasticbeanstalk 디렉토리를 생성한다.

엘라스틱 빈즈톡의 기본 nginx 를 확장하기 위해서는 .ebextensions/nginx/conf.d/ 폴더에 .conf 설정파일을 추가하면 된다. 그러면 자동으로 엘라스틱 빈즈톡의 nginx 구성에 .ebextensions/nginx/conf.d/ 에 있는 .conf 파일들이 추가된다.

~/workspace/boot-spring-boot/aws/
`-- .ebextensions
    `-- nginx
        `-- conf.d
            |-- elasticbeanstalk
            |   `-- 00_server.conf
            `-- boot-spring-boot.conf

conf.d 폴더에 있는 .conf 파일들은 기본 구성의 http 블럭에 포함되며, conf.d/elasticbeanstalk 폴더에 있는 파일의 내용은 http 블록 내에 server 블록에 포함된다.

엘라스틱 빈즈톡의 기본 nginx 구성을 완전히 덮어쓰기 위해서는 .ebextensions/nginx/nginx.conf 으로 구성을 추가하면 된다.

~/workspace/boot-spring-boot/aws/
`-- .ebextensions
    `-- nginx
        `-- nginx.conf

nginx 에서 파일업로드 사이즈를 10M로 올려보자.

  1. 프로젝트 내에 aws/.ebextensions/nginx/conf.d/elasticbeanstalk 폴더를 생성

  2. 00_server.conf 파일 생성

  3. 00_server.conf 내에 client_max_body_size 10M; 를 추가한다.

  4. 스프링 부트 패키징 시 .ebextensions/nginx/conf.d/elasticbeanstalk 폴더가 포함되어 배포되도록 만든다.

build.gradle 에 다음과 같은 태스크를 만들어 실행하면 손쉽게 가능해진다.

task awsEBZip(type: Zip, dependsOn: 'bootRepackage') {
from '../aws' // .ebextensions 위치
from 'build/libs/' + jar.archiveName bootRepackage 된 파일 위치
baseName = 'boot-spring-boot-' + jar.version // 재생성한 파일명
}


AWS Beanstalk configuration with SpringBoot

AWS 빈즈토크(Beanstalk, 줄여서 빈즈톡)에 스프링 부트 애플리케이션을 배포하는 것과 관련해서는 위의 내용을 참조하기 바란다.


결론부터 내리자면

시스템에서 환경변수는 언더코어('_')를 사용하는데 스프링 부트 속성은 마침표('.')를 사용한다. 이를 궁휼히 여긴 스프링부트 팀에서 언더코어로 정의된 환경변수 중 스프링 부트 속성들에 대해서 주입해주는 기능을 넣었다.

ex) SPRING_DATASOURCE_URL => spring.datasource.url 

스프링 부트를 jar 형태로 aws 배포할 때는 시스템 환경변수만 적용되니 언더스코어로 스프링 부트 속성을 정의하면 적용된다.



스프링 부트 외부 구성(External configuration) 기능 설명

스프링 부트는 구동과 관련하여 외부에서 속성값을 받아 실행시 적용하는 외부 구성(External configuration) 기능을 지원한다. 스프링 부트에서 사용할 수 있는 OS 환경변수가 몇가지 있다.

  • SPRING_APPLICATION_JSONspring.application.json

  • SERVER_PORTserver.port 와 동일

  • SPRING_PROFILES_ACTIVEspring.profiles.active 와 동일

  • SPRING_APPLICATION_JSON: JSON 형태로 외부구성 값을 전달받는 기능

이는 로컬환경에서 jar 를 실행할 때에도 다음처럼 적용가능하다. 아래 세 가지 방법 모두 동일하게 실행된다.

$ java -jar -Dspring.application.json='{"spring.profiles.active": "dev"}' -jar my-app.jar
$ java -jar my-app.jar --spring.application.json='{"spring.profiles.active": "dev"}'
$ java -jar my-app.jar --SPRING_APPLICATION_JSON='{"spring.profiles.active": "dev"}'

AWS 빈즈토크 구성

애플리케이션 생성

  1. [Create New Application] 생성

  2. 'Web Server Environment' [Create web server] 생성

    • Predefined configurationJava

    • Environment typeLoad Balancing, auto scaling

  3. Select a source for your application version. (나중에 S3 형태로 선택"

  4. 환경설정은 여러분의 몫

Configuration - Softwar configuration

  • 스프링 부트 기본포트는 8080, 빈즈톡에서는 5000번을 듣는다. 둘이 어긋나는 부분이 있으니 환경변수를 지정하여 스프링 부트 앱의 포트번호를 지정한다.

기본 환경속성은 다음과 같다.

Environment Properties

Property NameProperty Value

GRADLE_HOME

/usr/local/gradle

JAVA_HOME

/usr/lib/jvm/java

M2

/usr/local/apache-maven/bin

M2_HOME

/usr/local/apache-maven

SERVER_PORT

5000

여기에 스프링 부트 환경변수에 해당하는 환경속성들을 추가해야한다.

  • SPRING_DATASOURCE_URL: jdbc:mysql://{url}/ebdb

  • SPRING_DATASOURCE_USERNAME: {username}

  • SPRING_DATASOURCE_PASSWORD: {password}

  • SPRING_JPA_HIBERNATE_DDL_AUTO: update // 변경가능

  • SPRING_JPA_DATABASE_PLATFORM: org.hibernate.dialect.MySQL5Dialect // 변경가능

기본적인 실행환경에 대해서는 스프링 프로파일 을 이용해서 구분짓도록 한다. application.properties 혹은 application.yml 의 기본적인 구성을 다음과 같이 했다면…​

# 기본적용공간 
server:
  error:
    whitelabel:
      enabled: false
    include-stacktrace: always
 
---
spring: #local
  profiles: local
  datasource:
    url: jdbc:mysql:localhost:3306/local
    username: honeymon
    password: gooddeveloper
  jpa:
    show-sql: true
    hibernate.ddl-auto: update
aws:
  s3.url: https://s3.aws.com/local-honeymon
 
---
spring: #test
  profiles: test
  datasource:
    url: jdbc:mysql:localhost:3306/test
    username: honeymon
    password: goodtester
  jpa:
    show-sql: true
    hibernate.ddl-auto: create-drop
aws:
  s3.url: https://s3.aws.com/test-honeymon
 
---
spring: #dev
  profiles: dev
  datasource:
    url: jdbc:mysql:localhost:3306/dev
    username: honeymon
    password: goodman
  jpa:
    show-sql: false
    hibernate.ddl-auto: validate
aws:
  s3.url: https://s3.aws.com/dev-honeymon
 
---
spring:
  profiles: prod

빈즈톡으로 운영서버에 배포하면서 prod 프로파일을 실행하고 싶다. 그런데 이때 변경되어야 할 정보들이 몇가지 있다. 보이는가?

spring.datasource.urlspring.datasource.usernamespring.datasource.passwordspring.jpa.hibernate.ddl-auto 등이 보인다.

스프링 부트의 자동 구성(Auto-Configuration) 은 외부 구성을 통해서 값을 변경하는 게 가능하다. 그런데 빈즈 토크에서는 외부 구성을 설정해도 java -jar 실행을 해도 외부 설정값을 실행인자로 전달하지 않는 것으로 보인다. 이런 기능적인 제약을 해소하기 위해 제공하는 환경 변수들이 있다.

  • SPRING_DATASOURCE_URL=jdbc:mysql://{rdb}/ebdb // spring.datasource.url 대응

  • SPRING_DATASOURCE_USERNAME=<username> // spring.datasource.username 대응

  • SPRING_DATASOURCE_PASSWORD=<password> // spring.datasource.password 대응

  • SPRING_JPA_HIBERNATE_DDL_AUTO=update // spring.jpa.hibernate.ddl-auto 대응

  • SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQL5Dialect // spring.jpa.database-platform 대응

-


5가지 환경변수는 배포하는 환경에 따라 크게 달라질 수 있는 부분이기 제공하는 듯 하다. 대부분의 운영체제에서는 환경변수에서 . 로 명명하는 것을 허용하지 않고 언더스코어(_) 로 사용한다. 스프링부트에서는 이에 대한 지원을 하고 있다. 즉, spring.datasource.url 가 SPRING_DATASOURCE_URL 와 대응된다는 것이다. 그러니 빈즈톡에서는 환경변수를 SPRING_DATASOURCE_URL 로 지정해도 스프링 부트에서 알아서 spring.datasource.url 에 대입해주는 것이다.

String url = System.getProperty("SPRING_DATASOURCE_URL");
String username = System.getProperty("SPRING_DATASOURCE_USERNAME");
String username = System.getProperty("SPRING_DATASOURCE_PASSWORD");

짱 좋군.

스프링 부트를 jar 형태로 배포하고, 빈즈톡에서는 Java 애플리케이션 서버로 배포하기 위한 설정은 위의 '환경변수'를 정의하는 것으로 간단하게 구성이 가능하다.

이제 손쉽게!! 빈즈톡에 스프링부트 애플리케이션을 jar로 배포하면서 외부 구성을 변경하는 시도를 해볼 수 있을 것이다.


변경:

  • org.hibernate:hibernate-validator:5.4.0.Final → org.hibernate:hibernate-validator:5.3.6.Final

  • javax.el:javax.el-api:2.2.5 → org.glassfish:javax.el

해결방법힌트:

spring-boot.1.5.1 Reference document 에 기재된 버전에 맞춤

문제발생지점:

java.lang.IllegalStateException: Failed to load ApplicationContext
     at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
     at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
     at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
     at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
     at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
     at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
     at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
     at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
     at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
     at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
     at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
     at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
     at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
     at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
     at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
     at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
     at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
     at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
     at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
     at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
     at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
     at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
     at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
     at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke(Method.java:498)
     at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
     at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
     at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
     at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
     at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
     at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke(Method.java:498)
     at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
     at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
     at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377)
     at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
     at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
     at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerAdapter' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter]: Factory method 'requestMappingHandlerAdapter' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mvcValidator' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.Validator]: Factory method 'mvcValidator' threw exception; nested exception is javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
     at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1173)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1067)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
     at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
     at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
     at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
     at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
     at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
     at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
     at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
     at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
     at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370)
     at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
     at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
     at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
     at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
     ... 45 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter]: Factory method 'requestMappingHandlerAdapter' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mvcValidator' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.Validator]: Factory method 'mvcValidator' threw exception; nested exception is javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
     at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
     at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
     ... 62 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mvcValidator' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.Validator]: Factory method 'mvcValidator' threw exception; nested exception is javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
     at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1173)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1067)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
     at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
     at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
     at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
     at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
     at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:381)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$c8b21c0c.mvcValidator(<generated>)
     at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.getConfigurableWebBindingInitializer(WebMvcConfigurationSupport.java:567)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration.getConfigurableWebBindingInitializer(WebMvcAutoConfiguration.java:410)
     at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.requestMappingHandlerAdapter(WebMvcConfigurationSupport.java:526)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration.requestMappingHandlerAdapter(WebMvcAutoConfiguration.java:372)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$c8b21c0c.CGLIB$requestMappingHandlerAdapter$3(<generated>)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$c8b21c0c$$FastClassBySpringCGLIB$$bbc0971f.invoke(<generated>)
     at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
     at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:356)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$c8b21c0c.requestMappingHandlerAdapter(<generated>)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke(Method.java:498)
     at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
     ... 63 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.Validator]: Factory method 'mvcValidator' threw exception; nested exception is javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
     at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
     at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
     ... 87 more
Caused by: javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
     at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:102)
     at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.<init>(ResourceBundleMessageInterpolator.java:45)
     at org.springframework.validation.beanvalidation.LocalValidatorFactoryBean$HibernateValidatorDelegate.buildMessageInterpolator(LocalValidatorFactoryBean.java:418)
     at org.springframework.validation.beanvalidation.LocalValidatorFactoryBean.setValidationMessageSource(LocalValidatorFactoryBean.java:160)
     at org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite.getValidator(WebMvcConfigurerComposite.java:165)
     at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration.getValidator(DelegatingWebMvcConfiguration.java:137)
     at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.mvcValidator(WebMvcConfigurationSupport.java:614)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$c8b21c0c.CGLIB$mvcValidator$41(<generated>)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$c8b21c0c$$FastClassBySpringCGLIB$$bbc0971f.invoke(<generated>)
     at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
     at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:356)
     at org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration$$EnhancerBySpringCGLIB$$c8b21c0c.mvcValidator(<generated>)
     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke(Method.java:498)
     at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
     ... 88 more
Caused by: javax.el.ELException: Provider com.sun.el.ExpressionFactoryImpl not found
     at javax.el.FactoryFinder.newInstance(FactoryFinder.java:101)
     at javax.el.FactoryFinder.find(FactoryFinder.java:197)
     at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:189)
     at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:160)
     at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:98)
     ... 105 more
Caused by: java.lang.ClassNotFoundException: com.sun.el.ExpressionFactoryImpl
     at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
     at javax.el.FactoryFinder.newInstance(FactoryFinder.java:87)
     ... 109 more


javax.validation.ValidationException: HV000041: Call to TraversableResolver.isReachable()

문제가 생긴 것은 애플리케이션 구동 후에 데이터베이스 정보를 새롭게 추가하거나 갱신할 때javax.validation.ValidationException: HV000041: Call to TraversableResolver.isReachable() 에러가 발생했다.

Spring Boot 1.4.2.RELEASE 를 기반으로 해서 bootRepackage 로 빌드한 jar 파일을 서버에서 직접 실행할 때 다음과 같은 에러가 발생했다.

javax.validation.ValidationException
javax.validation.ValidationException: HV000041: Call to TraversableResolver.isReachable() threw an exception. at org.hibernate.validator.internal.engine.ValidatorImpl.isReachable(ValidatorImpl.java:1621) at org.hibernate.validator.internal.engine.ValidatorImpl.isValidationRequired(ValidatorImpl.java:1597) at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:609) at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:580) at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:524) at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:492) at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:457) at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:407) at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:205) at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:110) at org.springframework.validation.DataBinder.validate(DataBinder.java:866)
...
Caused by: java.io.FileNotFoundException: JAR entry !/META-INF/services/javax.persistence.spi.PersistenceProvider
...

javax.validation.ValidationException: HV000041: Call to TraversableResolver.isReachable() Caused by: java.io.FileNotFoundException: JAR entry !/META-INF/services/javax.persistence.spi.PersistenceProvider

이런 때는 검색. 그 결과 찾은 것이 아래 링크다.

정리하자면 톰캣과 관련된 에러로 보인다. Tomcat 8.5.5 and Tomcat 8.5.6

이걸 해결하는 버전으로 1.4.3 에서 tomcat 8.5.9 버전을 적용했지만

이와 관련된 문제를 해결한건 Tomcat 8.5.10 으로 보인다. 그래서 스프링부트 개발팀에서 Tomcat 8.5.11 로 변경한 1.4.4.RELEASE 으로 변경하니 정상적으로 동작한다.


2016/11/08 에 Spring Boot 1.4.2 Available Now 가 출시되었다.over 100 fixes, improvements and 3rd party dependency updates 100 여개의 결함을 수정하고 구현하고, 서드파티에 대한 의존성 업데이트가 있었다고 한다.

그와 관련된 변화 중 하나가 spring-boot 라는 플러그인 아이디가org.springframework.boot 로 변경되었다.

이와 관련된 정보는 프로젝트 빌드를 실행해보면,

...
The plugin id 'spring-boot' is deprecated. Please use 'org.springframework.boot' instead.
...

와 같은 메시지가 출력되는 것을 확인했다. 이에 대한 정보를 찾아보았다. 이에 대해서 정보를 찾아보던 중에

64. Spring Boot Gradle plugin 페이지에서 힌트를 발견했다.

buildscript {
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.2.RELEASE")
    }
}
apply plugin: 'org.springframework.boot'

와 같은 형태로 되어 있는 것을 확인했다. 내가 사용하고 있는 build.gradle 에는 다음과 같이 선언되어 있다.

buildscript {
    ext {
        springBootVersion = '1.4.2.RELEASE'
    }
    repositories {
        jcenter()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'spring-boot' 
apply plugin: 'spring-boot' 만 apply plugin: 'org.springframework.boot'으로 변경하면 된다.

해결책

buildscript {
    ext {
        springBootVersion = '1.4.2.RELEASE'
    }
    repositories {
        jcenter()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'org.springframework.boot' 
잘 찾았길 바란다.


+ Recent posts