Java Application을 원격지에서 감시하려면 JMX를 활성화해야 하고, Client로는 jConsole, VisualVM으로 접근하여 확인해야 합니다. GUI 환경이 갖추어지지 않는 CLI 환경은 jstatd (Virtual Machine jstat Daemon)을 활성화하고 rmi (Java Remote Method Invocation)을 이용하여 확인해야 합니다.


jstatd -10080 -J-Djava.security.policy=./java.policy
jps ---v rmi:192.168.0.2:10080
jstat -gcutil -h20 -t rmi:17755@192.168.0.2:10080 1000 1000


Jolokia는 원격지에서 JMX에 접근을 목적으로 사용되는 Java Specification Request (JSR 160)을 구현해놓은 Agent이며, Client와 Agent간의 통신은 HTTP/JSON으로 요청, JSON으로 정보를 받습니다. 



Java Application에 결합된 형태로 배포할 수 있기 때문에 사용성이 좋습니다. 예시는 Spring Boot 기준으로 합니다. Jolokia 활성화를 위해 모듈을 추가합니다.


dependencies {
    compile 'org.jolokia:jolokia-core'
}


Http Endpoint, MBean name 중복방지를 위해 추가 설정이 필요합니다. application.properties에 추가합니다.


management:
    context-path
/manage
    security
:
        enabled
false
 
endpoints
:
    beans
:
        sensitive
false
    jmx
:
        unique-names
true
        enabled
true
    jolokia
:
        enabled
true


Jolokia Protocol은 https://jolokia.org/reference/html/protocol.html에서 확인할 수 있습니다.


버전 확인
GET /manage/jolokia/version
 
Heap Memory 사용량 확인
GET /manage/jolokia/read/java.lang:type=Memory
 
Thread 수 확인
GET /manage/jolokia/read/java.lang:type=Threading/ThreadCount
 
Thread Dump 확인
POST /manage/jolokia
{
   "type":"EXEC",
   "mbean":"java.lang:type=Threading",
   "operation":"dumpAllThreads",
   "arguments":[true,true]
}
 
GC 현황 확인
POST /manage/jolokia
{
    "mbean": "java.lang:type=GarbageCollector,*",
    "type": "read"
}


대표적으로는 Memory, Thread, GC를 확인했으며, 자세한 내용은 Jolokia Protocol 문서를 참고하시기 바랍니다.


참고 사이트

  • https://jolokia.org/
  • https://jolokia.org/reference/html/protocol.html
  • https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#production-ready-jolokia


어렸을 때부터 지속되어 온 생활습관들이 세월이 흘러 중년이 되면 몸에서 질병으로 나타난다. 프로젝트의 요구사항을 기반으로 개발자가 탄생시킨 프로그램도 마찬가지로 에러 메시지로 상황을 알려준다. 


지속해서 눈감고 모른 체하게 되면 프로그램이 중단되는 사태가 발생하는데 질병을 무시했다가 큰 병을 얻는 경우와 같은 상황이다. 

사람은 정기적으로 건강검진을 받는 것처럼, 프로그램도 정기적으로 관리를 해야 한다. 


프로그래밍 언어마다 감시하는 방법이 존재하지만, 예시는 Java 프레임워크 Spring Boot 기준으로 하겠다. 


Spring Boot는 Actuator를 사용하면 프로그램의 상황을 감시할 수 있는데, Http Endpoint, JMX, 원격 SSH 3가지 방법으로 확인할 수 있다.


Actuator를 활성화하려면 모듈을 추가해야 한다. 


dependencies {
    compile 'org.springframework.boot:spring-boot-starter-actuator'
    compile 'org.springframework.boot:spring-boot-starter-security'
    compile 'org.springframework.boot:spring-boot-starter-remote-shell'
}


모듈이 정상적으로 컴파일되면 Http Endpoint, 원격 SSH가 활성화된다. port는 설정하지 않으면 기본값 2000이다.

Http Endpoint의 중복을 방지하기 위해 추가 설정이 필요하다. application.properties에 추가한다.


management:
    context-path: /manage
    security:
        enabled: false
    shell:
        ssh:
            enabled: true
            port: 18080
    health:
        db:
            enabled: true
        diskspace:
            enabled: true
 
endpoints:
    beans:
        sensitive: false


Http Endpoint 목록은 /manage/mappings로 요청하면 확인할 수 있다.


