[springboot] 스프링 부트 1.5.14.RELEASE 와 2.0.3.RELEASE 출시

지난 2018/06/14 스프링 부트 1.5 와 2.0 새로운 버전이 출시되었다. 기존에 있던 결함을 수정하고 의존성 라이브러리들을 업데이트 하는 수준이었다.

그런데 의도치 않게 내게 큰 문제가 발생을 하니…​ 바로 Lombok과 관련된 문제였다.

두 버전 모두 lombok 1.16.22 버전을 사용한다.

1.16.20 은 이상이 없었지만 1.16.22 버전에서는 다음과 같이 사용하면 컴파일 에러가 발생한다.

@Data
@NoArgsConstructor
public class Transfer {
    private String name;
    private Integer value;

    public Transfer(String name, Integer value) {
        this.name = name;
        this.value = value;
    }
}
Error:(9, 1) java: constructor Transfer() is already defined in class io.honeymon.boot.springboot.training.Transfer

이 문제를 해결하는 방법은 간단하다.

위 내용을 살펴보고, 세부적인 항목들을 명시적으로 작성하자.

@Getter
@Setter
@NoArgsConstructor
@ToString(onlyExplicitlyIncluded = true)  // (1)
@EqualsAndHashCode(onlyExplicitlyIncluded = true) // (2)
public class Transfer {

    @ToString.Include // (1)
    @EqualsAndHashCode.Include // (2)
    private String name;

    @ToString.Include
    @EqualsAndHashCode.Include
    private Integer value;

    @Builder
    public Transfer(String name, Integer value) {
        this.name = name;
        this.value = value;
    }
}

<1>, <2> lombok 1.16.22 버전부터 추가된 @Include@Exclude 를 이용해서 필드에서 toString()equals(), hashCode() 에서 사용할 필드를 지정할 수 있다. (onlyExplicitlyIncluded = true)를 선언해줘야 실제로 적용된다.

Note

명시적으로 필드선언을 했을 때 타입에 선언된 부분에 (onlyExplicitlyIncluded = true)를 선언해야 적용된다.

/**
 * Only include fields and methods explicitly marked with {@code @ToString.Include}.
 * Normally, all (non-static) fields are included by default.
 */
boolean onlyExplicitlyIncluded() default false;
Warning

그런데 이번에 배포된 1.16.22 에서는 (onlyExplicitlyIncluded = true) 부분에서 컴파일 에러가 발생한다. 이를 해결하기 위해서는 롬복 1.18.0 으로 변경해야 한다.

Error:(12, 45) java: Can't translate a class java.lang.Boolean to the expected class java.lang.Boolean

이전에는

@Getter
@Setter
@NoArgsConstructor
@ToString(of={"name", "value"})
@EqualsAndHashCode(of={"name", "value"})
public class Transfer {

    private String name;

    private Integer value;

    @Builder
    public Transfer(String name, Integer value) {
        this.name = name;
        this.value = value;
    }
}

위와 같이 of 속성에 문자배열로 등록해야해서 복사하여 붙여넣거나 타이핑을 하다가 오타가 발생할 가능성이 있었던 것을 피할 수 있게 되었다.

