Json을 파싱할 수 있는 ObjectMapper 예시를 소개한다.  


많이 알려져 있지만, 자주 실수할 수 있는 내용과  좋은 팁을 소개한다. 





먼저, UnrecognizedPropertyException을 많이 발생시킬 수 있는 내용을 소개한다. 


아래는 무난하게 잘 동작하는 코드이다. 


import java.util.List;

import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonTest {

  @Test

  public void mapperObject() throws Exception {

  ObjectMapper mapper = new ObjectMapper();

  String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\"}";

  EventData eventData = mapper.readValue(jsonInString, EventData.class);

  System.out.println(eventData);

  }

}

 

class EventData {


  @JsonProperty("event_code")

  private int eventCode;


  @JsonProperty("name")

  private String name;


  @JsonProperty("messages")

  private List<String> messages;

}




이 코드가 정상적으로 잘 동작하려면, json이 EventData에 맞게 오지 않거나 최소한 해당 json 노드이외에는 전달되지 않는다는 보장이 있어야 한다. 


갑자기 json 문자열에 새로운 노드가 새로 추가되면, com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException이 발생한다.


String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";




예외 내용은 다음과 같다. 


com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "emp" (class com.kakao.story.util.EventData), not marked as ignorable (3 known properties: "event_code", "name", "messages"])

 at [Source: {"event_code":112233,"messages":["hello","java"],"name":"x112x1121", "emp":"samuel"}; line: 1, column: 77] (through reference chain: com.kakao.story.util.EventData["emp"])

at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)

at 





예외를 발생시키지 않고 안전하게 해결하려면 @JsonIgnoreProperties(ignoreUnknown = true)을 클래스 정의에 둔다. 다른 방법도 있지만, 이 방식이 아주 많이 사용되는 방식이다.



import java.util.List;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";

EventData eventData = mapper.readValue(jsonInString, EventData.class);

System.out.println(eventData);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

class EventData {


@JsonProperty("event_code")

private int eventCode;


@JsonProperty("name")

private String name;


@JsonProperty("messages")

private List<String> messages;

}




참고로, 코드는 문제가 없어보이지만, org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "event_code" (Class com.kakao.story.util.EventData), not marked as ignorable 예외가 발생할 수 있다.


ObjectMapper 클래스의 fully qualified name을 유심히 살펴본다. 


import org.codehaus.jackson.map.ObjectMapper;


import java.util.List;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.DeserializationConfig;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

String jsonInString = "{\"event_code\":112233,\"messages\":[\"hello\",\"java\"],\"name\":\"x112x1121\", \"emp\":\"samuel\"}";

EventData eventData = mapper.readValue(jsonInString, EventData.class);

System.out.println(eventData);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

class EventData {


@JsonProperty("event_code")

private int eventCode;


@JsonProperty("name")

private String name;


@JsonProperty("messages")

private List<String> messages;

}



import com.fasterxml.jackson.databind.ObjectMapper를 사용해야지 org.codehaus.jackson.map.ObjectMapper를 사용하면 원인모를 미궁에 빠질 수 있다. 






또 다른 팁은 serialization시 null에 대한 핸들링이다. 아래 코드를 살펴본다.



import java.util.List;


import org.apache.commons.lang.builder.ToStringBuilder;

import org.apache.commons.lang.builder.ToStringStyle;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

EventData eventData = new EventData(); 

eventData.eventCode = 123123;

eventData.name = "wang.lee";

String jsonString = mapper.writeValueAsString(eventData);

System.out.println(jsonString);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

class EventData {


@JsonProperty("event_code")

int eventCode;


@JsonProperty("name")

String name;


@JsonProperty("messages")

List<String> messages;

@Override

public String toString() {

return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);

}

}


실행하면, 저장 안한 키의 값을 null로 serialization한다. 


{"event_code":123123,"name":"wang.lee","messages":null}


최적화된 json 정보를 유지하기 위해 null 정보를 뺄 수 있다.




Pojo 클래스에 @JsonInclude(Include.NON_NULL)을 추가하면, null 정보를 뺄 수 있다.

import java.util.List;


import org.apache.commons.lang.builder.ToStringBuilder;

import org.apache.commons.lang.builder.ToStringStyle;

import org.junit.Test;


