thrift의 자바 버전인 swift 를 이용할 때, enum 클래스 에 , 를 추가했다가 에러가 난 상황이 있어서 공유한다.

사실 이외에도 이 에러는 client와 server 간의 enum 클래스의 내용이 맞지 않을 때 발생할 수 있다. 따라서 특별히 조심해야 한다. Thrift 의 enum은 이렇게 문제가 잘 발생한다. 



swift에 쓰이는 enum 클래스 예시이다.


public enum ModelType {

  A(1),

  B(2),

;

}


B(2), 를 사용했는데.  runtime 초기화 때, 실패하는 Exception이 발생했다.


java.lang.IllegalArgumentException: Enum class ModelType does not have a value for 55

        at com.facebook.swift.codec.internal.EnumThriftCodec.read(EnumThriftCodec.java)

        at com.facebook.swift.codec.internal.EnumThriftCodec.read(EnumThriftCodec.java)



즉 맨 마지막 요소에 , 를 사용하면 다음 요소(enum 값)으로 판단하는 로직이 있다. 



https://github.com/facebook/swift/blob/master/swift-codec/src/main/java/com/facebook/swift/codec/internal/EnumThriftCodec.java




    @Override

    public T read(TProtocol protocol)

            throws Exception

    {

        int enumValue = protocol.readI32();

        if (enumValue >= 0) {

            if (enumMetadata.hasExplicitThriftValue()) {

                T enumConstant = enumMetadata.getByEnumValue().get(enumValue);

                if (enumConstant != null) {

                    return enumConstant;

                }

            }

            else {

                T[] enumConstants = enumMetadata.getEnumClass().getEnumConstants();

                if (enumValue < enumConstants.length) {

                    return enumConstants[enumValue];

                }

            }

        }

        // unknown, throw unknown value exception

        throw new UnknownEnumValueException(

                String.format(

                        "Enum %s does not have a value for %s",

                        enumMetadata.getEnumClass(),

                        enumValue

                )

        );

    }



더 이상 문제가 발생하지 않도록, 맨 마지막 컴마를 삭제하고 배포하니 문제가 없다. 


public enum ModelType {

  A(1),

  B(2)

;

}


Posted by '김용환'
,



jenkins job을 shell (또는 command util)로 실행할 때, exit(또는 return)을 활용하면 실패여부를 알릴 수 있다.


bash script를 쓴다면, exit 0만 성공이고 exit 나머지 값(예, exit 1, exit -1)로 종료되면, jenkins에서 실패로 표시할 수 있다.

Posted by '김용환'
,



spring boot 1.3이 출시되었다. 

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.3-Release-Notes


property가 바뀌었고, spring 4.2, spring security 4.0, cassandra, oauth2, jooq, HTTP persistent session 을 지원한다.


내가 볼 때 가장 좋은 것으로 spring-reload 대신 spring-devtool이다.

CoolReload(LiveReload)를 지원하고 Http세션도 잘 동작한다.


Developer Tools

Spring Boot 1.3 includes a new spring-boot-devtools module which aims to improve the development-time experience. The module provides:

  • Sensible property defaults (for example disabling template caches)

  • Automatic application restarts

  • LiveReload support

  • Remote development support (including remote updates and remote debug via an HTTP tunnel).

  • Persistent HTTP sessions across restarts

See the updated documentation for more information.



cache auto-configuration을 지원한다.


Caching Auto-configuration

Auto-configuration is now provided for the following cache technologies:

  • EhCache

  • Hazelcast

  • Infinispan

  • Any compliant JCache (JSR 107) implementation

  • Redis

  • Guava




리눅스에서 서비스로 쓸 수 있다. 


Fully executable JARs and service support

The Spring Boot Maven and Gradle plugins can now generate full executable archives for Linux/Unix operating systems. Furthermore you can now easily install these JARs asinit.d or systemd services. Running a fully executable JAR is as easy as typing:

$ ./myapp.jar

and to install it as an init.d service:

$ sudo link -s /var/myapp/myapp.jar /etc/init.d/myapp

Additional information is available in the reference documentation.





임베디드 servlet 애노테이션을 제공한다.


Support for @WebServlet, @WebFilter and @WebListener

When using an embedded servlet container, automatic registration of @WebServlet,@WebFilter and @WebListener annotated classes can now be enabled using@ServletComponentScan.



