lombok을 잘써보자! (3)

오늘은 예전에 lombok을 잘써보자! 시리즈에서 조금 추가된 내용을 써보려고 한다. lombok 버전도 올라가면서 새로 추가된 어노테이션도 있고 놓쳤던 부분도 있을 수 있어 좀 더 추가하여 내용을 이어가보자. 참고로 지금 필자가 lombok 을 사용하는 버전은 1.18.2 버전이다. 지금 현재까지 나온 버전은 1.18.4 버전으로 알고 있다.

lombok을 잘써보자! (1)

lombok을 잘써보자! (2)

@Value

Value 어노테이션이다. 이것은 불변을 의미한다.  아주 간단하게 클래스 레벨에 @Value 어노테이션만 선언하면 사용할 수 있다.  코드를 보면서 살펴보도록 하자.

@Value
public class ValueExample {
    String name;
    String email;
}

기본적으로 위와 같이 선언했을 경우 필드는 기본적으로 private 접근제어자와 final 이 붙은 상수가 된다. final이 붙어 setter는 존재하지 않고 getter만 존재한다. 클래스 자체도 final class로 상속을 받을 수 없다.  @Data 어노테이션과 비슷하게 equals, hashCode, toString을 함께 만들어 준다. @Data 어노테이션이 비슷하지만 불변인 정도? 그 정도로만 생각해도 문제없을 듯 하다.  기본 생성자는 private 생성자이다.  기본생성자는 만들어 주지만 private 생성자로 만들어 준다.  위의 클래스를 바닐라 자바로 본다면 다음과 같을 것이다.

public final class ValueExample {
    private final String name;
    private final String email;

    public ValueExample(String name, String email) {
        this.name = name;
        this.email = email;
    }

    private ValueExample() {
        this.name = null;
        this.email = null;
    }

    public String getName() {
        return this.name;
    }

    public String getEmail() {
        return this.email;
    }
    // equals, hashCode, toString
}

위와 비슷한 모양으로 코드가 생성될 것으로 판단된다.  @Value어노테이션의 속성으로는 staticConstructor 가 존재하는데 static한 생성자를 생성해주는 속성이다. 이 속성을 사용할 경우에는 모든 생성자가 private 으로 되고 정의해둔 해당 static 메서드만 사용할 수 있다.

@Value(staticConstructor = "of")
public class ValueExample {
    String name;
    String email;
}

ValueExample ve = ValueExample.of("wonwoo", "wonwoo@test.com");

주로 DTO로 사용할 때 사용하면 될 듯 하다.

@Wither

이번에는 Wither 어노테이션이다. 음.. 이 어노테이션도 불변?과 관련이 있다. 해당 프로퍼티를 다시 어사인할때 해당 Object를 변경하는게 아니라 새로운 Object를 리턴해준다. 이 어노테이션은 필드 혹은 클래스에 붙일 수 있다.

public class WitherExample {
    @Wither
    private final String name;

    @Wither
    private final String email;

