2017년 4월 17일 월요일

effective java 규칙 3. private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라 (singleton)

하나의 객체만을 사용하도록 강제할때 주로 사용하는 패턴으로 singleton pattern 이 있다.
singleton pattern은 매우 흔히 사용되는 패턴으로 그 구현 방법도 여러 가지이다.

1. public final 필드를 이용한 방법은 가장 손쉽게 싱글턴 객체를 생성 하는 방법이다.

public class SingletonByPublicStaticVariable {

public static final SingletonByPublicStaticVariable INSTANCE = new SingletonByPublicStaticVariable();
private SingletonByPublicStaticVariable() {}

public void test() {
System.out.println("singleton by public static final variable");
}

}
위 코드는 클래스가 로드될때 초기화를 통하여 INSTANCE 변수에 객체가 생성되어 할당된다.
이 방법의 장점은 선언만 보고 싱글턴 여부를 바로 알 수 있지만 클래스가 로드될때 항상 객체가 생성된다.
가령 객체로 생성할 필요없이 public static method 를 사용할때에도 객체는 변수에 할당된다.
그리고 객체가 생성될때 에러처리도 불가능하며 객체 생성 전에 로직이 필요한 경우엔 사용할 수 없다.


2. static 블록을 이용한 방법은 1번 방법을 조금 개선한 방식이다.

public class SingletonByStaticBlock {
private static final SingletonByStaticBlock INSTANCE;
static {
INSTANCE = new SingletonByStaticBlock();
}

public static SingletonByStaticBlock getInstance() {
return INSTANCE;
}

public void test() {
System.out.println("singleton by static block");
}
}
static 초기화 블럭은 클래스가 로딩 될 때 최초 한번만 실행된다.
1번과 비교하여 객체를 생성하기전에 로직을 담을 수 있기 때문에 객체생성 로직 또는 에러처리 구문이나 초기변수 셋팅등의 코드 작성이 가능하다.
예를 들자면 현재시간을 체크하여 연초면 2개의 인자를 받는 생성자를 통해 객체를 생성하고 연말이면 3개의 인자를 받는 생성자를 사용한다던가의 로직 기술이 가능하다.
다만 이방식도 클래스가 로드되는 시점에 무조건 객체가 생성된다.

3. 정적 팩터리를 이용한 싱글턴 방식

public class SingletonByFactory {
private static final SingletonByFactory INSTANCE = new SingletonByFactory();

private SingletonByFactory() {
}

public static SingletonByFactory getInstance() {
return INSTANCE;
}

public void test() {
System.out.println("singleton by factory method");
}
}

이 방식은 싱글턴패턴을 포기하는 상황에서 좀더 유연하게 작동한다.
1번 방식으로 구현했을때 싱글턴을 변경하면 해당 클래스를 사용하는 클라이언트 코드는 모두 수정이 불가피하게 된다.
정적 팩터리를 사용하면 메소드를 사용하기 때문에 내부 구현의 변화에 따른 클라이언트 코드의 영향을 줄일 수 있다.
성능적인 측면에서도 최신 jvm 은 정적 팩터리 메서드 호출을 거의 항상 인라인 처리하기 때문에 1번과 성능차이는 거의 없다.
그리고 이방식은 제너릭 타입을 수용하기에 용이하다.


4. lazy initialization 방식

1~3번 방식은 모두 클래스 로딩시점에 객체가 생성되어진다. lazy initialization 은 이러한 부담을 개선한 방식이다.

public class SingletonByLazyInit {
private static SingletonByLazyInit INSTANCE;
private SingletonByLazyInit() {}

public static SingletonByLazyInit getInstance() {
if(INSTANCE == null)
INSTANCE = new SingletonByLazyInit();

return INSTANCE;
}

public void test() {
System.out.println("singleton by lazy initialization");
}
}
위 코드에 나타난 대로 객체는 getInstance 메소드 실행되고 객체가 생성 된 적이 없어야만 객체가 생성된다.
그러나 이 방식도 잠재된 문제점이 있다.
동일 시점에 getInstance 가 호출될때 가령 multi thread 환경에서 객체가 두개 이상 생성될 가능성이 있다.

5. thread safe lazy initialization 방식
이방식은 4번의 문제점을 개선한 방석이다.

public class SingletonByLazyInitThreadSafe {
private static SingletonByLazyInitThreadSafe INSTANCE;

private SingletonByLazyInitThreadSafe() {
}

public static synchronized SingletonByLazyInitThreadSafe getInstance() {
if(INSTANCE == null)
INSTANCE = new SingletonByLazyInitThreadSafe();

return INSTANCE;
}

public void test() {
System.out.println("singleton by thread safe initialize ");
}
}