몽고DB 테스트 쉽게 테스트할 수 있도록 embedded할 수 있다.

http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-mongo-embedded



배너도 쉽게 만들 수 있다. 


ANSI color banner.txt files

You can now use ANSI placeholders in your banner.txt file to produce color output. Any${Ansi.}${AnsiColor.}${AnsiBackground.} or ${AnsiStyle.} properties will be expanded. For example:

${AnsiColor.BRIGHT_GREEN}My Application 

${AnsiColor.BRIGHT_YELLOW}${application.formatted-version}${AnsiColor.DEFAULT}



CommandLineRunner 대신 ApplicationRunner로 바뀌었다.


Application arguments

You can now implement the ApplicationRunner interface as an alternative toCommandLineRunner. This works in the same way but provides arguments as aApplicationArguments interface rather than a String[]. You can also injectApplicationArguments directly into any existing bean if you need to access the application arguments.

The ApplicationArguments interface provides convenience methods for accessing "option" and "non-option" arguments. For example:

@Autowired
public MyBean(ApplicationArguments args) {
    boolean debug = args.containsOption("debug");
    List<String> files = args.getNonOptionArgs();
    // if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
}




ObjectMappers 로 바인딩된 jackson 모듈만 사용한다.

Spring Boot 1.3 will only register Jackson Module beans withObjectMappers that are created or configured with the auto-configuredJackson2ObjectMapperBuilder






Posted by '김용환'
,

[spring boot] undertow

general java 2015. 10. 30. 18:36

undertow를 spring boot에서 쓰려면 다음과 같이 사용할 수 있다.


1. maven (pom.xml)

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>



2. gradle (


dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
compile ("org.springframework.boot:spring-boot-starter-undertow") {
exclude group: "io.undertow", module: "undertow-websockets-jsr"
} .. }



spring boot에서 undertow를 실행하면 tomcat에 비해 1/2정도로 빨리 실행된다. 그 이유는 undertow는 netty 기반이다. 최신 undertow는 netty 4.1을 기반으로 하고 있다. JBOSS에서 밀고 있는 웹 프레임워크이다. 쓰기 편하게 잘 감싸놨다. 



참조 : http://blog.arungupta.me/undertow-benchmarks-wildfly/


사실 undertow가 netty 를 기반으로 되어 있기 때문에 거의 성능이 비슷하다. 그동안 웹 컨테이너가 tomcat, jetty를 쓰고 있었는데, 상용 서비스로 undertow로 써봐도 안정적이고 괜찮은 것 같다. 



* 참고

https://github.com/undertow-io/undertow

http://undertow.io/


Posted by '김용환'
,


아래 코드를 실행하면 몇 개의 cpu가 동작하는지 확인해봤다.

List<String> deletedCid = allIds.parallelStream().filter(this::isDeleted).collect(Collectors.toList());

cpu 개수만큼 thread가 만들어진다. 

jstack 으로 확인해보니.특히 Pool은 ForjJoinPool을 사용한다.


cpu가 8개이면, 아래와 같이 생성한다.


"ForkJoinPool.commonPool-worker-1"

...

"ForkJoinPool.commonPool-worker-8"


만약 thread 개수를 늘리고 싶다면,  java.util.concurrent.ForkJoinPool.common.parallelism 값으로 조절한다. 


-Djava.util.concurrent.ForkJoinPool.common.parallelism=16


Posted by '김용환'
,


spring boot(1.3.0)를 이용해서 mongodb(mongo 3.0.6)를 사용하고 있다. 

mongo 터미널을 이용해서 mongodb에서 색인 추가/삭제하는 법을 알고, jpa 연동시 주의사항을 본다.



1. mongodb에서 색인 추가/삭제하기



* 참고 자료 : http://docs.mongodb.org/manual/core/index-creation/


customer 콜렉션에 색인 추가하기

 

replset:PRIMARY> use test

switched to db test

replset:PRIMARY> db.customer.save({id:1, firstName:'samuel', lastName:'kim'})

WriteResult({ "nInserted" : 1 })

replset:PRIMARY> db.customer.save({id:2, firstName:'daniel', lastName:'park'})

WriteResult({ "nInserted" : 1 })

replset:PRIMARY> db.customer.ensureIndex({firstName:1})

{

"createdCollectionAutomatically" : false,

"numIndexesBefore" : 1,

"numIndexesAfter" : 2,

"ok" : 1

}