{
  "{[/manage/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)"
  },
  "{[/manage/metrics || /manage/metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/auditevents || /manage/auditevents.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public org.springframework.http.ResponseEntity<?> org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint.findByPrincipalAndAfterAndType(java.lang.String,java.util.Date,java.lang.String)"
  },
  "{[/manage/configprops || /manage/configprops.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/mappings || /manage/mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/application || /application.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public org.springframework.hateoas.ResourceSupport org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint.links()"
  },
  "{[/manage/trace || /manage/trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/heapdump || /manage/heapdump.json],methods=[GET],produces=[application/octet-stream]}": {
    "bean": "endpointHandlerMapping",
    "method": "public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException"
  },
  "{[/manage/autoconfig || /manage/autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/beans || /manage/beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/env/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)"
  },
  "{[/manage/env || /manage/env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/health || /manage/health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest,java.security.Principal)"
  },
  "{[/manage/dump || /manage/dump.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/info || /manage/info.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  },
  "{[/manage/loggers/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.get(java.lang.String)"
  },
  "{[/manage/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)"
  },
  "{[/manage/loggers || /manage/loggers.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}": {
    "bean": "endpointHandlerMapping",
    "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()"
  }
}


대표적으로 많이 쓰이는 것은 2가지이다. 

상태 값 확인은 /manage/health를 사용하면 되고, 추가 설정을 하게 되면 redis, mysql 저장소도 확인할 수 있다.


{
  "status": "UP",
  "diskSpace": {
    "status": "UP",
    "total": 214734073856,
    "free": 195815051264,
    "threshold": 10485760
  }
}


Cpu, Memory, Threads, GC, Datasource를 감시하기 위해서는 /manage/metrics를 사용한다.


{
  "mem": 12496579,
  "mem.free": 8820382,
  "processors": 8,
  "instance.uptime": 81630586,
  "uptime": 81641671,
  "systemload.average": 0.05,
  "heap.committed": 12340992,
  "heap.init": 12582912,
  "heap.used": 3520609,
  "heap": 12340992,
  "nonheap.committed": 159528,
  "nonheap.init": 2496,
  "nonheap.used": 155587,
  "nonheap": 0,
  "threads.peak": 342,
  "threads.daemon": 234,
  "threads.totalStarted": 3086,
  "threads": 287,
  "classes": 14031,
  "classes.loaded": 14031,
  "classes.unloaded": 0,
  "gc.parnew.count": 522,
  "gc.parnew.time": 9718,
  "gc.concurrentmarksweep.count": 2,
  "gc.concurrentmarksweep.time": 605,
  "httpsessions.max": -1,
  "httpsessions.active": 0,
  "datasource.primary.active": 1,
  "datasource.primary.usage": 0.033333335,
  "datasource.slave.active": 0,
  "datasource.slave.usage": 0.0,
}


원격 SSH는 Spring Boot 2.0에서는 제거되었다. 하지만 기능이 매우 괜찮아서 아쉽기도 하다. 

별도로 유저, 비밀번호 설정을 하지 않으면, 최초 시작 시 로그에서 무작위로 생성되는 비밀번호를 확인해야 한다.


Using default security password: 6a37007e-01c9-465c-b9d8-95a7c88afd38


정상적으로 접속되면 help를 입력하여 확인하자.


$ ssh -18080 user@localhost
Password authentication
Password:
 
 :: Spring Boot ::  (v1.5.8.RELEASE) on localhost
> help
Try one of these commands with the -h or --help switch:
 
NAME       DESCRIPTION
autoconfig Display auto configuration report from ApplicationContext
beans      Display beans in ApplicationContext
cron       manages the cron plugin
dashboard  a monitoring dashboard
egrep      search file(s) for lines that match a pattern
endpoint   Invoke actuator endpoints
env        display the term env
filter     a filter for a stream of map
java       various java language commands
jul        java.util.logging commands
jvm        JVM informations
less       opposite of more
mail       interact with emails
man        format and display the on-line manual pages
metrics    Display metrics provided by Spring Boot
shell      shell related command
sleep      sleep for some time
sort       sort a map
system     vm system properties commands
thread     JVM thread commands
help       provides basic help
repl       list the repl or change the current repl
 
>


dashboard를 입력하면 Thread, Memory 현황을 실시간으로 확인할 수 있다.



Java에서 제공하는 jmap, jstat등을 이용한다면 기본적인 감시는 가능하겠지만, Spring Boot Actuator는 기능이 확장되어 좀 더 쉽게 상황을 진단할 수 있다. 