import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.annotation.JsonInclude;

import static com.fasterxml.jackson.annotation.JsonInclude.Include;


public class JsonTest {

@Test

public void mapperObject() throws Exception {

ObjectMapper mapper = new ObjectMapper();

EventData eventData = new EventData(); 

eventData.eventCode = 123123;

eventData.name = "wang.lee";

String jsonString = mapper.writeValueAsString(eventData);

System.out.println(jsonString);

}

}

@JsonIgnoreProperties(ignoreUnknown = true)

//@JsonInclude(Include.NON_NULL)
@JsonInclude(value=Include.NON_ABSENT, content=Include.NON_EMPTY)

class EventData {


@JsonProperty("event_code")

int eventCode;


@JsonProperty("name")

String name;


@JsonProperty("messages")

List<String> messages;

@Override

public String toString() {

return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);

}

}


결과는 다음과 같다. 


{"event_code":123123,"name":"wang.lee"}



Java8과 guava에서도 쓸 수 있게 @JsonInclude(value=Include.NON_ABSENT, content=Include.NON_EMPTY)를 사용했다. java7이하에서는 @JsonInclude(Include.NON_NULL)를 사용할 수 있을 것이다. 





참고로 @JsonInclude에 대해 설명하면 다음과 같다.


Include.NON_ABSENT는 값이 null이나 java8, guava의 optional의 내부값이 null일 때 serialization하지 않는다.

Include.ALWAYS는 값과 상관없이 serialization한다.

Include.NON_DEFAULT는 값이 기본 설정과 다른 경우 속성이 serialization한다.

Include.NON_EMPTY는 값이 null이 아니거나 empty하지 않으면 serialization한다.

Include.NON_NULL는 null이 아닐 때만 serialzation한다.






Posted by 김용환 '김용환'


google의 guava의 hash 함수가 존재한다.


패키지명은 com.google.common.hash.Hashing이다.


아주 친절하게도 http://goo.gl/jS7HH에 잘 저장되어 있어서 어느 것을 선택할 지 알려주고 있다!!





adler32, crc32,sha256 등등의 경우는 추천하지 않고 있다. Not stable것도 있으니 노트를 잘 보고 용도에 맞게 써야 할 것 같다. 


예를 들어, Hashing.murmur3_32는 다음 메소드를 가지고 있다. 


  /**

   * Returns a hash function implementing the

   * <a href="http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp">

   * 32-bit murmur3 algorithm, x86 variant</a> (little-endian variant),

   * using the given seed value.

   *

   * <p>The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A).

   */

  public static HashFunction murmur3_32(int seed) {

    return new Murmur3_32HashFunction(seed);

  }


   /**

   * Returns a hash function implementing the

   * <a href="http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp">

   * 32-bit murmur3 algorithm, x86 variant</a> (little-endian variant),

   * using a seed value of zero.

   *

   * <p>The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A).

   */

  public static HashFunction murmur3_32() {

    return Murmur3_32Holder.MURMUR3_32;

  }



다음과 같이 사용할 수 있다. 


 com.google.common.hash.HashFunction algorithm = com.google.common.hash.Hashing.murmur3_32(0);

 long b = algorithm.hashBytes("google:plus:201612123:na".getBytes()).padToLong()






참고로 jedis에도 hashing 코드가 존재한다.


https://github.com/xetorthio/jedis/blob/master/src/main/java/redis/clients/util/Hashing.java


다음과 같이 사용할 수 있다. 


redis.clients.util.Hashing algorithm = redis.clients.util.Hashing.MURMUR_HASH;

long b = algorithm.hash(redis.clients.util.SafeEncoder.encode("google:plus:20161223:na"));


Posted by 김용환 '김용환'



jenkins에 job을 생성하고 jenkins의 환경 변수를 스크립트에서 쓸 수 있는 환경 변수를 얻으려면 다음 주소를 살펴본다.



https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project


build number, build url, job name, jenkins url, build tag 등 다양한 환경 변수를 사용할 수 있다. 



주의사항으로.. 장비 이름과 실제 호출되는 도메인 이름이 다를 때는 jenkins url이 의도한 것과 달리 나타날 수 있다.


