2017년 4월 13일 목요일

effective java 규칙 1. 생성자 대신 정적 팩토리 메서드

일반 생성자로 객체를 생성할때 발생할 수 있는 문제점


1. class 의 생성자는 동일한 signature 별로 하나의 생성자만 가질수 있다.
(signature : 매개변수의 구성을 뜻하며 자료형, 갯수, 순서에 따라 달라진다.)


2.다른 초기화 기능이나 목적을 가진 객체를 생성하기 위해 생성자 매개변수의 순서를 바꾸는 방법으로 생성자를 overloading 한다.


3. 이렇게 만들어진 생성자를 사용하는 코드를 작성하려는 사람은 코드나 api 설명을 참조하지 않고서는 생성자가 만드려는 객체가 어떤 초기화 기능을 하는지 파악하지 못한다.

아래와 같은 class 가 있을때

public class Person {

private int age;
private int year;
private String name;

// 일반 생일자
public Person(int age, int year, String name) {
this.age = age;
this.year = year;
this.name = name;
}

// 빠른 생일자
public Person(String name, int age, int year) {
this.name = name;
this.age = age + 1;
this.year = year;
}
}

빠른 생일자 객체와 일반 생일자 객체를 생성하려고 할때 아래와 같은 코드로 생성할 수 있다. 