프로젝트 일정에 너무 치이다 보면 실수를 인지하지 못한 상태로 운영단계에 들어가게 되는데, 반드시 검수 일정을 확보하여 Spring Boot Actuator 기능을 기반으로 실수를 사전에 감시하여 제거했으면 한다.


참고 사이트

  • https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready


Java Application 개발을 하면서 처음부터 완벽하게 만든 다는 것은 능력자 가 아닌 이상 흔하지 않다. 잠재적인 결함이 있을 수 있고, 필수 값 체크를 못했다면 친숙한 Exception 인 NullPointerException 을 만나게 된다. 최근에 경험했던 일 이다.


Java String.split(String regex) 사용 시 '|' 파싱 이 안되는 경우 (http://blog.whitelife.co.kr/222)


배열에서 파싱 할때 split("|") 라고 작성 했을 때 Escape 처리를 해줘야 동작 한다. FindBugs 는 기본적으로 발생 하는 결함을 확인하고 Report 해준다. 이외에 분석 도구는 Checkstyle, cobertura, PMD 등 이 있다.


Eclipse 도 Problems Report 를 해준다. 무심결에 지나치기 보다 잠깐 이라도 보고 넘어가자.


FindBugs: http://findbugs.sourceforge.net/


Maven, Eclipse Plugin 형태로 제공 된다. Eclipse 기준으로 진행 하겠다.



Help > Eclipse Marketplace 클릭 한후 Find 에 FindBugs 라고 검색 한다. Install 버튼을 클릭 하자.



친절하게 가이드를 해준다. Next 를 계속 하자. 설치 도중 Security Warning 가 나오면 OK 하자.



완료 되었다면 재 시작 한다. Window > Show View 에 FindBugs 폴더가 추가 되는대 Bug Explorer 를 클릭 한다.



하단에 출력 창이 추가 됬을 것 이다.


Project Explorer 에서 마우스 오른쪽 클릭을 하면 FindBugs 라는 메뉴가 있다. 하위 메뉴도 FindBugs 를 클릭 한다.



결함이 있다면 Report 된다. 자세한 내용을 알고 싶다면 + 아이콘 클릭 후 하위 에 있는 값을 클릭 해보거나, 마우스 오른쪽 클릭 Show Bug Info 화면으로 이동하여 참고 하자.


FindBugs 는 강제적으로 해야 하는 것이 아닌, 선택 적이다. 프로그래밍도 습관이 중요 하다고 생각 한다. 당장 +1분, +5분, +10분 투자 하는게 아깝다고 하는 것 보다, 조금씩 투자 하다 보면, -60분, -120분, -180분 보상 받을 것 이다. 치명적인 결함이 줄어드므로...


프로젝트를 접하다 보면 화면에 출력 하거나 긴 문자열을 간추려서 사용 할 때, 문자열에서 Html 태그를 제거 해야 하는 경우가 있는대 유용한 라이브러리가 있다.



사용하기 편리한 유틸, Spring Framework 사용 시 지원 해주는 유틸 등 제공 된다.


pom.xml 에 추가 하자.


<dependency>
  <groupId>com.lyncode</groupId>
  <artifactId>jtwig-functions</artifactId>
  <version>2.0.1</version>
</dependency>


사용 방법


import static com.lyncode.jtwig.functions.util.HtmlUtils.stripTags;

public class StripTagsExample {
    public static void main(String... args) {
        String result = stripTags("<!-- <a href='test'></a>--><a>Test</a>", "");
        // Produced result: Test
    }
}

참고 사이트


Java Mail을 기반으로 하여 작성된 Spring Mail 라이브러리가 있다. 적용 해 보자.


pom.xml에 라이브러리를 추가 한다.


<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>3.0.5.RELEASE</version>
</dependency>


applicationContext.xml 에 Mail Sender Class 설정을 추가 하자. 필요에 따라 Properties 에서 SpringExpressionLanguage (SpEL) 문법을 사용해도 된다.


<util:properties id="config" location="classpath:config/config.properties" />

<!-- Spring mail configuration -->
<beans:bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
     <beans:property name="host" value="#{config['mail.host']}" />
     <beans:property name="port" value="#{T(java.lang.Integer).parseInt(config['mail.port'])}" />
     <beans:property name="username" value="#{config['mail.username']}" />
     <beans:property name="password" value="#{config['mail.password']}" />
     <beans:property name="defaultEncoding" value="#{config['mail.defaultEncoding']}" />

     <beans:property name="javaMailProperties">
        <beans:props>
            <beans:prop key="mail.transport.protocol">#{config['mail.protocol']}</beans:prop>
            <beans:prop key="mail.smtp.auth">true</beans:prop>
            <beans:prop key="mail.smtp.starttls.enable">false</beans:prop>
        </beans:props>
     </beans:property>
</beans:bean>


메일을 보낼 수 있는 Interface 를 작성 한다. 제네릭 문법으로 report 를 받는 것은 원하는 타입에 맞게 메일 내용을 작성 할 수 있게 하기 위해서 이다.


public interface Publisher {
    public <T> boolean publish(T report);
}


실제 메일 발송에 대한 구현 클래스 이다.


@Service("EmailPublisherService")
public class EmailPublisherService implements Publisher {

    private static final Logger logger = LoggerFactory.getLogger(EmailPublisherService.class);

    @Autowired
    private JavaMailSender javaMailSender;

    @Override
    public <T> boolean publish(T report) {
        logger.debug("Sending report by email...");
        boolean retVal = false;
        try {
            final String emailTo = "to@test.co.kr";
            final String emailFrom = "from@test.co.kr";
            final String subject = "test subject";
            final String message = (String) report;

            javaMailSender.send(new MimeMessagePreparator() {

                @Override
                public void prepare(MimeMessage paramMimeMessage) throws Exception {
                    MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(paramMimeMessage, true, "UTF-8");

                    mimeMessageHelper.setTo(emailTo);
                    mimeMessageHelper.setFrom(emailFrom);
                    mimeMessageHelper.setSubject(subject);
                    mimeMessageHelper.setText(message);

                    final File file = new File("test filename");

                    mimeMessageHelper.addAttachment(MimeUtility.encodeText("filename"), new InputStreamSource() {

                        @Override
                        public InputStream getInputStream() throws IOException {
                            // TODO Auto-generated method stub
                            return new FileInputStream(file);
                        }
                    });

                };
            });

            retVal = true;
        } catch (Exception e) {
            logger.error("Can't send email... " + e.getMessage(), e);
        }
        return retVal;
    }
}


메일에 첨부 파일이 있는 경우 new MimeMessageHelper(MimeMessage mimeMessage, boolean multipart, String encoding) 중 multipart 값을 true 로 변경 하고, MimeMessageHelper.addAttachment(attachmentFilename, inputStreamSource) 메소드를 사용 하자. 단 파일 명을 MimeUtility.encodeText(String text) 하지 않으면, 한글이 께져서 발송 된다.


@Controller
@RequestMapping(value="/mail")
public class SampleController {

    @Resource(name="EmailPublisherService")
    private EmailPublisherService emailPublisherService;

    @RequestMapping(value="/send")
    public @ResponseBody String send() throws Exception {
        try {
            emailPublisherService.publish("text message...");
            return "Success";
        } catch (Exception e) {
            throw e;
        }
    }
}


Controller 에 작성한 EmailPublisherService 를 Dependency Injection(DI) 하여 메일을 보내 보자.


참고 사이트


개발을 할 때 기존 기능을 보존 하면서, 확장 해야 하는 경우가 있다. 원본을 어쩔 수 없이 수정 해야 할 때는 피할 수 없지만, 확장만 한다면, Proxy 를 사용 하는 경우 쉽게 확장이 가능 하다. 사용 방법을 알아 보자.


Proxy 는 기존 에 만들었던 Class 를 새로운 Class로 포장 했다고 생각 하면 쉽다.


public class Packing {

    private Target target;

    public Packing(Target target) {
        this.target = target;
    }

    // doing...

}


위 소스를 주목 하자. 포장지 에 기존 Class 를 담았다. 하지만 먼가가 부족 하다. 함수를 연결해줄 고리가 필요 하다. 그것이 InvocationHandler 이다. java.lang.reflect 패키지에서 제공 하고 있다.


포장지에 연결 고리를 달아보자.


public class Packing implements InvocationHandler {

    // doing...

    @Override
    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
        return paramMethod.invoke(paramObject, paramArrayOfObject);
    }

}