코드는 거의 유사하나 getInstance method 에 synchronized 를 선언해 여러 thread 가 동시에 접근하지 못하도록 했다.
그러나 역시 여러 thread 가 getInstance 를 호출하게되면 높은 cost 비용으로 인해 프로그램 전반에 성능저하가 발생한다.

6. Enum 싱글턴 방식

4~5번 문제를 해결하는 effective java 에서 제안 하는 방식은 enum 싱글턴이다. effective java 에서는 이 방식이 가장 낫다고 한다.

public enum SingletonByEnum {
INSTANCE;

public void test() {
System.out.println("singleton by enum");
}
}

enum 방식은 일반적인 우리가 알고 있는 열거형을 싱글턴 패턴에 이용하는 방식이다.
원소가 하나인 enum 자료형을 정의하고 다른 method 들은 class 작성과 동일하게 작성한다.
effective java 에 따르면
단 한번의 인스턴스 생성을 보장하며 사용이 간편하고 직렬화가 자동으로 처리되고 직렬화가 아무리 복잡하게 이루어져도 여러 객체가 생길 일이 없으며, 리플렉션을 통해 싱글턴을 깨트릴수도 없다고 한다.
그리고 multi thread 로부터 안전하다. 
다만 이 방식은 생소하기도 하고 enum 자료형의 일반적인 쓰임새와 혼동될 것 같다는 생각이 든다. 

7. holder idiom 방식

이 방식 또한 thread 에 대해 안전하다
이 방식은 jvm 상에서 클래스 초기화 단계에 따른 특성을 활용한 방식이다.
public class SingletonByIdiom {

private SingletonByIdiom() {

}

private static class Singleton {
private static final SingletonByIdiom INSTANCE = new SingletonByIdiom();
}

public static SingletonByIdiom getInstance() {
return Singleton.INSTANCE;
}

public void test() {
System.out.println("singleton by idiom");
}
}

SingletonByIdiom 클래스는 클래스 로드 시점에 초기화되지만 정적 클래스로 정의된 내부 클래스의 초기화는 해당 시점에 이뤄지지 않는 특성이 있다.
즉 getInstance 를 통해 내부 클래스의 instance 를 호출할때 뒤늦게 초기화 되어 객체를 할당한다.
이 방식이 thread safe 한 이유는 jvm 의 클래스 초기화 과정에서 보장되는 원자적 특성(시리얼하게 동작)을 이용하기 때문이다. 즉 동기화 문제를 jvm이 처리 하도록 한다.
 


[참고 : 클래스가 최초로 초기화 되는 시점]
T에서 선언한 정적 필드가 상수 변수가 아니며 사용되었을 때
T가 클래스이며 T의 인스턴스가 생성될 때
T가 클래스이며 T에서 선언한 정적 메소드가 호출되었을 때
T에서 선언한 정적 필드에 값이 할당되었을 때
T가 최상위 클래스(상속 관계에서)이며 T안에 위치한 assert 구문이 실행되었을 때





직렬화 가능 클래스에서 역직렬화시 새로운 객체가 생기게 되는 문제 (singleton 깨짐, 단 enum 싱글턴 방식은 역직렬화에 따른 문제점이 없다. - holder idiom 방식은 미확인)

싱글턴 특성을 유지하려면 
1. 모든 필드를 transient 로 선언
2. readResolve 메서드 추가

// 싱글턴 상태를 유지하기 위한 readResolve 구현
private Object readResolve() {
    // 동일한 객체가 반환되도록 하는 동시에, 가짜 객체는 gc 가 처리하도록 만듬.
    return INSTANCE;
}


참고 사이트

2017년 4월 16일 일요일

effective java 규칙 2. 생성자 인자가 많을 때는 Builder 패턴 적용을 고려하라

객체를 생성할때 객체에서 사용할 멤버변수(상태 멤버변수는 아님 - 즉 사용 시점에 따라 값이 달라지는 멤버변수가 아닌)를 외부로 부터 받아야 하는 객체일 경우 생성자를 통하거나 객체 생성 후 setter 메서드를 통해 인자를 전달받는다.
이 멤버변수가 모두 필수 인자일 경우는 별 문제가 되지 않는다. 하나의 생성자에 인자를 모두 포함시키면 된다.
다만 인자의 갯수가 많아지면 가독성에 문제가 생기고 클라이언트 코드에서 객체 생성 코드를 작성하기가 어려워진다.
생각해보라 그 많은 인자를 생성자 인자 순서에 맞게 작성하는 것이 쉽지도 않고 인자타입이 같은 인자들의 순서를 잘 못 기입해도
컴파일러는 알지 못하며 런타임 시점에 문제가 생기게 되어 문제를 발견하기 어려워진다.
위 문제말고도 또 하나의 문제가 발생할 수 있다.