replset:PRIMARY> db.customer.indexes.find()

replset:PRIMARY> db.customer.find()

{ "_id" : ObjectId("5601058d3004b0f26a3fd806"), "_class" : "com.kakao.story.gateway.model.Customer", "firstName" : "Alice", "lastName" : "Smith" }

{ "_id" : ObjectId("5601058d3004b0f26a3fd807"), "_class" : "com.kakao.story.gateway.model.Customer", "firstName" : "Bob", "lastName" : "Smith" }

{ "_id" : ObjectId("561635bdc95ab6bd4d165508"), "id" : 1, "firstName" : "samuel", "lastName" : "kim" }

{ "_id" : ObjectId("561635d3c95ab6bd4d165509"), "id" : 2, "firstName" : "daniel", "lastName" : "park" }

replset:PRIMARY> db.customer.getIndexes()

[

{

"v" : 1,

"key" : {

"_id" : 1

},

"name" : "_id_",

"ns" : "test.customer"

},

{

"v" : 1,

"key" : {

"firstName" : 1

},

"name" : "firstName_1",

"ns" : "test.customer"

}

]




customer 콜렉션에 추가했던 색인을 삭제한다.


replset:PRIMARY> db.customer.dropIndex({'firstName':1})

{ "nIndexesWas" : 2, "ok" : 1 }

replset:PRIMARY> db.customer.getIndexes()

[

{

"v" : 1,

"key" : {

"_id" : 1

},

"name" : "_id_",

"ns" : "test.customer"

}

]




2. jpa 모델 연동 사항

Customer라는 클래스를 저장 모델로 하여 JPA로 사용하고 있다고 가정한다.

Customer 클래스에 id를 주어 간단하게 모델로 저장할 때는 아래와 같이 사용할 수 있다. 

public class Customer {

@Id
private String id;

private String firstName;

private String lastName; //... }

문제는 mongodb의 Customer 콜렉션에 새로운 색인을 만들기 위해서 @Indexed를 firstName 필드에 추가하면 색인에 추가되지 않는다.
import org.springframework.data.mongodb.core.index.Indexed;

public class Customer {

@Id
private String id;

@Indexed
private String firstName;

private String lastName; //... }



getIndexes()와 stats()을 호출했을 때 index 상황을 볼 수 있는데, 여전히 색인은 id만 되어 있다.
replset:PRIMARY> db.customer.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "staticmap.customer"
}
]

replset:PRIMARY> db.customer.stats()
...
"nindexes" : 2,
...


@Indexed의 정상 동작을 하도록 하려면, 클래스 앞에 반드시 @Document를 추가해야 한다. 그리고 mongo 터미널에서 확인하면 정상적으로 동작하는지 확인할 수 있다.

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class Customer {

@Id
private String id;

@Indexed
private String firstName;

private String lastName; //... }


replset:PRIMARY> db.customer.getIndexes()

[

{

"v" : 1,

"key" : {

"_id" : 1

},

"name" : "_id_",

"ns" : "staticmap.customer"

},

{

"v" : 1,

"key" : {

"_fts" : "text",

"_ftsx" : 1

},

"name" : "Customer_TextIndex",

"ns" : "staticmap.customer",

"weights" : {

"firstName" : 1

},

"default_language" : "english",

"language_override" : "language",

"textIndexVersion" : 2

}

]


replset:PRIMARY> db.customer.stats()
...
"nindexes" : 2,
...



Posted by '김용환'
,


spring boot 애플리케이션으로 개발하는 웹 서버를 다음과 같이 구성할 수 있다.



nginx 설정시 l7check url을 명세하고, 이를 web application에서 처리하도록 설정한다.


server {

    location /l7check.html {

        access_log  off;

        allow       all;


        proxy_pass http://localhost:8080/health;

        proxy_connect_timeout 1s;

        proxy_read_timeout 1s;

        proxy_send_timeout 1s;

    }

}



HeathChecker라는 Controller를 만들어서 배포시 start,stop할 때마다 호출하여 l7check를 제어할 수 있도록 만들었다.

/l7check/true, /l7check/false url을 주어 health check 결과를 build하도록 하였다. 참고로, spring boot는 health check down시는 503 에러를 발생하도록 되어 있어 l7check를 아주 손쉽게 처리할 수 있다. 

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HealthChecker implements HealthIndicator {

private Boolean isActivating = true;

@Override
public Health health() {
Boolean isOk = check();
if (!isOk) {
return Health.down().withDetail("Error Code", 10000).build();
}
return Health.up().build();
}

public Boolean check() {
return isActivating;
}

@RequestMapping(value="/l7check/{value}", method = RequestMethod.GET)
@ResponseBody
public Boolean setActivating(@PathVariable Boolean value) {
this.isActivating = value;
return this.isActivating;
}

}


Posted by '김용환'
,


2014년 spring batch에서 spring retry로 따로 분리되었다. Spring 4이상에서 사용할 수 있다.

(계속 유지보수는 안되고 있는 느낌이지만 유틸리티의 성격이 있어서 간단한 코드에서는 계속 쓸 수 있을 듯 싶다.)



https://github.com/spring-projects/spring-retry

예제만 봐도 깔끔한 느낌이다.

@Configuration
@EnableRetry
public class Application {

    @Bean
    public Service service() {
        return new Service();
    }

}

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}