class PersonMain {

public static void main(String[] args) {

Person man =
new Person(21, 1990, "홍길동");
Person earlyBirthMan = new Person(빠른 홍길동", 20, 1991);
}
}

하지만 Person class 코드를 보지 않고서는 빠른생일자와 일반생일자를 구별하여 객체를 생성할 수 없다.


(장점 1) 반면 정적 팩터리 메서드는 생성자의 class 명이 아닌 메소드 명을 명명할수 있다.

팩터리 메서드의 이름으로 생성하는 객체의 특성을 나타낼수도 있다.

또 같은 signaure 를 가진 생성자는 오로지 하나 이지만 팩터리 메서드로는 메서드명만 달리하면 그 제한이 없다.

따라서 같은 시그너처를 갖는 생성자를 여러개 정의할 필요가 있을때와

생성자마다 생성할 객체의 특성을 나타낼 필요가 있을때 

정적 팩터리 메서드로 생성자를 대신하여 객체를 생성하는 것이 좋다.

아래는 정적 팩토리 메서드를 사용한 수정한 코드이다.

class ImprovedPerson {

private int age;
private int year;
private String name;

private ImprovedPerson(int age, int year, String name){
this.age = age;
this.year = year;
this.name = name;
}
// 일반생일자
public static ImprovedPerson getPerson(int age, int year, String name) {
return new ImprovedPerson(age, year, name);
}

// 빠른생일자
public static ImprovedPerson getEarlyBirthPerson(String name, int age, int year) {
return new ImprovedPerson(age + 1, year, name);

}
}

class PersonMain {

public static void main(String[] args) {

ImprovedPerson man = ImprovedPerson.getPerson(21, 1990, "홍길동");
ImprovedPerson earlyBirthMan = ImprovedPerson.getEarlyBirthPerson(20, 1991, "빠른 홍길동");
}
}
(장점 2) 또한 생성자와는 달리 호출할 때마다 새로운 객체를 생성할 필요가 없다. 
- java.lang.Boolean.class 참고

이러한 장점을 활용한 패턴이 싱글턴 패턴이다.

class Sun {

private static Sun sun = new Sun();

// 생성자로 객체 생성 불가능하도록
private Sun(){}

// 항상 같은 객체 반환
public static Sun getInstance() {
return sun;
}
}
싱글턴 뿐만 아니라 몇몇의 만들어진 객체를 pool 형태로 캐시 해놓고 재사용도 가능하다.
동일한 객체가 요청되는 일이 잦고 객체를 만드는 비용이 클 때 적용하면 성능이 크게 개선된다.
이렇게 어떤 시점에 어떤 객체가 얼마나 존재할지를 정밀하게 제어가 가능하다.
(이런 기능을 갖춘 클래스는 개체 통제 클래스-instance-controlled class- 라고 부른다.)

그리고 생성자로 생성하는 객체는 자신의 자료형 객체만 반환하지만 
(장점 3) 정적 팩터리 메서드로 생성하는 객체는 반환값 자료형의 하위 자료형 객체를 반환 할 수 있다.
(정적 팩터리 메서드가 반환하는 객체의 클래스가 public 일 필요도 없다.)


class Star {

// Sun class 에서 extends 하기 때문에 private 선언 할 수 없음
Star(){}

public static Star getSun() {
return Sun.getInstance();
}

}


class Sun extends Star {

private static Sun sun = new Sun();

// 생성자로 객체 생성 불가능하도록
private Sun(){
}

// 항상 같은 객체 반환
public static Sun getInstance() {
return sun;
}
}


위 코드를 보면 Star의 getSun 팩터리 메서드를 사용해 Star class 에서 하위 자료형인 Sun 객체를 생성한다.
이러한 유연성을 활용하면 public 으로 선언되지 않은 클래스의 객체를 반환하는 api를 만들수 있고
공개되지 않은 클래스에서 구현을 하기 때문에 구현 세부사항을 감출 수 있어 캡슐화가 가능하다.

(
다만 위 Star class 코드 자체는 생성자로도 객체를 생성할 수 있는 문제가 있다. 차라리 Star 를 interface 로 만들고
Sun 에서 implement 한 후 StarBuilder (api class) 라는 클래스에서 Sun 객체를 반환하는 팩터리 메서드를 구현하는 방법이 좋다.

* 샘플코드는 추후에 작성 

interface Star {
void hello();
}

public class StarBuilder {

private StarBuilder(){}

public static Star getSun() {
return Sun.getInstance();
}
public static Star getMoon() {
return Moon.getInstance();
}
}


class Sun implements Star {

private static Sun sun = new Sun();

// 생성자로 객체 생성 불가능하도록
private Sun() {
}

// 항상 같은 객체 반환
public static Sun getInstance() {
return sun;
}
@Override
public void hello() {
System.out.println("hi sun");
}
}


class Moon implements Star {

private static Moon moon = new Moon();

// 생성자로 객체 생성 불가능하도록
private Moon() {
}

// 항상 같은 객체 반환
public static Moon getInstance() {
return moon;
}

@Override
public void hello() {
System.out.println("hi moon");
}
}

public class Client {
public static void main(String[] args) {
Star s = StarBuilder.getSun();
Star m = StarBuilder.getMoon();

s.hello();
m.hello();
}
}
)

이해를 돕기 위해 컬렉션 프레임워크 (java.util.Collections) 참조하겠다.

public static <E> Set<E> newSetFromMap(Map<E, Boolean> var0) {
return new Collections.SetFromMap(var0);
}


private static class SetFromMap<E> extends AbstractSet<E> implements Set<E>, Serializable {
private final Map<E, Boolean> m;
private transient Set<E> s;
private static final long serialVersionUID = 2454657854757543876L;

SetFromMap(Map<E, Boolean> var1) {
if(!var1.isEmpty()) {
throw new IllegalArgumentException("Map is non-empty");
} else {
this.m = var1;
this.s = var1.keySet();
}
}
………………
}

이렇게 newSetFromMap 메서드에 의해 반환된 객체 (SetFromMap) 은 Set interface 를 구현한 객체이고 
Set 인터페이스만 보고 클라이언트 코드를 작성한다. 

또한 팩터리 메서드에 전달되는 인자에 의해 반환되는 객체를 달리 할 수 도 있다. java.util.EnumSet 참조


public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> var0) {
Enum[] var1 = getUniverse(var0);
if(var1 == null) {
throw new ClassCastException(var0 + " not an enum");
} else {
return (EnumSet)(var1.length <= 64?new RegularEnumSet(var0, var1):new JumboEnumSet(var0, var1));
}
}