invoke 라는 함수를 볼 수 있다. 포장지와, 기존 Class 를 연결 시켜주는 역할을 한다.


paramObject: targetClass (기존 Class) 를 넣는다.
paramMethod: targetClass 에서 실행 하고 싶은 메소드를 넣는다.
paramArrayOfObject: targetClass 에서 실행하고 싶은 메소드의 파라미터 값을 넣는다.


위 3가지 설정을 끝내면 준비 작업은 끝났다. 3가지 정보에 따라 포장지에서 기존 Class 함수를 실행 하게 된다. 샘플을 만들어 보자.


Github: https://github.com/whitelife/whitelife-sample/tree/master/src/main/java/kr/co/whitelife/sample/proxy


  • Target
public class Target {

    public void doProcess() {
        System.out.println("doProcess.....");
    }

    public String doStringProcess(String string) {
        System.out.println("doStringProcess.....");
        return string;
    }

}

  • Packing
public class Packing implements InvocationHandler {

    private Target target;

    public Packing(Target target) {
        this.target = target;
    }

    public void doProcess() {
        try {
            System.out.println("Packing doProcess start.....");

            this.invoke(this.target, this.target.getClass().getDeclaredMethod("doProcess", new Class[]{}), new Object[]{});

            System.out.println("Packing doProcess end.....");
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public void doStringProcess(String string) {
        try {
            System.out.println("Packing doStringProcess start.....");

            String result = (String) this.invoke(this.target, this.target.getClass().getDeclaredMethod("doStringProcess", new Class[]{String.class}), new Object[]{string});
            System.out.println(result);

            System.out.println("Packing doStringProcess end.....");
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
        return paramMethod.invoke(paramObject, paramArrayOfObject);
    }
}

  • ProxyTest
public class ProxyTest {

    public static void main(String[] args) {
        Packing packing = new Packing(new Target());

        packing.doProcess();
        System.out.println();

        packing.doStringProcess("doString");
    }

}


샘플 작성이 끝났다면 실행 해 보자. 아래와 같은 출력을 볼 수 있다.


Packing doProcess start.....
doProcess.....
Packing doProcess end.....

Packing doStringProcess start.....
doStringProcess.....
doString
Packing doStringProcess end.....


여기 까지 왔다면, 성공 한 것이다. 기존에 각자 사용하던 Class를 Target 으로 생각하고, 포장지를 만들어서 사용하면 된다.


만약 Spring Framework 를 사용하는 경우, 직접 구현할 필요 없이 Spring AOP Around 기능을 사용하면 쉽게 처리가 가능 할 것이다. 위 샘플 코드와 비슷 하기 때문이다.


대표 적인 로그 라이브러리라 볼 수 있다. 로그를 출력 하는 Appenders 설정을 찾을 수 없기 때문에 발생 한다.


보통의 경우 classpath, Web application 의 경우 /WEB-INF/ 하위 경로 를 사용하기도 한다.


xml, properties 두 가지 스타일로 설정을 하면 된다. xml 로 설정을 해보자.
해당 파일을 추가 하자.


  • log4j.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <!-- Appenders -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %5p %c{1} - %m%n" />
        </layout>
    </appender>

    <!-- Application Loggers -->
    <logger name="kr.co.whitelife.spring">
        <level value="debug" />
    </logger>

    <!-- Root Logger -->
    <root>
        <priority value="debug" />
        <appender-ref ref="console" />
    </root>

</log4j:configuration>


Global 로그 설정 부분은 출력 할 appender 설정, 로그 레벨 설정 부분이다. Root Logger 부분을 참고 하고, 특정 package log 만 설정을 원하는 경우, Application Loggers 부분을 참고 하자.


Logger logger = LoggerFactory.getLogger(Sample.class);
logger.debug("I am {}", "boy");


테스트 코드를 실행 해보자. log 를 확인 할 수 있다.


'Java' 카테고리의 다른 글

Java Html 태그 제거 하기  (0) 2014.10.29
Java Proxy 사용하기  (0) 2014.10.13
Java Clone 사용 하기  (0) 2014.10.10
Java String.split(String regex) 사용 시 '|' 파싱 이 안되는 경우  (1) 2014.10.08
Java HashMap Key 정렬 하기  (0) 2014.09.24

원본 데이터의 보존이 중요 시 되는 경우, 사용하게 된다. 복제 기능에 대해서는 Cloneable 라는 Interface 를 구현 해야 한다.


public abstract interface Cloneable {
}


Object 클래스에 정의 되어 있다. 용도에 따라 얼마든지 Override 해서 사용 하면 된다.


protected native Object clone() throws CloneNotSupportedException;


접근 제한자가 protected 로 되어 있기 때문에, 외부에서는 접근이 불가능 하므로 아래와 같이 선언 해야 외부에서 사용이 가능 하다. Cloneable Interface 를 implements 하지 않았을 경우, java.lang.CloneNotSupportedException 이 발생 하니 주의 하자.


public class Model implements Cloneable {

    // Doing...

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}


메소드 정의가 끝났다면, clone() 을 호출 하여 사용 하도록 하자.


+ Recent posts