jenkis url은 node 기준으로 나타나기 때문에 호출시 불리우는 dns name이 아닐 수 있다.  장비 관점의 도메인이 아니라, dns 서버 관점의 도메인이 url로 볼 수 없다. 이는 jenkins 로그인 설정이 활성화되어 있는 경우, 스크립트 내부에서 jenkins url을 사용할 때, 로그인을 다시 해야 하는 상황이 생길 수 있다. 




Environment VariableDescription
BUILD_NUMBERThe current build number, such as "153"
BUILD_IDThe current build id, such as "2005-08-22_23-59-59" (YYYY-MM-DD_hh-mm-ss, defunct since version 1.597)
BUILD_URL 
The URL where the results of this build can be found (e.g. http://buildserver/jenkins/job/MyJobName/666/)
NODE_NAMEThe name of the node the current build is running on. Equals 'master' for master node.
JOB_NAMEName of the project of this build. This is the name you gave your job when you first set it up. It's the third column of the Jenkins Dashboard main page.
BUILD_TAGString of jenkins-${JOB_NAME}-${BUILD_NUMBER}. Convenient to put into a resource file, a jar file, etc for easier identification.
JENKINS_URLSet to the URL of the Jenkins master that's running the build. This value is used by Jenkins CLI for example
EXECUTOR_NUMBERThe unique number that identifies the current executor (among executors of the same machine) that's carrying out this build. This is the number you see in the "build executor status", except that the number starts from 0, not 1.
JAVA_HOMEIf your job is configured to use a specific JDK, this variable is set to the JAVA_HOME of the specified JDK. When this variable is set, PATH is also updated to have $JAVA_HOME/bin.
WORKSPACEThe absolute path of the workspace.
SVN_REVISIONFor Subversion-based projects, this variable contains the revision number of the module. If you have more than one module specified, this won't be set. 
CVS_BRANCHFor CVS-based projects, this variable contains the branch of the module. If CVS is configured to check out the trunk, this environment variable will not be set.
GIT_COMMIT 
For Git-based projects, this variable contains the Git hash of the commit checked out for the build (like ce9a3c1404e8c91be604088670e93434c4253f03) (all the GIT_* variables require git plugin)     
GIT_URLFor Git-based projects, this variable contains the Git url (like git@github.com:user/repo.git or [https://github.com/user/repo.git])
GIT_BRANCH 
For Git-based projects, this variable contains the Git branch that was checked out for the build (normally origin/master) 


Posted by 김용환 '김용환'



Jenkins에서 아래와 같이 job이 실행이 안되고, 계속 기다려야 하는 상황이 있을 수 있다.


(대기열—Waiting for next available executor on worker) 





아마도 화면에서는 이렇게 보일 것이다.





화면에서 추측할 수 있듯이 프로세스 executor 개수 설정에 따라 executor 상태 정보를 볼 수 있다.






jenkins 노드 설정에 # of executors 값이 1 로 설정되어 있다.(화면의 항목은 실행할 수 있는 job executor를 표현한 것이다) 이 값을 큰 값으로 정의한다.


큰 값(예, 20)으로 정의하면, 위 화면에서 3 번째 노드처럼 20개의 executor를 볼 수 있다.









Posted by 김용환 '김용환'





jenkins(1.583)에서 Job 구성시 Build 섹션에서 MultiJob Phase 에서 여러 job을 추가할 수 있다. 

만약 Multijob 실행 중 하나에서 문제가 발생하면, job에서는 다음과 같은 에러가 발생하고, 더 이상 job 실행이 되지 않는다.



<문제 발생한 job 로그>


Build step 'Execute shell' marked build as failure

Finished: FAILURE




<문제 job 때문에 같이 에러가 발생하는 job 로그>


Aborted by anonymous 

jenkins Finished: ABORTED




이 문제를 해결하는 과정을 공유한다.


- Job의 Kill the Phase on 옵션에 Never 를 선택한다.

- Abort all other job이 실행되지 않도록 한다.






이렇게 설정해도  문제가 발생할 수 있다. 




그럴 때는 장비에 접근해서 자바 데몬(slave)이 잘 떠 있는지 확인한다.

보니, slave 데몬이 동시에 두 개가 떠 있다면, 문제 해결의 실마리가 있는 지 확인할 수 있다.


$ ps -ef | grep java

www   11282 11227  0 19:10 pts/0    00:00:00 grep --color=auto java

www   24960 24711  0 16:47 ?        00:00:00 bash -c cd "/var/lib/jenkins" && java  -jar slave.jar

www   24961 24710  0 16:47 ?        00:00:00 bash -c cd "/var/lib/jenkins" && java  -jar slave.jar

www   25002 24960  0 16:47 ?        00:00:31 java -jar slave.jar

www   25003 24961  0 16:47 ?        00:00:36 java -jar slave.jar





아마도 장비 설정을 잘못한 경우이다. 


- jenkins 노드 설정시, 한번 slave agent를 실행한 상태라면, 기존 장비로 연결되어 있다. 따라서, '연결끊기'를 한후, 다시 '연결' 버튼을 누르고, launch slave agent 버튼을 클릭하여 jenkins slave 데몬이 정상적으로 실행될 수 있다. 



Multi job을 실행하는 장비에서 slave데몬이 아래처럼 필요한 데몬만 뜨도록 설정되어 있는지 ps 명령어로 확인한다.



$ ps -ef | grep java

deploy   12347 12225  0 19:22 ?        00:00:00 bash -c cd "/var/lib/jenkins" && java  -jar slave.jar

deploy   12368 12347  8 19:22 ?        00:00:05 java -jar slave.jar

deploy   12985  4801  0 19:23 pts/0    00:00:00 grep --color=auto java


    

이제 더 이상 aborted 에러 메시지를 발생하지 않는다.



Posted by 김용환 '김용환'


play1 framework에서 test 스코프 실행할 수 있다. 

(좀 이상하긴 하지만..)



conf/dependencies.yml를 수정한다. 


    - org.springframework -> spring-test 3.2.11.RELEASE:

            id: test





test 스코드 관련 의존성 파일 읽어온다.


play dependencies --%test --sync

play test



play run 할 때 다음 로그가 출력된다.


~ framework ID is test




다시 원래대로 상용 환경으로 실행하려면, 다음과 같이 실행한다.


play dependencies --sync

play run



* 참고 문서


https://github.com/playframework/play1/blob/master/documentation/manual/ids.textile


By the way, play test is then equivalent to:

play run --%test




* 참고 소스



https://github.com/playframework/play1/blob/master/framework/test-src/play/deps/dependencies_test2.yml


require:

    - play

    - play -> pdf [0.9,)

    - play -> deadbolt 1.5.4

    - play -> cobertura 2.4:

        id: test

    - commons-lang -> commons-lang 2.5

    - commons-codec 1.7

    - net.sf.json-lib -> json-lib 2.4 jdk15

    - crud



https://github.com/playframework/play1/blob/master/samples-and-tests/just-test-cases/conf/application.conf



%test.application.mode=dev

%test.db.url=jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE=0;MV_STORE=false;

%test.jpa.ddl=create


# Second database

%test.db_other.url=jdbc:h2:mem:other;MODE=MYSQL;LOCK_MODE=0

%test.db_other.jpa.ddl=create




https://github.com/playframework/play1/blob/master/framework/test-src/play/LoggerTest.java


  @Before

    public void setUp() throws Exception {

        Play.configuration = new Properties();

        Play.applicationPath = new File(".");

        Play.id="test";   

    }



Posted by 김용환 '김용환'

apache common에 NumberUtils 메소드 중에, to타입이라는 타입 변환 메소드가 있다.

또한 숫자인지 구분하는 것과 간단히 max, min을 구하는 메소드가 있어서 소개한다.



org.junit.Assert.assertEquals(1111L, org.apache.commons.lang.math.NumberUtils.toLong("1111"));

org.junit.Assert.assertEquals(0, org.apache.commons.lang.math.NumberUtils.toLong("111a"));


org.junit.Assert.assertEquals(1.1, org.apache.commons.lang.math.NumberUtils.toDouble("1.1"), 0);

org.junit.Assert.assertEquals(3000, org.apache.commons.lang.math.NumberUtils.toInt("3000"));

org.junit.Assert.assertEquals(0, org.apache.commons.lang.math.NumberUtils.toInt("abc"));


org.junit.Assert.assertEquals(false, org.apache.commons.lang.math.NumberUtils.isDigits("a1"));

org.junit.Assert.assertEquals(true, org.apache.commons.lang.math.NumberUtils.isNumber("111"));


org.junit.Assert.assertEquals(3, org.apache.commons.lang.math.NumberUtils.max(1, 2, 3));

org.junit.Assert.assertEquals(1, org.apache.commons.lang.math.NumberUtils.min(new int[]{1,2,3}));


//모두 통과






그리고, NumberUtils에는 큰 숫자를 만들 수 있는 'create타입'이 있는데, 그 중 createBigDecimal()과 createBigInteger() 메소드가 있어서 종종 유용하다.



System.out.println(org.apache.commons.lang.math.NumberUtils.createBigDecimal("11111111111111111111111111111111111111"));

System.out.println(org.apache.commons.lang.math.NumberUtils.createBigInteger("11111111111111111111111111111111111111"));


<결과>



11111111111111111111111111111111111111

11111111111111111111111111111111111111


Posted by 김용환 '김용환'



java의 reflection은 플랫폼을 구성하는 데 효율적으로 쓰일 수 있는 좋은 api이다.

spring의 util에는 ReflectionUtils를 통해 java의 reflection api를 쉽게 사용할 수 있는 api를 제공한다.



예제를 시작한다.


앞으로 사용할 예시를 위해서 사용될 import 이다.

import com.google.common.collect.Maps;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Map;



먼저, MyActivity를 정의했다. 


import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class MyActivity {
public String id;
public String content;
private String writer;
private boolean liked;

public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}



MyActivity 클래스에 ReflectionUtils의 findField, setField, getField, makeAccessible 메소드를 사용하여 내부 변수를 마음대로 바꾼다. 하지만, private 필드를 함부로 수정할 수 없기 때문에 makeAccessible 메소드를 사용한다.

MyActivity myActivity = new MyActivity();

Field idField = ReflectionUtils.findField(MyActivity.class, "id");
System.out.println("field(id) : " + idField);

Field writerField = ReflectionUtils.findField(MyActivity.class, "writer");
System.out.println("field(writer) : " + writerField);

ReflectionUtils.setField(idField, myActivity, "200001");
Object value = ReflectionUtils.getField(idField, myActivity);
System.out.println("value : " + value);

// error - Could not access method: Class org.springframework.util.ReflectionUtils can not access a member of class MyActivity with modifiers "private"
// ReflectionUtils.setField(writerField, myActivity, "Kent");

ReflectionUtils.makeAccessible(writerField);
ReflectionUtils.setField(writerField, myActivity, "Kent");


실행하면, 다음 결과를 얻을 것이다.


field(id) : public java.lang.String MyActivity.id

field(writer) : private java.lang.String MyActivity.writer

value : 200001




참고로, ReflectionUtils.makeAccessible 메소드는 java의 reflection api의 다음 api를 조금 추상화한 것 밖에 없다.

field.setAccessible(true);





예제 2는 조금 길지만, Reflection.doWithFields에 대한 예시이다. ReflectionUtils.FieldCallback()을 이용한 것이라서, 무척 괜찮은 api이다. 간단하게 field를 출력하기 위해 RelfectionUtils.FieldCallback()의 doWith 메소드와 matches 메소드의 사용 방법을 작성했다. 


accessible에 대해서 조심스럽게 써야 할 예시도 추가했으며, matches 메소드 없이도 doWith 메소드로 모두 처리할 수 있다. 




MyActivity myActivity = new MyActivity();
Map<String, Object> properties = Maps.newHashMap();
properties.put("id", "1");
properties.put("content", "sns number one service");

// 먼저 field를 필터하기 위해 new ReflectionUtils.FieldFilter를 한 후
// FieldCallback를 사용하여 필드의 값을 저장한다
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
field.set(myActivity, properties.get(field.getName()));
}
}, new ReflectionUtils.FieldFilter() {
@Override
public boolean matches(Field field) {
return properties.containsKey(field.getName()) && Modifier.isPublic(field.getModifiers());
}
});
System.out.println(myActivity);



ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
if (field.isAccessible()) {
field.set(myActivity, properties.get(field.getName()));
}
}
});

