답변은 : https://stackoverflow.com/questions/48954087/intellij-idea-complains-cannot-resolve-spring-boot-properties-but-they-work-fine 에 잘 나와 있다.

공식적인 레퍼런스는 https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html 참고

  • META-INF/spring-configuration-metadata.json
  • META-INF/additional-spring-configuration-metadata.json


누군가 Inspection을 끄라고 하는데 이것은 좋은 방법이 아닌 것 같다. 인스펙션은 사용자의 실수를 막기 위한 기능인데 보기 싫다고 끄는 것은 문제가 있는 설정에 대해 통지를 받을 수 없기 때문이다.


대신 답변중 하나는 @ConfigurationProperties 를 이용하는 것인데 이것보다는 후자가 좋아 보인다.

src/main/resources/META-INF/spring-configuration-metadata.json 파일을 만들어서 커스텀 프로퍼티를 정의하는 것이다.

{
"properties": [
{
"name": "aws.accessKey",
"type": "java.lang.String"
},
{
"name": "aws.secretKey",
"type": "java.lang.String"
}
]
}


참고할 만 한 한글 블로그: http://wonwoo.ml/index.php/post/1599

이상하게 Mac OS X이 macOS X로 바뀌면서 멋대로 언어가 로그인 창이라던지 종료 창이 영어로 표시되기 시작했다.
딱히 나는 언어설정을 바꾸지 않았고 기존에는 한글로 나왔다.

이럴 때 시스템 언어 설정을 변경할 수 있는 명령어가 있다.

터미널을 실행해서 아래 명령을 입력한다. 이후 암호를 입력하고 10번을 입력하면 끝.

$ sudo languagesetup


재부팅하고 나면 아래와 같이 한글로 변경되었다.


윈도우에는 msconfig 명령을 이용하면 자동 실행되는 프로그램들 목록을 볼 수 있다.

맥에서는 /Library/LaunchAgents 디렉토리에 가면 부팅시 실행시킬 에이전트 목록을 볼 수 있다.

$ cd /Library/LaunchAgents
ls -l

자동 실행을 막으려면 편집기로 편집을 해서 맨 마지막의 true를 false로 바꿔주면 된다.
읽기 전용 속성이기 때문에 (E212: Can't open file for writing) 에러를 만날 수 있으니 sudo로 권한을 높혀서 편집하자.

$ sudo vim com.paloaltonetworks.gp.pangpa.plist

ref. https://www.robertsetiadi.com/disabling-globalprotect-vpn-auto-run-during-mac-start-up/

호출하는 코드는 5.11. Pipelining 의 예제와 동일한 형태이다.

그런데 아래와 같은 Cast 예외가 발생한다.

java.lang.ClassCastException: com.sun.proxy.$Proxy163 cannot be cast to org.springframework.data.redis.connection.StringRedisConnection

StackOverflow에 유사한 질문이 있다.

https://stackoverflow.com/questions/50680948/java-lang-classcastexception-com-sun-proxy-proxy219-cannot-be-cast-to-org-spri/53665591

그런데 해결책으로 나오는 것이 딱히 맘에 들지 않는다.

인라인 구현에서 밖의 redisTemplate를 참조하고 있기 때문이다.
doInRedis안에서는 RedisConnection만 가지고 처리하는 것이 맞아보이기 때문이다.

예제는 아래와 같이 되어 있는데,

//pop a specified number of items from a queue
List<Object> results = stringRedisTemplate.executePipelined(
  new RedisCallback<Object>() {
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
      StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
      for(int i=0; i< batchSize; i++) {
        stringRedisConn.rPop("myqueue");
      }
    return null;
  }
});

아래와 같이 수정을 하는 것이 바람직하다.

//pop a specified number of items from a queue
List<Object> results = redisTemplate.executePipelined(
  new RedisCallback<Object>() {
    public Object doInRedis(RedisConnection connection) throws DataAccessException {
      return connection.rPop("myqueue".getBytes());
    }
});

만약 자바8 이후라면 다음과 같이 간단하게 쓸 수 있다.

//pop a specified number of items from a queue
List<Object> results = redisTemplate.executePipelined(
  (RedisCallback<Object>) connection -> connection.rPop("myqueue".getBytes())));

레퍼런스의 batchSize를 넣으면 아래와 같이 null객체가 쌓이므로 절대로 저렇게 하면 안된다.


아침에 아래와 같은 Wikipedia에서 메일이 왔다.

누군가가 내 계정으로 로그인을 시도하려고 하다가 비밀번호 초기화 요청을 해서 메일을 보냈다는 것이다.

아이피를 찾아보니 인도쪽이다.

위키백과에 접속을 하니 기존 비번은 계속 사용 가능했다. 임시적으로 기존 비번 + 생성된 비번의 체계가 공존하나보다.
(프랑스 위키에서 누군가 나에게 말을 남겼군..)



HikariCP 3.1.0을 사용하고 있다.
백그라운드 처리가 필요해서 별도의 스레드로 느리게 처리되는 곳에서 JPA작업을 하고 있었다.

그런데 아래와 같은 WARN 로그가 보였다.
결과부터 이야기하면 커넥션이 회수가 안되어 누수(leak)이 감지되었다는 것이다.

2018-12-04 17:55:51.378  WARN 14050 --- [ool housekeeper] com.zaxxer.hikari.pool.ProxyLeakTask     [] : Connection leak detection triggered for org.mariadb.jdbc.MariaDbConnection@4ebeb6b9 on thread Thread-19, stack trace follows