@Getter
@Setter
@NoArgsConstructor
@ToString(onlyExplicitlyIncluded = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Baggage {
    @EqualsAndHashCode.Include
    @ToString.Include
    private String name;    // 수화물명

    @EqualsAndHashCode.Include
    @ToString.Include
    private Integer value;  // 가치

    @EqualsAndHashCode.Include
    @ToString.Include
    private String departure;   // 출발지

    @EqualsAndHashCode.Include
    @ToString.Include
    private String arrival;     // 도착지

    private String description;

    @Builder
    public Baggage(String name, Integer value, String departure, String arrival, String description) {
        this.name = name;
        this.value = value;
        this.departure = departure;
        this.arrival = arrival;
        this.description = description;
    }
}

위와 같이 작성하면 된다.

정리

  • 스프링 부트 1.15.14와 2.0.3 출시

  • 롬복(Lombok)

    • 위 버전에 추가된 Lombok 1.16.22 버전 명시

    • @Data@NoArgsConstructor 는 함께 사용할 수 없다.

      • @Data@NoArgsConstructor가 기본 생성자(Default Consturctor)를 생성하는 부분에서 컴파일러 에러가 발생하는 것으로 추측할 수 있다.

    • @ToString@EqualsAndHashCode를 필드에 선언할 수 있다. 이 기능을 적용하려면 타입영역에서 @ToString(onlyExplicitlyIncluded = true) 으로 선언해야 한다.

    • 1.16.22 에서 컴파일 에러가 있어서 1.18.0 이 나옴

    • 1.18.0 버전을 사용하라.

또 삽질을 하러 갑시다.

얼마 전 Eclipse Mars가 출시하고, 스프링에서는 이클립스 마르스를 얹은 STS.3.7.0 버전을 내놓았다.

나는 지금까지 개발할 때 Lombok을 많이 사용해왔다. 클래스 내 필드에 대한 Getter/Setter 생성이나 toString(), equals(), hashCode() 메서드를 오버라이드 하기 위해 클래스에 코드가 덕지덕지 붙는 것을 어노테이션들(@ToString,@EqualsAndHashCode, @Getter, @Setter, @Data 등)로 대신할 수 있기 때문이다. 자바로 개발할 때 겪게되는 반복적이고 가독성에 크게 도움되지 않는 녀석들을 컴파일과정에서 어노테이션을 기반으로 하여 자동생성해주기에 코드가 그나마 많이 간결해진다.

그런데!! 이클립스 루나까지는 크게 문제가 없던 STS(3.6.0)에서 3.7.0으로 버전업을 하고 난 이후에 몇몇 결함이 발생한다. 예를 들어,

  • 현재 열고 있는 클래스에 대한 유닛테스트를 위해 클래스를 만들려고 하면 비어있는 파일만 생성하기
  • Getter/Setter 생성시 원인모를 예외를 발생시키기
  • 인스턴스 메서드에서 던지는 throws Exception 을 메서드에 반영하지 못함
  • 구현하고 있는 인터페이스에 메서드를 생성하지 못함

등... 개발하는 과정에서 자주하는 일들을 할 수가 없는 것은, 가뜩이나 좋지 않은, 생산성을 뚜욱하고 떨어뜨리는 슬픈 상황을 만들었다. 그래도 투덜거리면서 쓰다가,

투걸거리지만 말고, 버그 리포팅을 해서 개선할 수 있도록 해주세요.

간단하게 Getter/Setter를 만드는 작업을 진행해보면 다음과 같은 증상이 나온다.





  • 에러코드
eclipse.buildId=3.7.0.201506290652-RELEASE-e45
java.version=1.8.0_20
java.vendor=Oracle Corporation
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=ko_KR
Framework arguments:  -client -product org.springsource.sts.ide
Command-line arguments:  -os linux -ws gtk -arch x86_64 -client -product org.springsource.sts.ide -console
 
java.lang.reflect.InvocationTargetException
    at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:476)
    at org.eclipse.jface.operation.ModalContext.run(ModalContext.java:371)
    at org.eclipse.ui.internal.WorkbenchWindow$14.run(WorkbenchWindow.java:2156)
    at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
    at org.eclipse.ui.internal.WorkbenchWindow.run(WorkbenchWindow.java:2152)
    at org.eclipse.ui.internal.progress.ProgressManager$RunnableWithStatus.run(ProgressManager.java:1394)
    at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
    at org.eclipse.ui.internal.progress.ProgressManager$5.run(ProgressManager.java:1228)
    at org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:186)
    at org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:145)
    at org.eclipse.swt.widgets.Display.syncExec(Display.java:4633)
    at org.eclipse.ui.internal.progress.ProgressManager.runInUI(ProgressManager.java:1225)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.run(AddGetterSetterAction.java:618)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.generate(AddGetterSetterAction.java:549)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.run(AddGetterSetterAction.java:340)
    at org.eclipse.jdt.ui.actions.AddGetterSetterAction.run(AddGetterSetterAction.java:584)
    at org.eclipse.jdt.ui.actions.SelectionDispatchAction.dispatchRun(SelectionDispatchAction.java:279)
    at org.eclipse.jdt.ui.actions.SelectionDispatchAction.run(SelectionDispatchAction.java:251)
    at org.eclipse.jface.action.Action.runWithEvent(Action.java:473)
    at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:595)
    at org.eclipse.jface.action.ActionContributionItem.access$2(ActionContributionItem.java:511)
    at org.eclipse.jface.action.ActionContributionItem$5.handleEvent(ActionContributionItem.java:420)
    at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
    at org.eclipse.swt.widgets.Display.sendEvent(Display.java:4481)
    at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1327)
    at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:3819)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3430)
    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$4.run(PartRenderingEngine.java:1127)
    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:337)
    at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1018)
    at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:156)
    at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:654)
    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:337)
    at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:598)
    at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:150)
    at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:139)
    at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134)
    at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:380)
    at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:235)
    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:483)
    at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:669)
    at org.eclipse.equinox.launcher.Main.basicRun(Main.java:608)
    at org.eclipse.equinox.launcher.Main.run(Main.java:1515)
    at org.eclipse.equinox.launcher.Main.main(Main.java:1488)