마지막으로 팩터리 메서드를 사용하면 얻게되는 장점은
(장점 4) 형인자 자료형(parameterized type) 객체를 좀더 편리하게 생성할 수 있다.

java.util.Map 의 interface 를 보면 아래와 같이 선언되어있다.

public interface Map<K,V> {

따라서 맵객체를 uncheck warning 없이 사용하려면 아래와 같이 생성해야 한다.
Map<String, Integer> m = new HashMap<String, Integer>();
이렇게 일반적인 생성시 Map 의 형인자 자료형을 HashMap 생성시에도 작성해줘야 한다.

그렇지만 Guava 의 Maps 유틸처럼(com.google.common.collect) 

public static <K, V> HashMap<K, V> newHashMap() {
return new HashMap();
}

위와 같이 hashmap 의 팩터리 메서드가 정의 되어 있으면 형인자가 어떤식으로 복잡하게 되어있던 변수 선언부에만 작성하면 된다.
Map<String, List<List<List<String>>>> crazyVariable = Maps.newHashMap();
또한 java 1.7 이후 부터는 형식 추론이 가능해 아래와 같은 방법도 가능하다.
Map<String, List<List<List<String>>>> crazyVariable = new HashMap<>();

물론 단점도 있다.

(단점 1. ) 정적 팩터리 메서드만 있는 클래스는 public 이나 protected 로 선언된 생성자가 없으면 하위 클래스를 만들 수 없다.
(단점 2. ) 다른 static 메소드와 쉽게 구별할 수 없다.
그러나 명명 convension 에 의해 구별에 도움을 얻을순 있다.

- valueOf : 인자로 주어진 값(매개변수)과 같은 값을 갖는 객체를 반환, 형변환 메서드
- of : valueOf 축약형, EnumSet 참고
- getInstance : 싱글턴 패턴을 따를 경우, 인자 없이 항상 같은 객체를 반환
- newInstance : 항상 새로운 객체 반환
- getType : getInstance 와 같지만 팩터리 메서드가 다른 클래스 (util class) 에 있을때 getType 의 Type 은 반환되는 객체 타입
- newType : newInstance 와 같고 getType 처럼 팩터리 메서드가 다른 클래스에 있을때

요약
- 정적 팩터리 메서드와 public 생성자는 용도가 서로 다르며 그 차이와 장단점을 이해하는 것이 중요

- 정적 팩터리 메서드가 효과적인 경우가 많으니 무조건 public 생성자를 만들기 보다는 팩터리 메서드를 고려

2017년 2월 26일 일요일

mac 창전환 (alt tab)

application 간 전환 command + tab (좌 -> 우) / command + shift + tab (우 -> 좌)
※ app 간 전환화면에서 이전 선택으로 가는 방법은 윈도우와 동일하게 shift 를 추가로 누르고 tab 키를 누르면 이전 선택(우->좌) 으로 포커스가 이동한다.
※ 또 다른 방법은 전환화면 상에서 cmd + ' 를 누르면 위와 같이 동작한다.

한 application 내 multiple window 간 전환 command + ` (좌 -> 우) / command + shift + ` (우 -> 좌)

※ app 간 전환 (cmd + tab) 기능은 windows os 처럼 화면위에 switch 되는 app 의 아이콘이 나오기 때문에 전환 하려는 app을 명시적으로 선택이 가능하지만
app 내 창 전환은 전환 하려는 창들이 화면에 표시되지 않고 순차적으로 창들이 switch 되기 때문에 명시적으로 전환이 어렵다.
이럴 경우 app 간 전환 (cmd + tab ) 으로 전환 하려는 창을 선택 후 tab 키에선 손을 때고 cmd 키만 누른 상태에서 숫자 1 키를 누르면 expose 기능이 실행 되면서
해당 app 에 열려 있는 창들이 모두 보이면서 방향키로 전환하려는 창을 선택가능하게 된다.

- expose 기능 실행 방법 1 : cmd + tab 전환 화면 상에서 expose 기능을 보려는 app 에 focus 를 맞춘후 숫자 1 키
- expose 기능 실행 방법 2 : f10 현재 실행되는 app 의 expose 실행
(또는 shift+f10 or ctrl+f10 - mission control 설정에 따라 다름)
단축키 설정 : 시스템환경설정 -> 키보드 -> 단축키 -> Mission Control -> 응용 프로그램 윈도우

2017년 2월 5일 일요일

zookeeper node watcher 와 callback


Watcher Type

operation
create
delete
child create
child delete
change
exists
NodeCreate
NodeDelete


NodeDataChanged
getData

NodeDelete


NodeDataChanged
getChildren

NodeDelete
NodeChildrenChanged
NodeChildrenChanged



NODE_CREATED : 노드가 생성 됨을 감지
NODE_DELETE : 노드가 삭제 됨을 감지
NODE_DATA_CHANGED : 노드의 데이터가 변경됨을 감지
NODE_CHILDREN_CHANGED : 자식 노드가 변경 됨을 감지

Node 생성 종류
[zookeeper.create(nodepath, nodedata, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode, Callback, Object)]
CreateMode.PERSISTENT 영속성
CreateMode.EPHEMERAL 해당 세션이 살아 있을 경우에만 지속


[Executor]


public class Executor implements Runnable, AsyncCallback.ChildrenCallback {