    public WitherExample(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

위와 같이 필드를 정의 했을때 다음과 같이 사용할 수 있다.

WitherExample we = new WitherExample("wonwoo", "wonwoo@test.com");
WitherExample we1 = we.withName("woo");

위에서 말했다시피 해당 Object를 새로 만들어 return 해 주고 있다. 만약 위와 같이 모든 필드에 적용하고 싶다면 클래스 레벨에 @Wither 어노테이션을 붙어도 된다.

@Wither
public class WitherExample {

    private final String name;

    private final String email;

    public WitherExample(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

위 아래 모두 동일한 코드가 생성된다. 위 코드를 바닐라 자바로 바꾸어 본다면 아래와 같을 것이다.

public class WitherExample {
    private final String name;
    private final String email;

    public WitherExample(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public WitherExample withName(String name) {
        return this.name == name ? this : new WitherExample(name, this.email);
    }

    public WitherExample withEmail(String email) {
        return this.email == email ? this : new WitherExample(this.name, email);
    }
}

어렵지 않다.  @Wither 속성중에 해당 메서드의 접근제어자를 설정해 줄 수도 있다. 

public class WitherExample {

    @Wither(AccessLevel.PROTECTED)
    private final String name;
    //...
}

@Singular와 @Builder.Default

이 어노테이션은 @Builder 어노테이션을 사용할 때 유용하다.  @Builder 어노테이션은 다들 아시다시피 builder 패턴으로 해당 Object을 만들어주는 그런 어노테이션이다. 생성자의 파라미터가 많을 경우 유용한 어노테이션이다. 그 때 사용할 수 있는 어노테이션이 @Singular 어노테이션과 @Builder.Default 어노테이션이다. @Singular 어노테이션은 컬렉션에 사용할 수 있는데 하나의 어떤 Object을 컬렉션에 추가 할 수도 있고  컬렉션 모두를 추가할 수 도 있다.

@Builder.Default 어노테이션은 @Builder 어노테이션을 사용할 경우 미리 선언된 프로퍼티의 값을 사용할 수 없다. 

아래의 예제를 보면서 살펴보도록 하자.

@Builder
public class SingularExample {

    @Builder.Default
    private String name = "wonwoo";

    @Singular
    private List<String> phones;
}

위 처럼 사용할 경우에는 name에 기본적으로 wonwoo 라는 값을 넣어두었다. 만약 @Builder.Default 어노테이션이 존재 하지 않는다면 해당 값을 초기화 되지 않는다.

SingularExample singularExample  = SingularExample
    .builder()
    .build();

SingularExample(name=wonwoo, phones=[])

위와 같이 아무 값을 넣지 않았지만 name에는 wonwoo라는 값이 존재한다.  만약 @Builder.Default 를 제거한다면 아래와 같은 값이 출력 될 것이다.

@Builder
public class SingularExample {
    private String name = "wonwoo";

    @Singular
    private List<String> phones;
}

SingularExample(name=null, phones=[])

@Singular 어노테이션은 단일 Object를 컬렉션에 추가할 수도 있고 컬렉션 자체를 추가할 수 도 있는 어노테이션이다. 사용하는 코드를 살펴보자.

SingularExample singularExample = SingularExample
    .builder()
    .phone("010-0000-1111")
    .phone("010-0000-1112")
    .phones(Arrays.asList("010-1111-2222", "010-1111-2222"))
    .build();

위와 같이  phones 라는 컬렉션에 phone을 하나씩 하나씩 추가할 수 있다. 컬렉션을 사용할 때 유용한 어노테이션인 듯 싶다. 

빌더 어노테이션을 유용하게 사용한다면 한번씩 살펴보는 것도 나쁘지 않다.

@FieldNameConstants

이 어노테이션은 어노테이션명 그대로 필드명을 상수로 사용하는 어노테이션이다. 근데 조금 문서와 다르다. 필자는 intellij를 쓰는데 해당 플러그인이 지원을 제대로 해주지 않는 건지.. 모르겠지만 필자가 테스트 해본 걸로 글을 작성할 예정이니 참고하면 되겠다. 보니까 1.18.4 부터 디자인이 조금 바뀌었다고 한다. 일단 이런게 있다고만 알자!

@FieldNameConstants
public class FieldNameConstantsExample {
    private String abcd;
}

위와 같이 상수를 담고 있는 클래스에 작성하면 된다.  그럼 해당 필드명 그대로 상수 값이 된다. 

String fieldAbcd = FieldNameConstantsExample.FIELD_ABCD;
System.out.println(fieldAbcd);
// abcd

위와 같이 작성하고 출력할 경우에 abcd 라는 문자열이 출력 된다. 

@FieldNameConstants
public class FieldNameConstantsExample {

private String ABCD;
}

만약 위와 같이 상수를 ABCD 대문자로 하면 필드명에 _ 언더바가 많이 생긴다.

String fieldAbcd = FieldNameConstantsExample.FIELD_A_B_C_D;

또 한  해당 필드에 접근제어를 할 수 도 있다.

@FieldNameConstants(level = AccessLevel.PRIVATE)
private String abcd;

글쎄다. 사용할지는 모르겠다. 아마도 당분간은 사용하지 않을 것 같다.

@Accessors

해당 어노테이션은 클래스 레벨에 사용할 경우 setter를 체이닝하게 만들 수 있는 어노테이션이다.  참고로 필드에서 사용할 수 있는 prefix는 언급하지 않겠다. 딱히 사용할 일도 없을 것 같아서.. 그냥 필드명 그대로 사용할 것 같다.

어쨌든 제외 하고도 두개의 속성이 있는데 다른점은 메서드명이 달라진다는 것뿐이지 하는 역할을 같다.

@Accessors(chain = true)
@Data
public class AccessorsExample {

    private String name;
    private String email;
}

위와 같이 chain 옵션은 사용할 경우에는 setter가 만들어 질 때 해당 클래스를 다시 리턴하는 체이닝방식으로 만들어 진다.

AccessorsExample accessorsExample = new AccessorsExample();
AccessorsExample emailAccessors = accessorsExample.setEmail("wonwoo@test.com");

그렇다고 해서 불변은 아니다.  상태를 변경시킨후에 해당 Object을 리턴할 뿐이다. 

public AccessorsExample name(String name) {
    this.name = name;
    return this;
}

public AccessorsExample email(String email) {
   this.email = email;
   return this;
}

대략 위와 같이 setter가 만들어 진다고 생각하면 된다.
이번엔 fluent 속성을 사용해보자.

@Accessors(fluent = true)
@Data
public class AccessorsExample {

    private String name;
    private String email;
}

달라진 것 메서드명 뿐이다. getter, setter 모두 달라진다.

AccessorsExample accessorsExample = new AccessorsExample();
AccessorsExample emailAccessors = accessorsExample.email("wonwoo@test.com");
AccessorsExample nameAccessors = accessorsExample.name("wonwoo");
String name = accessorsExample.name();

get과 set이 라는 prefix가 사라지고 email, name 으로 상태를 변경 시키고 값을 가져올 수 있다.  이 또한 상태만 변경시키지 새로운 Object을 만드는 것은 아니다. 

오늘은 이렇게 lombok을 잘써보자! 3탄을 가져나왔다. 예전에 썼던 1탄, 2탄 모두 참고하면 좋겠다.

부디 유용한 글이 되었기를..

Spring boot 2.1 변화

몇 일전에 Spring boot 2.1이 릴리즈 되었다. 그래서 오늘 이와 관련된 이야기를 해보려고 한다. Spring boot 2.1의 변화. 릴리즈 된 내용들을 살펴보도록 하자. 물론 다 알아보지는 못하고 필자가 아는 내용, 혹은 잘 사용했던 내용 위주로만 설명할 예정이니 더 많은 내용들은 문서를 통해 확인하길 바란다. 또한 꼭 문서를 보는 것은 추천한다.
그럼 한번 살펴보도록 하자.

Deprecations from Spring Boot 2.0

Spring boot 2.0 에서 deprecated 되었던 메서드, 클래스, 생성자, 필드 등이 모두 삭제 되었다. CouchbaseHealthIndicatorProperties, EnvironmentTestUtils, RouterFunctionMetrics 클래스가 삭제 되었고, 여러개의 필드, 메서드들이 삭제 되었다. 더 많은 정보를 원한다면 해당 문서를 찾아보길 바란다.
만약 업그레이드를 한다면 없어진 클래스, 메서드를 사용하지 않길 바란다.

Bean Overriding

Spring boot 2.1 부터는 Bean 오버라이딩 기능이 불가능하다. 이는 실수를 막기위함이라고 하는데.. 예를들어 다음과 같이 코드를 작성했을 경우 에러가 발생한다.



@Configuration
public class SuperConfig {

  @Bean
  Foo foo() {
  return new Foo();
  }
}

@Configuration
public class Config extends SuperConfig {

  @Override
  @Bean
  Foo foo() {
  return new Foo();
  }
}

그럼 다음과 같은 에러가 발생한다.

The bean 'foo', defined in class path resource [SuperConfig.class], could not be registered. A bean with that name has already been defined in class path resource [Config.class] and overriding is disabled.

만약 Bean Overriding 을 허용하고 싶다면 다음과 같이 프로퍼티에 작성하면 된다.

spring.main.allow-bean-definition-overriding=true

Actuator ‘info’ and ‘health’ Endpoint Security

이전 Actuator 를 사용할 경우 spring-security가 클래스패스에 있을 경우 모든 Endpoint가 인증을 시도했다. 하지만 이제부터는 spring-security 가 클래스 패스에 있어도 info, health endpoint는 인증을 시도 하지 않고 사용할 수 있다. 이 것은 디펜더시 이외에 아무 설정 하지 않았을 경우를 의미한다.

Servlet Path

server.servlet.path 프로퍼티가 spring.mvc.servlet.path 프로퍼티로 변경 되었다.

Logging Refinements

이건 뭔소린지 잘 모르겠다. 아래에 logging group하고 관련있는 건지..? 아무튼..

logging.level.web=debug

web 과 관련된 로그를 debug로 남기고 싶다면 위와 같이 작성하면 된다. 그럼 org.springframework.core.codec 패지키, org.springframework.http 패키지, org.springframework.web 패키지 아래의 로그들이 debug으로 출력 된다.

2018-11-04 19:08:37.928 DEBUG 11085 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public java.lang.String ml.wonwoo.springbootnew.SpringBootNewApplication$TestController.hello()
2018-11-04 19:08:37.928 DEBUG 11085 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : GET "/?test=1", parameters={masked}
2018-11-04 19:08:37.929 DEBUG 11085 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public java.lang.String ml.wonwoo.springbootnew.SpringBootNewApplication$TestController.hello()
2018-11-04 19:08:37.930 DEBUG 11085 --- [nio-8080-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/html', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
2018-11-04 19:08:37.930 DEBUG 11085 --- [nio-8080-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Writing ["hello"]

위와 같이 유용한 로그들이 출력된다. Http 메서드, URL, 파라미터 등이 출력 되니 참고하면 된다. 근데 여기서 파라미터들은 마스킹 처리되어 있다. 민감한 정보들이 노출될 수 있어 마스킹 처리는 했지만 해당정보도 보여주고 싶다면 다음과 같이 프로퍼티에 작성하면 된다.

spring.http.log-request-details=true

그럼 이와 같이 파라미터 정보도 출력된다.

2018-11-04 20:47:24.115 DEBUG 11176 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : GET "/?test=1", parameters={test:[1]}

Narayana JTA Support

Narayana JTA 는 공식지원은 중단 되었고 이를 대체할 narayana-spring-boot-starter 가 존재한다. 만약 필요하다면 아래와 같이 업그레이드를 해야 한다.


<dependency>
    <groupId>me.snowdrop</groupId>
    <artifactId>narayana-spring-boot-starter</artifactId>
    <version>2.0.1</version>
</dependency>

HttpPutFormContentFilter

HttpPutFormContentFilter 클래스가 Deprecated 되었고 이를 대체할 FormContentFilter 클래스가 추가 되었다. 또 한 이와 관련된 spring.mvc.formcontent.putfilter.enabled 프로퍼티는 spring.mvc.formcontent.filter.enabled 프로퍼티로 변경 해야 된다.

InfluxDB HttpClient Customization

이전에는 OkHttpClient.Builder 를 빈으로 선언해서 InfluxDB를 사용했다면 이제는 InfluxDbOkHttpClientBuilderProvider 이용해서 사용하는 것은 권장한다. 아마도 다음 버전에는 지원하지 않을 예정으로 보인다. 현재 Spring boot 2.1에서 OkHttpClient.Builder 빈으로 등록해 사용한다면 (InfluxDB를) 경고 로그가 출력 될 것이다.

Spring Version POM Property

spring-boot-dependencies에 정의되어 있는 spring.version 프로퍼티가 변경되었다. 이제는 spring-framework.version을 프로퍼티로 사용해야 한다.

<spring-framework.version>5.0.0.RELEASE</spring-framework.version>

하지만 이것은 권장하지 않는다. Spring boot가 지원해주는 Spring version을 사용하는 것을 더욱 권장하고 있다.

Removal of ‘spring.provides’ Files

spring.provides 파일들이 사라졌다. 이전에는 STS, 다른 IDE들의 의존성들을 파악 할 수 있었으나 starter POM으로 스캐닝이 충분하기 때문에 사라졌다. 맞나?

Thymeleaf Spring Security Extras

thymeleaf-extras-springsecurity4 에서 thymeleaf-extras-springsecurity5 로 업데이트 되었다. 만약 이 모듈을 사용하고 있다면 thymeleaf-extras-springsecurity5로 변경 해야 된다.

Json Simple

json-simple가 이제는 의존성 관리에서 제거 되었다. 또한 json-simple 를 사용하는 JsonParser의 구현체(JsonSimpleJsonParser)도 사라졌다.

Jersey 1

Jersey 1은 이제 더이상 지원하지 않는다 Jersey 2로 업그레이드 해야 한다.

Endpoint ID names

자신만의 Endpoint를 만들 경우 Id를 좀 더 엄격하게 체크한다. 예를들어 id는 영숫자이어야 하며 문자로 시작해야 한다. 만약 숫자로 시작할 경우 에러가 발생한다.
또 한 -, . 특수문자를 사용할 경우 경고 메세지가 출력 된다. 해당 클래스는 EndpointId를 이용하니 참고하면 되겠다.

Third-party Library Upgrades

다들 아시다피시 Spring boot 2.1은 Spring Framework 5.1을 기반으로 하고 있다. 특별한 경우가 아니라면 Spring boot 가 명시해준 Spring Framework를 사용하길 권장한다.
– Tomcat 9
– Undertow 2
– Hibernate 5.3
– JUnit 5.2
– Micrometer 1.1

기타 등이 업그레이드 되었다.

Java 11 Support

Sprig boot 2.1은 java8 도 지원하지만 java10, java11 까지도 지원한다.

DataSize Support

이거슨 옛날 포스팅 참고

Context ApplicationConversionService Support

ApplicationConversionService 에 DataSize 컨버터 도 추가 되었다. 그래서 아래와 같이 사용할 수 있다.

@Value("${my.duration:10s}")
private Duration duration;

이것 역시 위의 포스터 참고
– 수정: 실제 이내용은 아닌데 왜 내가 이런내을 썼나 의문이다.(아마도 바로 위에 DataSize 가 나와서 그런듯 싶다.) 근데 문서도 조금 의아한게 ApplicationConversionService 클래스는 spring boot 2.0 부터 추가 된 클래스이고 DataSizeConverter 가 2.1 부터 추가된 것도 사실이다. 근데 Duration을 언급한게 조금..

Profile Expression

Profile 표현식이 좀 더 향상 되었다. 예를들어 다음과 같은 표현식을 사용할 수 있다.
production &amp; (us-east | eu-central) 프로덕션이 활성화 되어있고 us-east, 또는 eu-central 활성화 되었을 경우 일치하는 표현식이 된다. 해당 Profile을 파싱하는 클래스는 ProfilesParser이다. 참고 하면 되겠다.

Task Execution

이제 Spring boot 는 ThreadPoolTaskExecutor를 자동설정을 제공해준다. @EnableAsync을 사용할 경우 이전에는 SimpleAsyncTaskExecutor 가 빈으로 등록 되었지만 이제는 ThreadPoolTaskExecutor가 기본적으로 빈으로 등록 된다. spring.task.execution 프로퍼티를 이용해서 속성들을 변경 할 수 있다. 또한 TaskExecutorBuilder 클래스로 좀 더 쉽게 설정 할 수 있다.
– 수정: 실제 기본적으로는 SimpleAsyncTaskExecutor 클래스가 빈으로 등록 되지 않는다.

Task Scheduling

위와 동일하게 ThreadPoolTaskScheduler가 자동설정을 제공해준다. 둘다 모두 해당 어노테이션을 선언해야 한다. Task Scheduling 은 @EnableScheduling 어노테이션을 설정 후 spring.task.scheduling 프로퍼티로 속성들을 변경 할 수 있다. 이 역시 TaskSchedulerBuilder를 제공해 준다.

Logging Groups

Logging을 그룹 별로 묶어서 선언할 수 있다. 아까 위에서 봤던 내용이다. 해당 그룹은 사용자들이 직접 만들 수 도 있다.

logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat
logging.level.tomcat=TRACE

위와 같이 org.apache.catalina, org.apache.coyote, org.apache.tomcat
l 패키지를 tomcat 그룹으로 묶었다. 그 후 그룹을 TRACE 레벨로 로그를 출력하겠다는 의미이다.

Bootstrap mode for JPA setup

Bootstrap mode 가 추가 되었다. 아마도 JPA Repsitories를 초기화 지연을 시킬지 결정하는 설정같다. 하지만 필자는 잘 모르겠다. 이정도까지..
해당 설정은 다음과 같이 하면 된다.

spring.data.jpa.repositories.bootstrap-mode=deferred

deferred 말고도 lazy 도 존재하지만… 별도의 스레드에서 동작한다고 하니 참고하면 되겠다. deferred 와 lazy 차이는 잘 모르겠다.

Kafka Streams Support

Kafka Streams를 지원한다. 해당 자동 클래스는 KafkaStreamsAnnotationDrivenConfiguration 참고하면 되겠다.

Spring Data JDBC Support

Spring Data JDBC를 지원한다. spring-boot-starter-data-jdbc 디펜더시를 받아 사용하면 된다.

JUnit 5

slice test를 Junit5 이용해서 테스트를 진행한다면 @ExtendWith(SpringExtension.class) 어노테이션을 작성하지 않아도 된다. 이미 각 slice test에 해당 어노테이션이 메타 어노테이션으로 작성되어 있기 때문이다.

Actuator Endpoints

actuator endpoint들이 추가 되었다. Caches Endpoint, Spring Integration Graph Endpoint, Health Endpoint 이 추가 및 변경 되었다. 각 엔드포인트들은 해당 문서를 참고 하면 되겠다.

기타 Micrometer, 잡다한 내용들이 변경이 되었으니 이 글은 참고만 하고 해당 공식 문서를 참고하길 바란다.
오늘은 이상으로 Spring boot 2.1 릴리즈 변화에 대해서 알아봤다.

elasticsearch local 로 띄어보기

오늘 작성할 내용은 elasticsearch 소스를 직접 받아서  local(IDEA) 로 띄어보는 것을 해보도록 하자. 딱히 어렵거나 복잡하진 않지만 몇가지 할 작업이 있기에 작성해보도록 하자. 

기준은 mac과 intellij, elasticsearch 버전은 6.4  버전 gradle 4.9 기준으로 설명한다.

git clone https://github.com/elastic/elasticsearch.git

특정 작업할 공간에서 위와 같이 elasticsearch 를 받아오자. 물론 IDEA에서 직접 받아와도 된다. 

elasticsearch 를 받아왔다면 다음과 같이 커멘드창에 작성해야 한다.

cd elasticsearch
git checkout 6.4
gradle idea

저렇게 작성했는데 필자는 다음과 같은 에러가 발생하였다.

> Failed to apply plugin [id 'elasticsearch.build']
   > the environment variable JAVA_HOME must be set to a JDK installation directory for Java 1.10 but is [/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home] corresponding to [1.8]

정확한 이유는 모르겠으나 기존에 설치되어 있던 java10으로 변경하였다. java10이 설치 되어 있어 그런지.. 원래 6.4버전은 java8 부터 가능하다고 문서에 나와있는데. java8보다 높은 버전이 있어서 그런지 몰라도 java10으로 바꾸니 잘된다.

gradle idea 가 끝나면 elasticsearch 를 IDEA로 띄어보자. 그 후에 gradle를 리플레시 해보면 소스들이 모듈별로 잘 나눠져 있을 것이다. 

이정도만 해도 일단 반은 끝났다. 솔직히 여기까지 오는데만 해도 시간이 꽤 걸렸다. elasticsearch 자체만 빌드하는 것만으로도 오래걸린다. 소스가 워낙 많아서..

실제 서버를 돌리는 모듈은 server 라는 모듈에 있다.

캡쳐가 왜 이렇게 크지.. 

아무튼 server라는 모듈에 src/main/java/org/elasticsearch/bootstrap/Elasticsearch 보면 main 함수가 존재한다. 그걸 이용해 서버를 돌리면 된다. 한번 Run을 해보자. 아마도 안될 것이다. 이렇게 쉽게 된다면 굳이 이 글을 쓸필요가 없었을 것이다. 

일단 돌려보면 아래와 같은 메세지가 나온다.

ERROR: the system property [es.path.conf] must be set

es.path.conf 라는 프로퍼티가 필요 하다는 것이다. 일단 설정 파일이 필요한데 그 파일은 distribution/src/config 폴더 밑에 elasticsearch.yml, jvm.options, log4j2.properties 복사하여 설정할 경로에 복사하면 된다. 물론 이것을 그대로 `es.path.conf` 넣어도 상관은 없다. 하지만 필자는 따로 폴더를 만들어서 거기에 복사해 넣었다. elasticsearch.yml 보면 ${path.data} 와 같은 문자들이 있는데 지금은 필요 없다. 다 삭제해도 된다.

그 후에 다음과 같이 intellij 에 vm options으로 해당 프로퍼티에 경로를 작성해준다. 

다시 한번 Run 버튼을 눌러보자. 이번은 다른 에러가 발생한다.

Exception in thread "main" java.lang.IllegalStateException: path.home is not configured
	at org.elasticsearch.env.Environment.<init>(Environment.java:103)
	at org.elasticsearch.env.Environment.<init>(Environment.java:94)
	at org.elasticsearch.node.InternalSettingsPreparer.prepareEnvironment(InternalSettingsPreparer.java:86)
	at org.elasticsearch.cli.EnvironmentAwareCommand.createEnv(EnvironmentAwareCommand.java:95)
	at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:86)
	at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:124)
	at org.elasticsearch.cli.Command.main(Command.java:90)
	at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:93)
	at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:86)

이번엔 path.home이 없다는 에러이다. 할일이 많다. path.home 도 역시 원하는 폴더를 만들어 작성하면 된다.

필자는 다음과 같이 작성하였다.

이제 다시 한번 돌려보자.  다시 돌려보면 일단 많은 에러가 발생한다. 한개씩 해결해보자.

org.elasticsearch.bootstrap.StartupException: java.lang.IllegalStateException: modules directory [/Users/wonwoo/elastic/modules] not found

첫 번째로 위와 같은 에러가 발생한다. 해당 설정 폴더에 modules  이라는 폴더를 찾을 수 없다는 것이다. 이 에러는 git으로 다운 받은 모듈들을 복사해서 넣어도 되고 아니면 실제 elastic  사이트가서 elasticsearch 를 다운받은 후에 모듈폴더만 가져와도 된다. 필자의 경우에는  elasticsearch 사이트가서 다운받은 후 해당 폴더에 넣어 두었다.  참고로 다운받는 것을 추천한다. git으로 clone한 모듈은 intellij 관련한 파일들도 있어 잘 파싱이 안되는 것 같았다. 

위와 같이 /Users/wonwoo/elastic/modules 폴더안에 elasticsearch 모듈들을 넣었다.  위와 같이 폴더를 넣고 다시 돌려보자. 

이제는 다음과 같은 에러가 발생한다.

main ERROR Could not register mbeans java.security.AccessControlException: access denied ("javax.management.MBeanTrustPermission" "register")

이 것은 정확한 원인은 잘 모르겠지만 해결 방법은 존재한다. 아마도 권한이 없어서 그런거 같은데.. 저것은 그냥 -Dlog4j2.disable.jmx=true log4j2의 jmx를 disable로 하는 것으로 끝냈다. 하지만 정확한 원인은 필자도 잘.. 

다시 한번 돌려보자. 그럼 아래와 같은 에러가 또 발생한다. 이놈의 에러는 언제쯤..

java.lang.NoClassDefFoundError: org/elasticsearch/plugins/ExtendedPluginsClassLoader

ExtendedPluginsClassLoader  클래스가 정의가 안되어 있다고 에러가 발생한다. 이것은 intellij 와 gradle 과의 관련이 있는 듯 하다. intellij에서 Edit Configurations 에 들어가 Include dependencies with "Provided" scope 를 다음과 같이 체크해 준다.

그 후에 다시 Run을 해보자. 

아마도 마지막 에러일 듯 싶다.

org.elasticsearch.bootstrap.StartupException: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "createClassLoader")

이 것 역시 권한과 관련이 있어보인다.  이것의 해결방법은 ${JAVA_HOME}/conf/security/java.policy 파일을 열어서 다음과 같이 추가해주면 된다.

permission java.lang.RuntimePermission "createClassLoader";

이제 마지막으로 서버를 돌려보자. 그럼 아무 에러없이 정상적으로 서버가 올라가는 것을 볼 수 있다. 

정상적으로 서버가 동작하는지 확인하기 위해 다음과 같이 커멘드창에 입력하거나 브라우저를 띄워서 확인해보자.

$ http :9200
curl http://localhost:9200

{
    "cluster_name": "elasticsearch",
    "cluster_uuid": "ierbMoD4SDmXbY9xbsrQng",
    "name": "fcsWvlW",
    "tagline": "You Know, for Search",
    "version": {
        "build_date": "Unknown",
        "build_flavor": "unknown",
        "build_hash": "Unknown",
        "build_snapshot": true,
        "build_type": "unknown",
        "lucene_version": "7.4.0",
        "minimum_index_compatibility_version": "5.0.0",
        "minimum_wire_compatibility_version": "5.6.0",
        "number": "6.4.1"
    }
}

그럼 위와 같이 정상적으로 response가 나온것을 확인할 수 있다. 

오늘은 이렇게 elasticsearch 소스를 받아서 로컬로 띄어보는 작업을 해봤다. 별거 아닐줄 알았는데 해보니까 시간이 꽤 걸렸다. 혹시나 elasticsearch에 기여할 것이 있다면 위와 같이 해당 소스를 받아서 테스트를 해보도록 하자. 

그럼 오늘은 이만!