System.out.println(myActivity);


/*
access 못할 필드에 set하면, FieldCallback를 사용할 때 에러 발생한다.
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
field.set(myActivity, properties.get(field.getName()));
}
});


<결과>
field : public java.lang.String MyActivity.id
field : public java.lang.String MyActivity.content
field : private java.lang.String MyActivity.writer

Not allowed to access field 'writer': java.lang.IllegalAccessException: Class ReflectionTest$3 can not access a member of class MyActivity with modifiers "private"
java.lang.IllegalStateException: Not allowed to access field 'writer': java.lang.IllegalAccessException: Class ReflectionTest$3 can not access a member of class MyActivity with modifiers "private"

==>


*/

properties.put("writer", "kent");
properties.put("liked", true);

//FieldCallback를 사용할 때 에러가 발생하니, field에 access하게 만들던지, modifier로 구분한다.
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
ReflectionUtils.makeAccessible(field);
field.set(myActivity, properties.get(field.getName()));
}
});

System.out.println(myActivity);



ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
int modifier = field.getModifiers();
if (Modifier.isPrivate(modifier) || Modifier.isFinal(modifier) || Modifier.isStatic(modifier)) {
// do nothing;
} else {
String name = field.getName();
if (name.equals("id") || name.equals("content")) {
field.set(myActivity, properties.get(field.getName()));
}
}
}
});
System.out.println(myActivity);