또한 객체의 멤버변수가 모두 필수 인자가 아니라 필수 인자와 선택적 인자가 혼재 되어있을때의 객체 생성시에도 문제가 발생한다.
필수필드와 선택적 필드가 같이 있는 클래스의 객체를 생성하는 방법은 보통 다음 두가지 방법을 사용한다.

1. 하나는 점층적 생성자 패턴 (telescoping constructor pattern) 을 사용하는 것이고
2. 다른 하나는 자바빈 패턴을 사용하는 것이다.

점층적 생성자 패턴은 
필수 인자값은 클래스에 필수 인자를 모두 받는 생성자를 하나 만들고
선택적 인자를 하나 받는 생성자를 추가하며 추가된 생성자 안에 두개의 선택적 인자를 받는 생성자를 추가하는 방식으로 생성자를 쌓아 올리듯 추가하는 방식이다.
이 방식은 선택적 인자의 갯수 만큼 생성자가 증가하며 설정할 필요가 없는 필드에도 인자가 전달된다.
그리고 이방식은 인자가 많을 경우의 문제점인 

가독성에 문제가 생기고 
클라이언트 코드에서 객체생성 코드를 작성하기가 어려워지며 
인자의 순서를 혼동할 가능성이 많고 
타입이 같은 인자의 순서를 혼동할 경우 컴파일러는 알지 못하며 런타임 시점에 오류가 발생할 수 있어 오류를 찾아내기가 어려워지는 문제
” 
이 전혀 해결되지 않는다.
다만 생성자를 통하여 객체를 생성하기 때문에 안정성은 보장이 된다. (멤버변수의 final 과 변경 불가능 객체로 생성 = 객체 일관성 향상)

* 점층적 생성자 패턴을 적용한 클래스
public class Telescoping {

// 필수
private final int year;
private final int month;
private final int day;

// 선택
private final int hour;
private final int minute;
private final int second;


public Telescoping(int year, int month, int day) {
this(year, month, day, 0);
}

public Telescoping(int year, int month, int day, int hour) {
this(year, month, day, hour, 0, 0);
}

public Telescoping(int year, int month, int day, int hour, int minute) {
this(year, month, day, hour, minute, 0);
}

/**
* 인자가 많아 질수록 인자의 순서를 혼동하기 쉽고 사용하기 어려워짐
* @param year
* @param month
* @param day
* @param hour
* @param minute
* @param second
*/
public Telescoping(int year, int month, int day, int hour, int minute, int second) {
this.year = year;
this.month = month;
this.day = day;
this.hour = hour;
this.minute = minute;
this.second = second;
}

}

두번째 대안인 자바빈(JavaBeans) 패턴은
인자가 없는 (또는 필수 인자만 받는) 생성자를 호출하여 객체를 생성한 다음 설정 메서드(setter method)를 통해 멤버변수 필드의 값을 채우는 것이다.
얼핏 보기에도 이 방식은 점층적 생성자 패턴에서 발생할수 있는 인자의 순서를 혼동하는 문제점도 해결되고 객체를 생성 하기도 쉬워 대안으로 선택하기 쉽다. 
이 패턴의 또 하나의 특징은 멤버변수를 setter 메서드로 설정하기 때문에 멤버변수의 final 지정이 불가능하다.
생성자로 인자를 받지 않기때문에 (필수 인자를 받는 생성자는 논외) 멤버변수는 setter 를 통해 값을 받을 수 밖에 없고 그렇기 때문에 멤버변수에 final 지정이 불가능하다.
이점이 첫번째 객체 생성 방식인 점층적 생성자 패턴과의 또다른 차이점이다.
이 차이점으로 인해 이 자바빈 패턴에는 심각한 단점이 발생한다.
객체 일관성(consistency) 훼손이 가능해지며 항상 변경 가능한 객체가 만들어지기 때문에 변경 불가능(immutable) 클래스를 만들 수 없다 (스레드 안정성을 제공하려면 더 많은 작업이 필요함). 
추가적인 작업을 통하여 일관성을 향상 시킬수는 있지만 매우 까다로우며 거의 쓰이지도 않는다.
그리고 또다른 단점은
1회의 함수(생성자) 호출로 객체 생성을 끝낼 수가 없다는 점이다. 이 단점 역시 객체 생성후 추가 객체 값 설정 작업으로 인해 일관성 문제를 야기 시킨다.