Retry를 간단하게 아래와 같이 코딩할 수 있다. 

RetryTemplate template = new RetryTemplate();

template.setRetryPolicy(new SimpleRetryPolicy(2));

final ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();

backOffPolicy.setInitialInterval(20L);

template.setBackOffPolicy(backOffPolicy);

 

template.execute(new RetryCallback<String>() {

  @Override

  public String doWithRetry(final RetryContext context) throws Exception {

    return "";

  }

});


괜찮은 코드는 아래 코드를 추천한다.

https://dzone.com/articles/spring-retry-ways-integrate

http://www.java-allandsundry.com/2014/12/spring-retry-ways-to-integrate-with.html

https://github.com/bijukunjummen/test-spring-retry


간단하게 테스트를 할 수 있는 예제로 테스트해봤다. 2 번 retry를 바로 하는 구조의 호출을 다음과 같이 개발해볼 수 있다. 

public interface RemoteCallService {
@Retryable(maxAttempts = 2, backoff = @Backoff(delay = 0))
String call(String url);
}


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SpringRetryTest {

@Autowired
private RemoteCallService remoteCallService;

@Test
public void testRetry() {
String message = this.remoteCallService.call("http://social.google.com");
verify(remoteCallService, times(2)).call("http://social.google.com");
System.out.println(message);
assertThat(message, is("OK"));
}

@Configuration
@EnableRetry
public static class SpringConfig {

@Bean
public RemoteCallService remoteCallService() throws Exception {
RemoteCallService remoteService = mock(RemoteCallService.class);
when(remoteService.call("http://social.google.com"))
.thenThrow(new RuntimeException("Remote Exception 1"))
.thenReturn("OK");
return remoteService;
}
}
}


Spring RestTemplate의 retry를 spring retry를 이용해서 개발해보았더니. 괜찮아 보였다. 테스트 코드까지 깔끔해진다. 


Posted by '김용환'
,


spring 3.0부터 사용할 수 있는 p, c notation 예제를 RestTemplate으로 설명하는 예제이다.


p는 property, c는 constructor argurment를 의미하는 노테이션이다. 여러 줄에 걸쳐 쓰면 verbose한 느낌이 난다.

그래서 notation을 써보니 깔끔한 느낌이 난다. 


ImageManager라는 클래스를 RestTemplate을 생성하는 예제이다. applicationContext.xml 파일의 timeout 설정을 살펴본다.

public class ImageManager implements InitializingBean {
private RestTemplate restTemplate;
public ImageManager(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
} //... }


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<beans> <bean id="imageRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory" p:readTimeout="250" p:connectTimeout="250" p:connectionRequestTimeout="250"/>
<bean id="imageRestTemplate" class="org.springframework.web.client.RestTemplate" c:requestFactory-ref="imageRequestFactory"/>
<bean id="imageManager" class="com.google.social.util.ImageManager">
<constructor-arg index="0" ref="imageRestTemplate" />
</bean> </beans>



Posted by '김용환'
,


spring app을 실행하다가 아래 에러가 발생해서 app 실행이 되지 않으면,

.m2/repository를 삭제하고 다시 maven(gradle) compile을 실행시킨다.


java.lang.IllegalStateException: LifecycleProcessor not initialized - call 'refresh' before invoking lifecycle methods via the context: Root WebApplicationContext: startup date root of context hierarchy



Posted by '김용환'
,