결과는 다음과 같다.


field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

MyActivity[id=1,content=sns number one service,writer=<null>,liked=false]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=<null>,liked=false]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=kent,liked=true]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=kent,liked=true]






또한 필드 뿐만 아니라, 필드에 붙은 Annotation도 처리할 수 있다. Comment 라는 모델과 CommentType이라는 Annotation 결합형에 대해서 ReflectionUtils 처리 코드를 살펴본다.



public class Comment {

public String id;

@CommentType("type1")
public String type;

public String content;


@CommentType("method")
public void createComment() {
System.out.println("create comment");
}
}


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CommentType {
String value();
}



Comment 객체를 생성하고, 필드에 CommentType에 annotation이 있다면, 처리하는 방식이다. AOP 나, 프레임 워크 구축 때 유용할 수 있는 포맷이다. 

Comment comment = new Comment();
comment.id="1";
comment.content="hahahah";
comment.type="comment";

ReflectionUtils.doWithFields(Comment.class, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

if (field.getAnnotation(CommentType.class) != null) {
System.out.println("field : " + field);
}
List<Annotation> annotationList = Arrays.asList(field.getAnnotations());
for (Annotation annotation : annotationList) {
System.out.println(annotation);
}
}
});


결과는 다음과 같다.


field : public java.lang.String Comment.type