public class JavaBean {

// 필수 - final 선언 하지 못함
private int year;
private int month;
private int day;

// 선택 - final 선언 하지 못함
private int hour;
private int minute;
private int second;

public JavaBean() {
}

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDay() {
return day;
}

public void setDay(int day) {
this.day = day;
}

public int getHour() {
return hour;
}

public void setHour(int hour) {
this.hour = hour;
}

public int getMinute() {
return minute;
}

public void setMinute(int minute) {
this.minute = minute;
}

public int getSecond() {
return second;
}

public void setSecond(int second) {
this.second = second;
}

public static void main(String[] args) {

/*
가독성은 향상되나 객체 생성을 1회에 끝낼 수 없고
멤버변수를 setter 로 추후에 셋팅이 가능해 일관성이 훼손될 수 있다
*/
JavaBean j = new JavaBean();
j.setYear(2017);
j.setMonth(4);
// ...
j.setSecond(17);

// 일관성 훼손 가능
// j.setYear(2018);
}
}


위 두 패턴의 단점을 개선한 세번째 대안은 빌더(Builder) 패턴이다.
이 패턴은 점층적 생성자 패턴의 안정성과 자바빈 패턴의 가독성을 결합한 대안이다.
기본적인 생성 방법은 코드로 설명하겠다.

* 빌더 패턴을 적용한 클래스
public class SomePerson {

// 필수인자 final 선언가능
private final String name;
private final Date birthday;

// 선택적인자 final 선언가능
private final String address;
private final int height;
private final String socialStanding;
private final Boolean isCyber;

/**
* builder 를 통해서 만 객체 생성할 수 있도록 private 선언
* 빌더 객체에서 실제 객체로 인자 복사
* @param builder
*/
private SomePerson(Builder builder) {
this.name = builder.name;
this.birthday = builder.birthday;

this.address = builder.address;
this.height = builder.height;
this.socialStanding = builder.socialStanding;
this.isCyber = builder.isCyber;
}

public static class Builder {
private String name;
private Date birthday;
private String address;
private int height;
private String socialStanding;
private Boolean isCyber;

/**
* 필수 인자 값은 생성자로 설정
* @param name
* @param birthday
*/
public Builder(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}

public Builder address(String address) {
this.address = address;
return this; // builder 객체 자신을 return
}

public Builder height(int height) {
this.height = height;
return this; // builder 객체 자신을 return
}

public Builder socialStanding(String socialStanding) {
this.socialStanding = socialStanding;
return this;
}

public Builder nomalPeople(String address, int height) {
this.address = address;
this.height = height;
return this;
}

public Builder rankPeople(String socialStanding, String address, int height) {
this.socialStanding = socialStanding;
this.address = address;
this.height = height;
return this;
}

public Builder cyberPeople(String address, int height) {
this.address = address;
this.height = height;
this.isCyber = Boolean.TRUE;
return this;
}

public Builder realPeople(String address, int height) {
this.address = address;
this.height = height;
this.isCyber = Boolean.FALSE;
return this;
}

// 최종적으로 변경 불가능 객체 생성
public SomePerson build() {
return new SomePerson(this);
}
}
}

* 객체를 생성하려는 클라이언트 클래스
public class MakePersonMain {

/**
* 점층적 생성자 패턴과 비교해봤을때
* 빌더패턴으로 객체를 생성시 인자의 이름이 있으므로 인자를 혼동하지 않는다. (자바빈 패턴 장점)
* 또한 빌더패턴으로 생성된 객체는 변경불가능한 객체로 생성이 가능하다. (점층적 생성자 패턴 장점)
* 그리고 빌더 패턴이 갖는 또 한가지의 장점은 선택적 인자를 필요한 만큼 포괄적으로 설정이 가능하며
* 여러 성격의 객체를 만들수 있다.
* 하나의 빌더로 여러 객체를 생성할 수 있다.
* @param args
*/
public static void main(String[] args) {

// 기본생성 (필수인자)
SomePerson person = new SomePerson.Builder("홍길동", new Date()).build();

// 선택적 인자 포함 생성
SomePerson personExt = new SomePerson.Builder("홍길동2", new Date())
.address("조선...").height(160).build();

// 일반인 - 선택적 인자 포괄 생성
SomePerson personExt2 = new SomePerson.Builder("홍길동", new Date())
.nomalPeople("한양", 170).build();
// 양반 - 객체 생성시 명시적으로 객체의 특성을 나타내도록 생성할 수 있다.
SomePerson nobility = new SomePerson.Builder("황희", new Date())
.rankPeople("영의정", "한양", 166).build();

// 하나의 빌더로 여러 객체 생성 가능, 어떤 필드의 값은 자동으로 채움(isCyber 필드와 같은)
SomePerson.Builder builder = new SomePerson.Builder("메트릭스-네오", new Date());
SomePerson cyberMan = builder.cyberPeople("뉴욕", 195).build();
SomePerson realMan = builder.realPeople("지하세계", 185).build();

}

}