그런데 신기하게도 백그라운드 스레드가 끝나는 로그 이후에 이전에 커넥션 누수로 알려진 것이 회수가 되었다는 오탐 로그가 찍혔다.

2018-12-04 17:55:59.744  INFO 14050 --- [      Thread-19] com.zaxxer.hikari.pool.ProxyLeakTask     [] : Previously reported leaked connection org.mariadb.jdbc.MariaDbConnection@4ebeb6b9 on thread Thread-19 was returned to the pool (unleaked)


찾아보니 JPA에서 findOne 같은 동작은 실제 누수로 판단하는 기준 시간(threshold)가 너무 짧게 되어 있어서 누수로 오탐을 한다는 것이다. 이 문제는 2.6.0로 업데이트 하면 해결이 된다고 한다. 이미 3.1 버전인데???


삽질 할 뻔한 예외 처리기...


Exception Handler에서 ResponseStatus에 reason를 적으면 return 한 형태의 객체로 반환이 안되고 임의의 형태로 반환된다.

@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(NotFoundJobException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="No Job Found")
public AcAuthResult notCompletedJobExceptionHandler(NotFoundJobException ex) {
return AcAuthResult.builder()
.jobId(ex.getJobId())
.build();
}


원하는 형태:

{
   "jobId":8,
   "createdAt":"2018-11-30 17:12:19.000",
   "completedAt":null,
   "currentStep":"STANDBY",
   "demanderId":"namo",
   "numberOfTasks":2,
   "tasks":[
   ]
}


반환된 형태:

{
   "timestamp":"2018-11-30T08:16:24.472+0000",
   "status":206,
   "error":"Partial Content",
   "message":"Not Finished.",
   "trace":"com.tistory.demo.exception.NotCompletedJobException\n\tat com.tistory.demo.service.JobService.responseResult(JobService.java:49)\n\tat com.tistory.demo.service.pg.TaskService.process(AccountVerificationTaskService.java:62)\n\tat com.tistory.demo.controller.pg.ApiController.authenticateAccount(PgController.java:289)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:215)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:142)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:998)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:901)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:875)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.lang.Thread.run(Thread.java:748)\n",
   "path":"/pg/authentication/account"
}

reason을 빼면 된다.


+

만약 핸들러 메서드가 모두 @ResponseBody를 붙인다면,
@RestControllerAdvice를 쓰면 @ControllerAdvice을 썼을 때 메서드마다 @ResponseBody를 붙여주는 수고를 덜을 수 있다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {



퇴근시에 로컬에서 웹 어플리케이션을 띄어놓고 퇴근한다.

출근할 때 아래와 같은 경고 로그가 다량 떠있다.

2018-11-28 02:44:21.892  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=2h28s701ms).

2018-11-28 02:45:46.788  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=54s902ms).

2018-11-28 02:47:41.700  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=54s904ms).

2018-11-28 02:49:06.617  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=54s915ms).

2018-11-28 02:51:01.900  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=55s269ms).

2018-11-28 02:52:26.821  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=54s910ms).

2018-11-28 02:54:21.733  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=54s905ms).

2018-11-28 04:55:20.700  WARN 2135 --- [ool housekeeper] com.zaxxer.hikari.pool.HikariPool        : api-dev-conn-pool - Thread starvation or clock leap detected (housekeeper delta=2h28s965ms).

스레드 굶주림 혹은 시간 도약(리프)가 발견되었다는 메세지이다.

찾아보니 랩탑 같은 경우 잠들기 모드에 들어가면 발생할 수 있다는 brettwooldridge라는 사람의 댓글이 있었다.

이슈: What does this mean Thread Starvation or clock leap detected #679

또 다른 가능성으로는 VM으로 가동시에 주기적으로 시스템 시간을 동기화하면서 시간 도약이 이루어질 수 있다는 것.

하지만 질문자는 자기는 VM으로 돌리지 않았다고 한다.

질문자는 아직 결론을 찾지 못한 것 같다. 나의 경우는 맥북이 잠들기 모드로 빠지면서 시간 도약이 이러줘지 않았나 생각해본다.


yml 프로퍼티에서 Duration 사용을 하고 있었다.

pilot 프로젝트에서는 @Value로 잘 주입이 되는데, 본 branch로 해당 설정 코드를 옮겨오니 아래와 같은 에러가 발생했다.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoBean' defined in com.github.detouched.DemoValueApp: Unsatisfied dependency expressed through method 'demoBean' parameter 0; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'java.time.Duration'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.time.Duration': no matching editors or conversion strategy found

로그를 해석보면 String 타입의 문자열을 Duration으로 변환하는 컨버터를 찾지 못했다는 것이다.

스프링 부트 깃헙의 이슈 13237을 보니

Duration 타입의 설정은 application.properties 형태에서만 사용이 된다고 한다.
다행히 스프링 팀에서도 yml에서도 컨버팅이 되도록 한다고 하는데 그것이 2.1 버전이다. 깃헙의 이슈12148 참고

본 브랜치의 스프링 부트 버전이 2.0.2.RELEASE 이다보니 Duration으로 변환이 되지 못하던 것이다.

주의해야 할 점은 단순히 build.gradle 상 버전만 수정하면 동일한 에러가 발생했다.
뭔가 캐싱이 되어 있는지 clear를 해야지 제대로 인식이 되었다.