@CommentType(value=type1)

BUILD SUCCESSFUL

Total time: 2.121 se





다음은 필드가 아닌 메소드 관점으로 살펴보는 메소드, ReflectionUtils.findMethod 메소드 예시이다. 모든 method를 출력하는 예시이다.


반면, 주의해야 할 내용도 있다. 실제 없는 메소드를 호출하면, NullPointerException(NPE)가 발생할 수 있다. 



Comment comment = new Comment();
comment.id="1";
comment.content="hahahah";
comment.type="comment";

Method method = ReflectionUtils.findMethod(Comment.class, "createComment");
ReflectionUtils.invokeMethod(method, comment);

// java.lang.NullPointerException
// Method noMethod = ReflectionUtils.findMethod(Comment.class, "noMethod");
// ReflectionUtils.invokeMethod(noMethod, comment);


ReflectionUtils.doWithMethods(Comment.class, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
System.out.println(method.getName());
}
});



결과는 다음과 같다.


create comment

createComment

finalize

wait

wait

wait

equals

toString

hashCode

getClass

clone

registerNatives

notify

notifyAll






ReflectionUtils.doWithMethods를 사용하여 메소드에 연관된 annotation을 발견할 수 있다. 사용방식은 ReflectionUtils.doWithFields 메소드와 동일하다.


Comment comment = new Comment();
comment.id = "1";
comment.content = "hahahah";
comment.type = "comment";


ReflectionUtils.doWithMethods(Comment.class, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

Annotation annotation = AnnotationUtils.getAnnotation(method, CommentType.class);
if (annotation instanceof CommentType) {
System.out.println("find comment type annotation.");
System.out.println(method.getName());
}

}
});


결과는 다음과 같다.