요약

필수 인자와 선택적 인자가 있을때 점층적 생성자 패턴
    - 필수 인자들을 받는 생성자를 정의, 선택적 인자를 받는 추가적 생성자들을 쌓아 올리듯 추가
    - 단점 1. 인자 수가 늘어나면 클라이언트 코드를 작성하기가 어려워지고 가독성이 떨어짐
    - 단점 2. 설정할 필요가 없는 필드에도 인자를 전닳해야함
    - 단점 3. (많은 선택적 인자를 받는) 생성자 호출시 인자의 타입이 같다면 인자의 순서를 잘못 넣더라도
    컴파일시점에 오류를 검출할 수가 없고 런타임 시점에 문제가 발생하여 오류의 발견이 어려움

    - 장점 1. 생성된 객체의 일관성 = 생성자로 객체를 생성하기 때문에 누락된 값 없이 의도한 객체를 생성

생성자에 전달되는 인자 수가 많을 때
자바빈 패턴
    - 점층적 생성자 패턴의 단점 3. 을 보안
    - 인자 없는 생성자를 호출하여 객체부터 생성하고 설정 메서드(setter methods)들을 호출하여 값을 채움
    - 단점 1. 1회의 호출로 객체 생성을 끝낼 수 없음 = 객체 일관성(consistency)이 일시적으로 깨짐
    - 단점 2. 변경 불가능 클래스를 만들 수 없음 = 스레드 안정성 제공 하기 어려움
    - 단점 3. 누락된 필드값이 발생할 수 있음 = 런타임 시점에 오류 발생, 오류의 발견이 어려움

    - 장점 1. 쉬운 객체 생성, 가독성 향상

빌더 패턴
    - 점층적 생성자 패턴의 안정성 + 자바빈 패턴의 가독성
    - 장점 1. 불필요한 생성자를 만들지 않고 객체 생성 (또는 필수 인자만 받도록)
    - 장점 2. 인자의 순서 무관
    - 장점 3. 가독성 좋음
    - 장점 4. 안정성 (변경 불가능 객체로 생성가능)
    - 장점 5. 객체 생성시 객체의 특성을 나타낼 수 있음

    - 단점 1. 빌더 객체를 추가적으로 생성해야함
    - 단점 2. 코드량 증가

결론
    - 빌더 패턴을 상황에 맞게 적절히 수정해서 사용
        실무에서 멤버변수가 많은 객체를 생성시 항상 빌더 패턴을 사용하는 것은 아니다. 
        일단 DTO 나 VO 등 data entity 인 경우 자바빈 패턴을 사용하는 경우가 더 많고 편리할 때가 많다.
        그 이유는 orm 이나 sqlmap 의 조회 결과를 담을때 builder 패턴을 사용하기가 까다롭다.  mybatis resultmap 의 association 
        빌더패턴으로 객체를 구현하는 것은 인자(인자로 값을 전달 받는 클래스의 멤버변수)가 얼마나 많은지 
        그리고 클라이언트에서 해당 객체를 생성할때 가독성의 중요도등을
        파악해 상황에 맞게 사용하면 될 것 같다.
    - 인자가 많은 생성자를 가진 클래스를 설계할 때, 특히 대부분의 인자가 선택적일 때 유용함
    - 변경이 가능해야 하거나 상태값을 나타내는 변수는 setter method 제공
    - 그 이외의 상황에서는 final 설정 또는 변경 불가능하도록 만듬
    - 빌더 클래스의 설정 메서드는 만드려는 객체를 잘 설명할 수 있도록 만듬, 상황에 따라 여러 인자를 받는 설정 메서드를 만듬
    즉 각각의 비즈니스에 사용되는 인자를 묶어 각 비즈니스를 해결하는 객체를 만듬
    예) Sample s = new Sample.Builder(필수인자, 필수인자..).someBusiness(선택적인자, ...).build();

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");

    }
}

Intelij 설정 및 plugin

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