Caused by: java.lang.IndexOutOfBoundsException: Index: 5, Size: 5
    at java.util.ArrayList.rangeCheck(ArrayList.java:653)
    at java.util.ArrayList.get(ArrayList.java:429)
    at org.eclipse.jdt.internal.formatter.TokenManager.get(TokenManager.java:68)
    at org.eclipse.jdt.internal.formatter.TokenManager.findIndex(TokenManager.java:161)
    at org.eclipse.jdt.internal.formatter.TokenManager.firstIndexIn(TokenManager.java:188)
    at org.eclipse.jdt.internal.formatter.TokenManager.firstTokenIn(TokenManager.java:194)
    at org.eclipse.jdt.internal.formatter.SpacePreparator.visit(SpacePreparator.java:196)
    at org.eclipse.jdt.core.dom.MethodDeclaration.accept0(MethodDeclaration.java:611)
    at org.eclipse.jdt.core.dom.ASTNode.accept(ASTNode.java:2711)
    at org.eclipse.jdt.core.dom.ASTNode.acceptChildren(ASTNode.java:2782)
    at org.eclipse.jdt.core.dom.TypeDeclaration.accept0(TypeDeclaration.java:470)
    at org.eclipse.jdt.core.dom.ASTNode.accept(ASTNode.java:2711)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.prepareSpaces(DefaultCodeFormatter.java:350)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.prepareFormattedCode(DefaultCodeFormatter.java:193)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.format(DefaultCodeFormatter.java:155)
    at org.eclipse.jdt.internal.formatter.DefaultCodeFormatter.format(DefaultCodeFormatter.java:139)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFormatter.formatString(ASTRewriteFormatter.java:246)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFormatter.formatNode(ASTRewriteFormatter.java:376)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFormatter.getFormattedResult(ASTRewriteFormatter.java:187)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer.doTextInsert(ASTRewriteAnalyzer.java:1357)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer$ListRewriter.rewriteList(ASTRewriteAnalyzer.java:647)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer$ListRewriter.rewriteList(ASTRewriteAnalyzer.java:802)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer.rewriteParagraphList(ASTRewriteAnalyzer.java:1175)
    at org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteAnalyzer.visit(ASTRewriteAnalyzer.java:1811)
    at org.eclipse.jdt.core.dom.TypeDeclaration.accept0(TypeDeclaration.java:453)
    at org.eclipse.jdt.core.dom.ASTNode.accept(ASTNode.java:2711)
    at org.eclipse.jdt.core.dom.rewrite.ASTRewrite.internalRewriteAST(ASTRewrite.java:302)
    at org.eclipse.jdt.core.dom.rewrite.ASTRewrite.rewriteAST(ASTRewrite.java:291)
    at org.eclipse.jdt.internal.corext.codemanipulation.AddGetterSetterOperation.run(AddGetterSetterOperation.java:351)
    at org.eclipse.jdt.internal.core.BatchOperation.executeOperation(BatchOperation.java:39)
    at org.eclipse.jdt.internal.core.JavaModelOperation.run(JavaModelOperation.java:729)
    at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:2241)
    at org.eclipse.jdt.core.JavaCore.run(JavaCore.java:5409)
    at org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter.run(WorkbenchRunnableAdapter.java:106)
    at org.eclipse.jface.operation.ModalContext.runInCurrentThread(ModalContext.java:463)
    ... 48 more

라고 올챙이 프로젝트(https://github.com/hangum/TadpoleForDBTools)를 지휘하고 계시는 조현종님이 말씀하시기에 '그래, 버그를 수정할 수 있도록 돕자'하는 마음에 이클립스의 'Error View'를 열고 이클립스에서 생긴 예외상황을 찾아봤다. 그리고 이 것을 SNS에 올리니까

그거 롬복 때문에 생긴 문제에요. 롬복 최신버전 받으면 정상적으로 동작해요.

라고 지인(http://sbcoba.tistory.com/)이 알려주었다. 이 때 찾아드는 허무감이란... 요 근래에 개발환경에 큰 변화가 찾아오기는 했다.

  • JDK 7 -> JDK 8
  • Eclipse Luna -> Eclipse Mars

이런 변화를 간과하고 이클립스만 탓한 내가 부끄러워지는구나.... 사용하고 있던 롬복은 v1.14.8 버전이다. 현재(2015/08/27) project lombok에서 다운로드 가능한 버전은1.16.6(https://projectlombok.org/changelog.html)이다. 8월 16일에 반영된 내용을 살펴보니

이다. 이클립스 마르스와 관련해서 버그가 있었던 것이었다. 최신버전 받으면 해결된다. 혹은... 롬복을 사용하지 않으면 된다.


+ Recent posts