find comment type annotation.

createComment

Posted by 김용환 '김용환'

spring boot에서 아주 간단히 multi-datasource를 관리하고 jdbctemplate를 사용할 수 있는 예제를 소개한다.





먼저 maven또는 gradle 파일의 dependency에 db의 driver, spring-boot-starter-jdbc, commons-dbcp2를 추가한다.


build.gradle

compile("mysql:mysql-connector-java:5.1.36")
compile("org.springframework.boot:spring-boot-starter-jdbc")
compile("org.apache.commons:commons-dbcp2:2.1.1")


application.yml

spring:
profiles : dev
activity_slave_db_0:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://db0.google.com:3306/test
username: develop
password : devteam
max-wait : 10000
max-active : 5
test-on-borrow: true
validationQuery: select 1
activity_slave_db_1:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://db1.google.com:3306/test
username: develop
password : devteam
max-wait : 10000
max-active : 5
test-on-borrow: true
validationQuery: select 1



Datasource 설정 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DatabaseConfig {

@Primary @Bean(name = "dsSlave0")
@ConfigurationProperties(prefix="spring.activity_slave_db_0")
public DataSource dsSlave0() {
return DataSourceBuilder.create().build();
}

@Bean(name = "dsSlave1")
@ConfigurationProperties(prefix="spring.activity_slave_db_1")
public DataSource dsSlave1() {
return DataSourceBuilder.create().build();
}

@Bean(name = "jdbcSlave0")
@Autowired
public JdbcTemplate slave0JdbcTemplate(@Qualifier("dsSlave0") DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}

@Bean(name = "jdbcSlave1")
@Autowired
public JdbcTemplate slave1JdbcTemplate(@Qualifier("dsSlave1") DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}

}




실제 DAO에서 사용하는 jdbctemplate 코드.

@Component
public class ActivitySlaveDao {
@Autowired
@Qualifier("jdbcSlave0")
private JdbcTemplate jdbcTemplate0;
@Autowired
@Qualifier("jdbcSlave1")
private JdbcTemplate jdbcTemplate1;
private List<JdbcTemplate> getJdbcTemplates() {
List list = Lists.newArrayList();
list.add(jdbcTemplate0);
list.add(jdbcTemplate1); }

// .. jdbcTemplate 사용. }


Posted by 김용환 '김용환'


up이라는 테이블이 존재하고 그에 맞는 간단한 모델 객체가 있다.

up이라는 테이블은 간단하게 id와 date가 존재하고, date 타입으로 update_at 이라는 필드가 있다고 가정하자.


public class Up {

public long id;

public long updateAt;

}



RowMapper를 정의한다. 유의할 점은 mysql date 타입을 java 객체에서 표현할 방법이 중요하다. Types.TIMESTAMP인지 메타데이터를 확인한다. Object로 먼저 읽은 후, TimeStamp 객체로 변경한 다음 getTime()을 호출하여 long 값으로 얻어내야 한다.

(String이나 그냥 Long 타입으로 받아들인후 Date 타입으로 쉽게 변환되지 않는다)

public class UpRowMapper implements RowMapper {

public Object mapRow(ResultSet rs, int rowNum) throws SQLException {

Up up = new Up();

up.id = rs.getLong("id");

ResultSetMetaData metadata = rs.getMetaData();

if (metadata.getColumnType(2) == Types.TIMESTAMP) {

Object updateAt = rs.getObject(2);


up. updateAt = ((Timestamp) updateAt).getTime();

}

return up;

}

}



실제 쿼리는 다음과 같이 사용한다.

List<Up> upList = jdbcTemplate.query("select id, updated_at from up", new UpRowMapper(), id);


jdbcTemplate에 배치성 데이터를 생성한다.  이 때 setTimestamp() 메소드를 사용한다.

jdbcTemplate.batchUpdate("INSERT IGNORE INTO up (id, updated_at) values (?, ?)  ", new BatchPreparedStatementSetter(){

@Override

public void setValues(PreparedStatement ps, int i) throws SQLException {

Up up = upList.get(i);

ps.setLong(1, up.id);

ps.setTimestamp(4, new Timestamp(up. updateAt));

}


@Override

public int getBatchSize() {

return upList.size();

}

});

Posted by 김용환 '김용환'