    ZooKeeper zk;
    public Executor() throws IOException {
        zk = new ZooKeeper("127.0.0.1:2181", 3000, null);
        // default watcher 등록
        Watcher w = new NodeMonitor(zk, TestNodeManager.getInstacne());
        zk.register(w);

        try {

            /*
            1. a node 의 children node 변경 감지 (a node delete, child delete, child create)
                - zookeeper connection 생성시 등록한 default watcher or zk.register(w)  watcher -
            2. zk.getChildren 실행되면
                - watcher 가 변경 감지하여 실행하는 것은 아님 변경감지하면 watcher 의 process method 실행됨 -
                실행결과(children node List)를 등록한 callback -w callback (processResult)- 으로 후 처리
             */
            // zk.getChildren("/a", true, (AsyncCallback.ChildrenCallback) w, null);
            // 하위 노드에 watcher 를 모두 등록하려면 현재 class 에서 등록하는게 더 직관적
           zk.getChildren("/a", true, this, null);

            // 최초 시작시 a node 변경 감지를 위한 설정 - create, delete, data change - NodeDataChanged
            zk.exists("/a", true);
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void processResult(int rc, String path, Object ctx, List children) {
        KeeperException.Code code = KeeperException.Code.get(rc);
        // 성공
        if(code == KeeperException.Code.OK) {
            if(children != null) {
                // 조회된 child node 모두 watcher (getChildren, exists) 등록
                for (String c : children) {
                    try {
                        zk.getChildren(path + "/" + c, true, this, null);
                        zk.exists(path + "/" + c, true);
                    } catch (KeeperException | InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }

    public static void main(String[] args) throws IOException {

        Executor exec = new Executor();
        exec.run();

    }

    @Override
    public void run() {
        try {
            synchronized (this) {
                while (true) {
                    wait();
                }
            }
        } catch (InterruptedException e) {
        }

    }

}


[Watcher and Callback]

public class NodeMonitor implements Watcher, AsyncCallback.ChildrenCallback, AsyncCallback.StatCallback {

    private Logger logger = LoggerFactory.getLogger(NodeMonitor.class);

    private ZooKeeper zk;
    private NodeManager manager;

    public NodeMonitor(ZooKeeper zk, NodeManager manager) {

        logger.debug("-------- Constructor --------");

        this.zk = zk;
        this.manager = manager;
    }

    /**
     * watcher 는 node 감지에 소비되므로 감지시 재등록 해주어야 한다.
     * 특정 node 하위의 모든 event 를 감시하려면 exists 와 getChildren 모두에 watcher 를 등록해야 한다.
     * Watcher @Override node 변화 감지, operation 의 watcher 인자에 의해 실행
     * @param event
     */
    @Override
    public void process(WatchedEvent event) {

        String path = event.getPath();

        logger.debug("event type {}, path {}", event.getType(), path);

        String eventType = event.getType().name();
        String eventState = event.getState().name();
        System.out.println("## process: path : "+path+", eventType : "+ eventType + ", eventState: "+ eventState);



        switch (event.getType()) {
            case NodeChildrenChanged:
                // 하위 노드 조회 메소드 - 하위 노드 조회가 성공하면 this.callback method - processResult - 후 처리
                // getChildren watcher 재 등록
                zk.getChildren(path, true, this, null);

                break;
            case NodeDataChanged:

                logger.debug("node data path = {} ", path);
                // watcher 재등록만을 위한 목적이면 callback = null
                // NodeDataChanged 에 대한 후 처리가 필요한 경우 callback class 는 AsyncCallback.StatCallback 를 구현
                // zk.exists(path, true, this, null);
                try {
                    zk.exists(path, true);
                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }

                // exists callback method 에서 처리 하지 않고 process method 에서 처리 할 수 있음
                // 물론 아래 로직을 exists callback 에 기술할 수 있음
                try {
                    byte[] b = zk.getData(path, this, null);
                    String data = new String(b);
                    boolean isTestNode = manager.isTestNode(path, data, this::selectNodeData);
                    // manager.getNode(data);

                    logger.debug("node data changed {} - is test node {} : ", data, isTestNode);
                } catch (KeeperException | InterruptedException e) {
                    e.printStackTrace();
                }

                break;
        }
    }

    private Node selectNodeData(String path, String data) {

        Node node = manager.getNode(data);
        node.setPath(path);

        return node;
    }


    /**
     * getChildren Callback - operation 실행 후 수행되는 callback method, operation 의 callback 인자에 의해 실행
     * 즉 watcher 에 의해 변화를 감지하고 변화에 맞는 operation 을 수행했을때
     * operation 의 지정된 callback 에 의해 processResult 가 실행
     * @param rc
     * @param path
     * @param ctx
     * @param children
     */
    @Override
    public void processResult(int rc, String path, Object ctx, List children) {

        KeeperException.Code code = KeeperException.Code.get(rc);
        logger.debug("CALLBACK - getChildren, path : {}, code : {} ", path, code);

        // 성공
        if(code == KeeperException.Code.OK) {
            if(children != null) {

                // do something...
                // NodeChildrenChanged event 발생시 등록한 callback 에 의해 수행 될 비즈니스 로직
                logger.debug("children {} ", children);
                for (String c : children) {
                    logger.debug("child node {}" + c);

                    try {
                        // 하위의 하위가 변경된게 아니기때문에 하위의 getChildren call back 필요없음
                        zk.getChildren(path + "/" + c, true);

                        zk.exists(path + "/" + c, true);
                    } catch (KeeperException | InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
        // 실패
        else {
        }

    }

    /**
     * exists callback
     * implements AsyncCallback.StatCallback
     * @param rc
     * @param path
     * @param ctx
     * @param stat
     */
    @Override
    public void processResult(int rc, String path, Object ctx, Stat stat) {

    }

}


※ operation 실행시 watcher parameter 의  true 와 watcher object 의 차이
true - default watcher 등록
watcher object - 인자로 전달되는 object 를 watcher 로 등록 (this 는 현재 object 를 watcher 로 등록)

2017년 2월 4일 토요일

GC Monitoring

[GarbageCollectorMXBean api 사용]

        for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {

            // 마지막 gc 정보
            System.out.println("gc count : " + gcBean.getCollectionCount());

            com.sun.management.GarbageCollectorMXBean sunGcBean = (com.sun.management.GarbageCollectorMXBean) gcBean;

            // 마지막 gc duration time
            System.out.println(sunGcBean.getName() + "last gc time : " +  sunGcBean.getLastGcInfo().getDuration());

            // 마지막 gc duration time = getLastGcInfo().getDuration()
            System.out.println(sunGcBean.getLastGcInfo().getEndTime() - sunGcBean.getLastGcInfo().getStartTime());

            // 마지막 gc 실행 정보
            System.out.println(sunGcBean.getLastGcInfo());
        }

[jmx api 사용]

        try { 
            MBeanServerConnection server = ManagementFactory.getPlatformMBeanServer();
            ObjectName name = new ObjectName("java.lang:type=GarbageCollector,name=PS MarkSweep");

            MBeanInfo info = null; 
            info = this.server.getMBeanInfo(objectName);
            List names = Arrays.stream(Optional.ofNullable(info).map(MBeanInfo::getAttributes).orElse(new MBeanAttributeInfo[]{null})).map(MBeanAttributeInfo::getName).collect(toList());

            names.forEach(n -> System.out.println(n + " : " + server.getAttribute(name, n)));
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        }

[ result ]
PS Scavenge - young gc
PS MarkSweep - full gc


※ 정리 필요함

jmx MBean


application 에서 감시할 항목을 MBean 으로 jmx 에 노출 시켜 application 을 모니터링 할 수 있다.

jmx  (2번 3번은 은 하나의 파일로 super - sub class 형식으로 작성가능)
1. mbean interface 생성
2. mbean 구현체 생성
3. mbean 구현체 jmx 에 등록 코드 작성
4. java 실행 with jmx option
5. jvisualvm 확인


* 표준 MBean Interface 생성 (※ 주의 interface  suffix -MBean
    - MBean  인터페이스는 감시할 항목 정의
public interface MonitoringMBean {
    long getExecuteTime();
    long getPollingCount();
    long getChannelPercentage();
}
* MBean Interface 구현
 - 구현체에서 jmx 에 등록할 항목의 을 return 한다.
  - interface 의 구현체의 접미사 MBean 을 제외하고 파일명이 같아야 한다.

public class Monitoring implements MonitoringMBean {

    public long getExecuteTime() {
        return MonitoringCounter.getInstance().getExecuteTime();
    }
    public long getPollingCount() {
        return MonitoringCounter.getInstance().getPollingCount();
    }
    public long getChannelPercentage() {
        return MonitoringCounter.getInstance().getChannelPercentage();
    }

}
* 모니터링 값 설정
- 여러 객체에서 MonitoringMBean 구현체인 Monitoring 객체에 값을 지정 하려면 싱글톤 방식으로 값을 전달해야 한다.
public class MonitoringCounter {
    private static MonitoringCounter counter;

    private long executeTime;
    private long pollingCount;
    private long channelPercentage;

    private MonitoringCounter() {
    }

    public static MonitoringCounter getInstance() {
        if(counter == null) {
            counter = new MonitoringCounter();
        }
        return counter;
    }

    public long getExecuteTime() {
        return executeTime;
    }

    public void setExecuteTime(long executeTime) {
        this.executeTime = executeTime;
    }

    public long getPollingCount() {
        return pollingCount;
    }

    public void setPollingCount(long pollingCount) {
        this.pollingCount = pollingCount;
    }

    public long getChannelPercentage() {
        return channelPercentage;
    }

    public void setChannelPercentage(long channelPercentage) {
        this.channelPercentage = channelPercentage;
    }

}

* MBean 등록
public class MonitoringExporter {
    private Monitoring monitoring;

    public MonitoringExporter() {
        try {
            this.monitoring = new Monitoring();

            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            // new ObjectName("임의의 MBean 명 :type=임의의 Type명");
            ObjectName jmxObjectName = new ObjectName("SomeApp:type=SomeType");
            server.registerMBean(monitoring, jmxObjectName);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     -Dcom.sun.management.jmxremote.port=9999(임의의 포트 - 사용자 설정)
     -Dcom.sun.management.jmxremote.ssl=false
     -Dcom.sun.management.jmxremote.authenticate=false
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        MonitoringExporter exporter = new MonitoringExporter();
    }

}
* application 실행 java option 
 
-Dcom.sun.management.jmxremote.port=9999 (임의의 포트)
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false

* application

public class SomeApp {
    private Random rand;

    public SomeApp() {
        this.rand = new Random();
    }

    public void run() {
        System.out.println("do something.....");
        MonitoringCounter.getInstance().setChannelPercentage(rand.nextLong());
        MonitoringCounter.getInstance().setExecuteTime(rand.nextLong());
        MonitoringCounter.getInstance().setPollingCount(rand.nextLong());
    }

    public static void main(String[] args) throws Exception {
        MonitoringExporter.main(null);
        SomeApp someApp = new SomeApp();
        while(true) {
            someApp.run();
            Thread.sleep(1000);
        }
    }

}

* jvisualvm 확인 
  - MBeans 항목은 별도 jvisualvm 플러그인 설치
    visualvm > tools > plugins > Avaliable Plugins > VisualVM-MBeans
    또는 http://visualvm.java.net/pluginscenters.html 에서 현재 visualvm 버전의 plugin search

 > jvisualvm













* jmx reader

 - mbean 으로 등록된 모니터링 항목을 jvisualvm이 아닌 mbean api 로 application 에서 조회
 
public class JMXReader {

    private MBeanServerConnection server;

    /**
     * local connection
     */
    public JMXReader() {
        this.server = ManagementFactory.getPlatformMBeanServer();
    }

    /**
     * remote connection
     * remote 정의
     * 같은 서버(localhost) 이라도 java 로 실행시키는 app 이 다를 경우 remote 로 이해해야함
     * @param url
     * @throws IOException
     */
    public JMXReader(String url) throws IOException {
        JMXServiceURL serviceURL = new JMXServiceURL(url);
        /* java.io.IOException: The client has been closed. 발생 - 발생이유는 좀더 분석이 필요함
        try (JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceURL)) {
            this.server = jmxConnector.getMBeanServerConnection();
        }
        */
        JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceURL);
        this.server = jmxConnector.getMBeanServerConnection();
    }

    public String[] getDomains() {
        try {
            return this.server.getDomains();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     *
     * @param objectName 정규식 및 null 가능
     * null = all
     * *:* = all
     * SomeApp:*
     * Some*:type:S*
     *
     * @return
     */
    public Set getObjectNames(ObjectName objectName) {
        try {
            return Sets.newHashSet(
                    Optional.ofNullable(this.server.queryNames(
                            objectName
                            ,null
                    )).orElse(Sets.newHashSet()));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     *
     * @param objectName MBean
     * @return
     */
    public List getAttributes(ObjectName objectName) {
        MBeanInfo info = null;
        try {
            info = this.server.getMBeanInfo(objectName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Arrays.stream(Optional.ofNullable(info).map(MBeanInfo::getAttributes).orElse(new MBeanAttributeInfo[]{null})).map(MBeanAttributeInfo::getName).collect(toList());
    }

    /**
     *
     * @param objectName MBean
     * @param attrName MBean 항목
     * @return
     */
    public Object getAttributeValue(ObjectName objectName, String attrName) {
        try {
            return this.server.getAttribute(objectName, attrName);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    public static void main(String[] args) throws MalformedObjectNameException, IOException {

        // local
        JMXReader local = new JMXReader();
        System.out.println(local.getAttributeValue(new ObjectName("java.lang:type=GarbageCollector,name=PS MarkSweep"), "Name"));
        System.out.println("-------------------------- local end");

        // remote
        JMXReader remote = new JMXReader("service:jmx:rmi:///jndi/rmi://127.0.0.1:9999/jmxrmi");

        Arrays.stream(remote.getDomains()).forEach(System.out::println);
        System.out.println("-------------------------- domains end");

        remote.getObjectNames(new ObjectName("*:*")).forEach(System.out::println);
        System.out.println("-------------------------- objectNames end");

        remote.getAttributes(new ObjectName("SomeApp:type=SomeType")).forEach(System.out::println);
        System.out.println("-------------------------- attributes of SomeApp MBean  end");

        System.out.println(remote.getAttributeValue(new ObjectName("SomeApp:type=SomeType"), "ExecuteTime"));
        System.out.println("-------------------------- attribute value of SomeApp MBean ExecuteTime Attribute end");

    }
}

2017년 1월 3일 화요일

intellij inline debugging

설정 :
Preference > Build,Execution, Deployment > Debugger > Data Views

Show values inline  : debugger 의 variable 을 소스 코드에 출력
Show value tooltip : 값을 확인해볼 변수에 마우스 커서를 위치하면 tooltips 으로 값 확인
Show value tooltip on code selection : 변수를 선택(selection) 하면 show variable tooltip

# 추가 tip
1. debug tool window 의 variables tab 에서
변수를 우클릭후 inspect.. 를 선택하면 variable 을 modal 창에서 확인 가능

2. array 변수를 variables tab에서 확인시 array size 지정
- array 변수 우클릭 > Adjust Range... 선택 후 나오는 창에서 range 지정

2016년 11월 22일 화요일

mybatis mapper 동적 설정


spring 등 여타 framework 에서 sql query xml 을 mybatis 에 설정 하는 방법은
@MapperScan annotation 을 활용하거나 mybatis 의 scan 등을 활용해 mapper 를 손쉽게 설정 할 수 있다.

하지만 독립적으로 mybatis 를 사용하면서 로직에 따라 동적으로 mapper 를 등록해야 하는 일이 생겼는데 
공식 reference site 에 딱히 방법이 나와 있지 않아 난감해진적이 있었다.
그때 해결했던 이력을 남겨본다.

핵심만 말하자면
mybatis 라이브러리안에 XMLMapperBuilder 라는 class 를 활용해 해결할 수 있다.

아래는 test 했던 난잡한 sample code 이다. 그래도 작은 도움은 될거라 생각된다.

/**
 * Created by fall1999y on 2016. 6. 23..
 */

public class SessionFactoryProvider {
    private static final String mapperFiles[] = {"mybatis/sql/testsql.xml", "mybatis/sql/samplesql.xml"};

    public SqlSessionFactory produceFactory() {
        String resource = "mybatis/mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {

            throw new RuntimeException("Fatal Error.  Cause: " + e, e);
        }
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        for (String mapperFile : mapperFiles) {
            InputStream in = getClass().getClassLoader().getResourceAsStream(mapperFile);
            Configuration configuration = sqlSessionFactory.getConfiguration();
            if (in != null) {
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(in, configuration, mapperFile, configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } else {
                System.err.println("Error file not loaded " + mapperFile);
            }
        }
        return sqlSessionFactory;
    }

    public static void main(String[] args) {
        SessionFactoryProvider provider = new SessionFactoryProvider();
        SqlSessionFactory factory = provider.produceFactory();
        SqlSession s = factory.openSession();

        TestMapper mapper = s.getMapper(TestMapper.class);
        System.out.println(mapper.selectTestTable());
        SampleMapper sampleMapper = s.getMapper(SampleMapper.class);
        System.out.println(sampleMapper.selectSampleTable());
    }
}


참고로 아래는 독립적인 mybatis 에서 xml scan 하는 설정이다. (mybatis.xml)
<mappers>
    <package..
부분만 보면 이해가 가능하다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="properties/db.properties" />
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        <environment id="development2">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="net.vjvj.mapper"/>
    </mappers>
</configuration>

Intelij 설정 및 plugin

1. preferences... (settings...) Appearance & Behavior > Appearance - Window Options        ✓   Show memory indicator Editor ...