반응형
반응형

1. 개요

 - 업무를 하던 도중 특정 로직에서 Enhancer, MethodInterceptor이라는 객체를 사용하고 있었다. 로직을 분석해봐도 이해가 잘 가지 않았던 구조였기 때문에 공부의 필요성을 느껴 공부 후 내용을 정리한다.


2. Enhancer란?

 - Enhancer의 사전적 의미는 기능을 높이는 것, 증진시키는 것을 의미한다. 이를 프록시에 대입하여 생각해보니 프록시 객체는 다양한 객체를 호출할 수 있기때문에 기능이 증진된다는 것과 비슷한 맥락을 갖는 것 같다.

 - 로직적으로 이 객체는 프록시 객체를 생성하는 역할을 한다.


3. Enhancer를 사용한 프록시 객체 생성

 - Enhancer 객체를 사용하여 프록시 객체를 생성하는 예제이다.

 

3.1) EnhancerTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ssk.simpany.test.methodInterceptorTest;
 
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.NoOp;
 
public class EnhancerTest {
 
    public static void main(String[] args) {
        
        Enhancer enhancer = new Enhancer(); //Enhancer 객체 생성
        
        enhancer.setSuperclass(MyService.class); // 타켓 클래스 지정
        enhancer.setCallback(NoOp.INSTANCE);      // 옵션  (NoOp, MethodIntetceptor 등)
        Object targetObj = enhancer.create();     // 프록시 객체 생성
        
        if(targetObj instanceof MyService){           // targetObj 객체가 MyService로 형 변환이 가능한지
            //형변환 가능.
            MyService myService = (MyService)targetObj; //형변환
            myService.myServiceMethod("test");
        }
    }
}
 
cs

 

3.2) MyService.java

1
2
3
4
5
6
7
8
9
package com.ssk.simpany.test.methodInterceptorTest;
 
public class MyService {
 
    public String myServiceMethod(String a){
        System.out.println("call my ServiceMethod is "+a);
        return a;
    }
}
cs

 

- EnhancerTest 클래스에서 프록시 객체를 생성할 수 있는 Enhancer 객체를 생성하고 setSuperclass 메서드를 사용하여 타겟 클래스를 지정한다.

- setCallback 메서드에는 NoOp.INSTANCE(NoOption)을 입력했는데, 말 그대로 옵션이 없는 프록시객체로 설정하기 위함이다.

- NoOp로 설정하면 단순히 프록시 객체를 통해 타겟 클래스만 호출하는 것이고, MethodInterceptor로 설정하면 타겟 클래스 호출 전 후로 로직을 넣거나 매개변수를 변경하는 등의 작업이 가능하다. 후자의 경우도 다뤄보도록 할 예정이다.

- enhancer.create() 메서드를 호출하면 마침내 프록시 객체를 생성하게 된다.

- Object 형으로 반환되게 되지만 내부적으로는 앞서 설정한 타켓 클래스이기 때문에 instanceof를 사용하여 형 변환 또한 가능함을 확인할 수 있다. 결과적으로 enhance 에서 생성한 프록시 객체를 통해 MyService객체를 호출한다.

 

3.3) 결과

프록시 객체를 통한 MyService 호출 결과

 

  사실 위 예제를 직접 작성했을땐 '그냥 객체 생성하고 호출하면 될것이지 왜 프록시까지 굳이 생성해서 호출하는걸까?' 라는 생각과 함께 비지니스 로직에 의해 동적으로 호출되어야 할 객체가 있다면 이를 사용했을 때 어느정도 효과를 볼 수 있지 않을까라는 생각도 들었다. 타겟 객체를 생성하는 게 아닌 호출하는 것이므로 메모리적으로도 좋지않을까? 혹시 누군가 알고계시다면 댓글로 달아주시길 부탁드리와요..


4. MethodInterceptor란?

 프록시 객체의 콜백 기능 중 하나로, 프록시 객체에서 타겟 객체를 호출하기 전, 후로 비지니스 로직을 추가하거나, 타겟 클래스가 아닌 다른 객체의 메소드를 호출하거나, 인자 값을 변경할 수 있다.

 구현 방법은 커스텀한 클래스에 MethodInterceptor 인터페이스를 상속받은 후 intercept 메서드를 오버라이드 한다.

 

4.1) MethodInterceptorTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.ssk.simpany.test.methodInterceptorTest;
 
import java.lang.reflect.Method;
 
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
 
public class MethodInterceptorTest implements MethodInterceptor{
 
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before target Proxy");
        Object returnValue = methodProxy.invokeSuper(object, args);
        System.out.println("after target Proxy");
        return returnValue;
    }
}
cs

 

4.2) Main.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.ssk.simpany.test.methodInterceptorTest;
 
import org.springframework.cglib.proxy.Enhancer;
 
public class Main {
 
    public static void main(String[] args) {
        
        Enhancer enhancer = new Enhancer();     // enhancer 객체 생성
        enhancer.setSuperclass(MyService.class);// 타겟 클래스 지정
        enhancer.setCallback(new MethodInterceptorTest()); //콜백으로 MethodInterceptorTest 객체 설정
        Object targetObj = enhancer.create();    //프록시 생성
        MyService myService = (MyService)targetObj; //형변환
        myService.myServiceMethod("test"); //프록시 객체로 메서드 호출
    }
}
 
cs

- MethodInterceptor를 사용하여 아주 간단한 프록시 객체를 구현했다. 기존과 다른 건 MethodInterceptorTest 클래스를 구현한 후 callback 으로 설정했다는 점이다.

- 이 설정으로 인해 프록시 객체로 메서드를 호출하면 MethodInterceptorTest 클래스의 intercept 메서드가 호출되게 되어 before target Proxy 문자열 호출 후 타겟 클래스가 실행됨을 확인할 수 있다.

참고로 methodProxy.invokeSuper의 리턴 값은 타겟 객체의 리턴 값이다.

 

4.3) 결과

MethodInterceptor를 적용한 프록시 객체 호출 결과


5. 마치며

 지금은 단순히 enhancer와 MethodInterceptor를 이용해 프록시를 구현하는 것만 해보아서 그런지 느낌만 알 뿐, 아직도 머릿속에서 정리가 되지 않은 느낌이다. 또한 어떻게 활용되는지도 크게 와닿지 않는다. 다음 게시글로 이 방식을 활용하여 다양한 예제를 만들어보도록 하겠다.

반응형
반응형

1. 개요

 - 클래스에 접근을 제어하는 접근제어자에 대해 복습하였다.


2. 접근제어자

 - 클래스의 멤버변수와 메서드는 접근제어자를 통해 외부에서의 접근을 제어할 수 있다.

 - 접근제어자의 종류와 특징은 다음과 같으며, public과 private가 자주 사용된다.

 

  > public : 클래스의 외부에서 접근이 가능.

  > private : 클래스 내부에서만 접근 가능.

  > default : 동일 패키지에 있는 다른 클래스에서 접근 가능.

  > protected : 동일 패키지의 다른 클래스와 다른 패키지의 하위클래스에서 접근 가능 

 


3. public

 - MyClass 멤버변수의 접근제어자를 public으로 설정하면 외부 클래스인 AccessMain 클래스에서 접근이 가능하다.

 - 멤버변수가 public으로 되어있기 때문에 위 객체의 값을 어디서든 수정, 조회할 수 있다.

MyClass
AccessMain


4. private

 - MyClass 멤버변수의 접근제어자를 private로 설정하면 외부 클래스인 AccessMain 클래스에서 접근이 불가능하다.

 - 예제의 AccessMain 클래스를 보면 7번째 줄에서 컴파일 에러가 발생함을 확인할 수 있다.

 - 접근제어자가 public인 getter, setter 메서드를 생성하면 메서드를 통한 접근이 가능해진다. (AccessMain : 9 line)

 - 만약 특정 객체에 대해 값을 조회하는 기능만 넣고싶다면 public 형식의 getter만 생성하면 된다.

MyClass


5. default

 - MyClass 멤버변수의 접근제어자 설정하지 않으면 default로 설정되며 같은 패키지 내의 클래스에서만 접근할 수 있다.

 - MyClass와 AccessMain 클래스는 같이 패키지에 위치하므로 접근이 가능하다.

 - AccessMain2 클래스는 MyClass의 하위 패키지에 위치하므로 접근이 불가능하다.

MyClass

 

AccessMain

 

AccessMain2


5. protected

 - MyClass 멤버변수의 접근제어자를 protected로 설정하면 같은 패키지 내의 클래스와 자식클래스에서 접근할 수 있다.

 - MyClass와 AccessMain 클래스는 같이 패키지에 위치하므로 접근이 가능하다.

 - AccessMain2 클래스는 MyClass의 하위 패키지에 위치하지만 MyClass를 상속받은 자식클래스이므로 접근할 수 있다.

MyClass

 

AccessMain

 

AccessMain2


6. 마치며

 접근제어자에 대한 복습을 하니, 머릿속에 한번 더 정리되는 느낌이 들어 좋다. default는 있는지도 몰랐는데... 신기했으나 앞으로 쓸일은 없을것같다!

반응형
반응형

1. 개요

 스터디로 File 업로드, 다운로드 로직을 작성하던 도중, 특정 부분에 빨간줄이 등장했다. 자연스럽게 마우스 가져가보니 Exception!. 별생각 없이 add thrwos declaration 클릭. 상황종료.

이처럼 예외 처리에 큰 의미나 생각을 부여하지 않았다.

두둥등장한 에러.

 그러던 어느날, 고객사측에서 에러 문의가 들어와 로그를 확인해보니 딱 봐도 DB 예외로 보이는 ERDBException이 발생하고 있었다.(커스텀 익셉션인 것!) 그래서 DB 예외라는 필터를 머릿속에 장착한 뒤 로직을 확인해보니... 이게웬걸.. 모든 예외에 대한 처리를 ERDBException으로 던지고있었다.

 그리고 깨달았다. 예외처리를 제대로 하지 않으면 유지보수 시 혼란을 초래할 수 있다는 것을...


2. 예외? 에러?

 예외에 대한 기본 개념을 정립하기 위해 구글링을 하니 "예외와 에러의 차이" 가 눈에 들어왔다. 사실 이 두 개념은 비슷하다고 생각했으나, 막상 확인해보니 예외 공부에 아주 중요한 키포인트가 됐던 내용이었다.

 정리하면 에러는 시스템 레벨에서 발생하는 아주 심각한 수준의 문제이다. 예를들면 서버의 과부하가 걸려 발생하는 OutOfMemory 같은 것이다. 이러한 에러는 프로그래머가 미리 예측하지 못하며 로직으로 처리할 수 없다.

 이에반해 예외는 프로그래머가 작성한 로직으로 인해 발생하는 문제이다.  미리 예측하여 처리할 수 있기 때문에 올바른 처리방법을 통해 핸들링하는 것이 중요하다.

예외와 에러의 계층구조 (출처 : https://velog.io/@new_wisdom/Exception)


3. 예외 코드 예제

 예외 코드에 대한 예제 코드를 txt 파일을 조회하는 것으로 작성해보도록 하겠다.

예외 처리를 안한 코드

 위 코드에 빨간줄이 아주 많다; 빨간줄이 나타나는 이유는 예외처리를 하지 않아서인데, 이처럼 예외처리를 꼭 해줘야하는 것들이 있다. 이러한 예외들을 CheckedException이라고 한다. 말 그대로 컴파일 시점에서 체크해주는 예외이다.

 런타임 시점에 발생하는 예외들도 있는데 이는 UnCheckedException이라고도 하고 RuntimException이라고 한다. 이러한 예외는 컴파일 시점에 체크되지 않아 위 소스처럼 에러가 뜨지 않는다.


4. try, catch, finally

 예외를 처리하기 위해서 사용하는 기본적인 문법이 있다. 바로 try, catch, finally이다.

 try는 예외 발생 가능성이 있는 로직이 포함된 블럭, catch는 예외가 발생했을 때 처리되는 로직이 포함된 블럭, finally는 예외가 발생하던 안하던 최종적으로 처리되는 로직이 포함된 블럭이다.

간단한 예제를 통해 실습해보자.

 

예외 멈춰!

 

예외 멈춰! 결과

현재 File 객체의 매개변수 값으로 텍스트파일의 경로를 넣어놨는데, 이 경로는 실제로 존재하지 않는 파일의 경로이다. 이로인해 try 문 안의 fis = new FileInputStream 부분에서 FileNotFoundException (FileNotFoundException 예외는 Exception을 상속하고 있음)  이 발생하게 되는데, 이때 catch 블록으로 이동하여 예외 처리 후, 최종적으로 finally문에서 스트림을 닫게 된다.

 

그럼 이런 의문이 들 수 있다. 

 

 Q : catch의 매개변수에 Exception을 넣으면 결론적으로 try 문에서 발생한 모든 예외들은 저 하나의 catch문에서 처리되는게 아닌가? 그럼 다양한 예외에 대한 핸들링하지 못하잖아?

 

맞다. 저렇게 무작정 Exception으로만 넣게되면, 모든 예외를 한곳에서 핸드링하는 격이기 때문에 상황에 맞게 예외를 분기해서 처리해야 한다. 한곳에서 처리하는 것이 효율적인 예외도 있겠지만, 예를 들어 로직에 SQLException과 FileNotFoundException 예외가 발생할 수 있는 상황이라면 각각의 예외에 대한 세부 로그를 남기는 등의 예외처리를 하면 좋지 않을까 싶다. (참고로 모든 예외를 Exception으로 통일한 프로젝트도 있었다.)


5. throw, throws

 예외란 프로그래머가 작성한 로직에 의해 발생된다. 그렇다면 프로그래머가 예외를 발생시킬수도 있을까? 있다. 추가적으로 예외에 대한 책임을 전가할수도 있다. 이게 바로 throw와 throws이다.

 

 - throw는 예외를 강제로 발생시킨 후, 상위 블럭이나 catch문으로 예외를 던진다.

throw

위 예제는 main 메서드에서 myException메서드를 호출하고, 여기서 throw를 통해 Exception을 강제로 발생시키고 있다.

때문에 catch 블럭으로 처리가 위임되고, 여기서 예외를 처리하고 있다. 이는 콘솔 로그를 통해 알수있다.

 

 - throws는 예외가 발생하면 상위메서드로 예외를 던진다.

throws

일반적으로 throws를 사용하면 try, catch 구문이 생성되지 않는 것을 확인할 수 있는데, 이 이유는 throws구문에 의해 예외에 대한 처리를 호출부로 위임하기 때문이다.

throw를 통해 예외를 발생시키고 throws는 이 예외를 밖으로 던져버리고 있다.

추가적으로 이 두가지를 합친 방식도 있다.

 

throw + throws는 예외처리를 catch문에서도 하고, 호출부로 예외를 던진다.

throw + throws

22줄에 throw e를 통해 예외를 발생시키면 throws에 의해 상위 메서드로 예외를 던지게 된다.

예외처리를 예외 발생지와 호출부에서 하고싶다면 이와 같은 방법을 사용하면 된다.

반응형
반응형

목차

1. 정규 표현식이란

2. 정규식 패턴

3. 정규식 메소드

4. 예제


1. 정규 표현식이란

 정규 표현식이란 특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어이다.

 형식 언어를 사용하여 특정한 규칙을 가진 문자열의 패턴을 파악한다면 텍스트 편집, 포맷팅, 검색, 치환 등의 문자열 작업이 가능하게 된다. 기본적으로 패턴은 두 개의 '/(슬래시)' 사이에 작성한다. 

 

 예를 들어 '20190809'라는 문자열에서 패턴을 파악하여 '2019''08''09' 형태로 나누고, 그 사이에'-' 문자열을 삽입하여 '2019-08-09' 형태로 만들 수 있고, 저렇게 만든 날짜형 데이터에서 사용 가능한 메소드인 getDay(), getFullYear() 등을 사용할 수 있게 된다. 표현을 위한 포맷팅뿐 아니라 메소드의 사용까지도 가능하게 해주는 좋은 녀석이다.


2. 정규식 패턴

 그렇다면 이 패턴은 어떻게 만들까? 패턴을 만드는 방법은 크게 두 가지이다.

 첫째, 단순 패턴 사용

 둘째, 특수 문자 사용

 

 2.1 단순 패턴 사용

  단순 패턴은 단순히 문자열을 대응시킬 때 사용한다. 예를 들어 /hi/라는 패턴은 문자열에서 hi라는 문자열이 그대로 나타나야 대응된다. "hi, hellow"에서 hi라는 문자열이 대응된다.

 연습이 필요하다고 생각한다면 정규식을 테스트할 수 있는 사이트(https://regexr.com)를 들어가 보길 권한다. 참고로 끝에 있는 'g'는 패턴 속성으로 대상 문자열의 처음부터 끝까지 해당 패턴을 적용시키는 것이다. 이 속성이 없다면 'hi hi hi' 문자열의 대응 부분은 처음의 'hi' 하나뿐이다.

regexr.com

 

 2.2 특수 문자 사용

  단순 패턴을 사용하여 대응하기 힘든 것을 대응시키고자 할 경우 패턴에 특수 문자를 사용한다. 예를 들어, /^hi/ 패턴은 문자열의 시작이 'hi' 문자로 시작되는 것에 대응한다. 만약 'hi, hellow' 문자열에 이 패턴을 적용시킨다면 'hi'가 대응되며, 'oh, hi'라는 문자열에 적용시킨다면 대응되는 문자는 아무것도 없다.

 

특수문자의 종류와 기능이다.

특수 문자 기능 예시
^ 입력의 시작 부분과 대응된다. /^시작/ 시작했다 시작
$ 입력의 끝 부분과 대응된다.  /끝$/ → 끝났다 
* 표현식이 0회 이상 연속으로 반복되는 부분과 대응된다.

/ab*c/ → cbbabbbbcdebc, acbd

+ 표현식이 1회 이상 연속으로 반복되는 부분과 대응된다. /ab+c/ → cbbabbbbcdebc, acbd
? 표현식이 0 또는 1회 등장하는 부분과 대응된다. {0,1} 과 같다. 있어도 ok, 없어도 ok 이라고 생각하면 편하다 /010?/ 01, 010, 011
. 개행 문자를 제외한 모든 단일 문자와 대응된다. /hi./ → hi, hi1, hi2, hi12, hi 
(x) 패턴의 부분을 나누는 것. 포맷팅에 유용하게 쓰인다.

/(a)(b)/ ab

replace(/(a)(b)/,'$1-$2') → a-b

x|y | 는 or 와 동일한 기능을 하며, 하나라도 있을 시 대응된다. /fuck|suck|dick/ fuckyou, suckyou
{n} 표현식이 n번 나타나는 부분에 대응된다. /(hi){2}/ → hi, hihi, hi hi
{n,m} 표현식이 n번~m번 나오는 부분과 대응된다. /(hi){2,3}/ → hi,hihi,hihihi,hihihihi
[abc] 대괄호 안의 문자가 하나라도 있을시 대응된다. /[abc]/g a,ab,abc

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/%EC%A0%95%EA%B7%9C%EC%8B%9D

 

정규 표현식

정규 표현식은 문자열에 나타는 특정 문자 조합과 대응시키기 위해 사용되는 패턴입니다. 자바스크립트에서, 정규 표현식 또한 객체입니다.  이 패턴들은 RegExp의 exec 메소드와 test 메소드  ,그리고 String의  match메소드 , replace메소드 , search메소드 ,  split 메소드와 함께 쓰입니다 . 이 장에서는 자바스크립트의 정규식에 대하여 설명합니다.

developer.mozilla.org

더 많은 특수 문자에 대해 알아보고 싶다면 위의 링크로 들어가 보길 권한다.


3. 정규식 메소드

 정규식을 만들어 적용시키기 위해서는 정규식 메소드를 사용해야 한다.

 메소드 또한 위의 링크에서 잘 정리되어있기 때문에 많이 사용되는 메소드만 정리하겠다.

메소드 명 사용 방법 기능
exec pattern.exec(String) 패턴을 String에 적용하여 실행시킨다.
test pattern.test(String) 패턴을 String에 적용시킨 결과가 존재하는지 true와 false로 출력시킨다
replace String.replace(pattern,'type') 패턴을 String에 적용시키고 type으로 치환한다.

특정 문자를 제거하고 싶다면 String.replace(/\-/g, '')

 

반대로 String에 구역을 나눠 해당 구역 간격으로 '-' 문자를 넣어주고 싶다면 

 

번호에 '-' 기호 삽입

참고로 replace의 두 번째 인자인 $1, $2, $3는 정규식에서 대응되는 각각의 부분이다.


4. 예제

 입력받은 두 날짜의 시간 차이 계산하기

 

 4.1) 소스코드

  4.1.1) html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <title>Page Title</title>
    <script type="text/javascript" src="3js.js"></script>
</head>
<body>
    <input type="text" id = "date1">
    <input type="text" id = "date2">
    <input type="button" id = "dateButton" value="계산">
</body>
</html>

 

  4.1.2) Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
window.onload = function(){  
    document.getElementById("dateButton").addEventListener("click",dateCalcul);
    function dateCalcul(){
        var date1 = document.getElementById("date1").value;
        var date2 = document.getElementById("date2").value;
        var pattern = /(^(19|20)\d{2})(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])$/
        if(pattern.test(date1) && pattern.test(date2)){
            var formatDate1 = date1.replace(/(\d{4})(\d{2})(\d{2})/,'$1-$2-$3'); //YYYYMMDD 형태
            var formatDate2 = date2.replace(/(\d{4})(\d{2})(\d{2})/,'$1-$2-$3');
            var checkDate1 = new Date(formatDate1).getDate(); //1,2,3,4 형태, 메소드 적용 데이터
            var checkDate2 = new Date(formatDate2).getDate();
            checkDate1 = zeroFormat(checkDate1); //D 형태일때 0 추가 = DD형태
            checkDate2 = zeroFormat(checkDate2);
            var formatDate1Date = formatDate1.split('-')[2]; //DD 추출, 순수 입력 데이터
            var formatDate2Date = formatDate2.split('-')[2];
            
            if(formatDate1Date != checkDate1 || formatDate2Date != checkDate2){ //DD 형태 비교
                alert("입력한 날짜는 존재하지 않습니다");
                return;
            }
            var convertDate1 = new Date(formatDate1).getTime();
            var convertDate2 = new Date(formatDate2).getTime();
            var absDate = Math.abs(convertDate1-convertDate2);
            var result = absDate/(24*60*60*1000);
            alert(formatDate1+" 과 "+formatDate2+" 의 사이 날짜는 "+result+"일 입니다.");          
        }else{
            alert("19~20년도 사이의 정확한 날짜를 입력해주세요");
        }
    }
}
function zeroFormat(input){
    if(input<10){
        input = "0"+input;
    }
    return input;
}
 

 2번 라인 - dateButton Click 이벤트 추가 및 함수 실행

 

 4~5번 라인 - 날짜 데이터인 date1, date2의 value 값을 가져옴

 

 6번 라인 - 정규식 패턴 생성(사용자가 YYYYMMDD 형태로 입력했는지 검사)

 (^(19:20)\d{2}) - 제일 앞이 19 아니면 20, 그 뒤 숫자 2개 : 1900~2099

 (0[1-9]|1[0-2]) - '0 다음 1부터 9 사이 값' 또는 '1 다음 0부터 2 사이 값 : 01~12

 (0[1-9]|[12][0-9]|3[01])$ - '0 다음 1부터 9 사이' 또는 '1 또는 2 다음 0부터 9 사이' 또는 '3 다음 0 또는 1' : 01~31

 $ - (YYYYMMDDD와 같이 형식을 오버했을 때 검출함)

 

 7번 라인 - 입력한 두 텍스트 박스의 value에 패턴을 적용시키고 대응 여부를 확인(true or false)

 

 8~9번 라인 - Date 객체를 생성하기 위한 데이터 형식인 (YYYY-MM-DD)를 맞춰주기 위한 포매팅

 

 10-11번 라인 - 객체의 메소드를 적용하여 날짜를 가져옴

 

 12-13번 라인 - 날짜가 1일, 2일 과 같이 한 자리 수일 때 형식이 DD가 아닌 D가 됨. 즉 비교가 안되므로 DD형태로 변경해주기 위한 메소드인 zeroFormat() 호출

 

 ※ DD 형태로 변형해야 하는 이유

   사용자의 입력 날짜가 윤월일 인지 판별하기 위함 (2월 30일처럼 없을 수도 있는 날짜 판별)

   Date 객체의 getDate() 메서드는 입력한 날짜의 '일자'를 받아옴. 그런데 만약 2018년 2월 30일의 일자를 받아오면

   3일이 나오게 됨. 즉, 없는 일자만큼 카운트를 계산하여 가까운 있는 날짜에 더해줌. 2월 31일의 가장 가까운 날짜는

   3월 1일이며, 2월 28일에서 초과된 2일만큼 증가시키면 3월 3일이 나옴. = 계산 오류 발생.

   이러한 오류를 막기 위해 DD 형태로 변형하여 사용자가 입력한 날짜의 일자와 메소드를 적용시킨 일자를 비교하여

   일치할 경우 존재하는 날짜로 판별하고, 불일치할 경우 존재하지 않은 날짜로 판별하기 위함 

 

 14-15번 라인 - split 메소드를 사용하여 '-' 기준으로 나눈 배열의 3번째인 일자가 저장된 값을 읽어옴.

 

 17번 라인 - 사용자가 입력한 일자와 메소드를 적용시킨 일자를 비교

 

 21번 라인 - 입력 날짜에 문제가 없으면 해당 시간의 날짜를 시간으로 변환(milis)

 

 23번 라인 - 밀리 초로 변환된 두 날짜의 시간을 빼고 그 데이터를 절댓값으로 변환

 

 24번 라인 - 밀리초를 날짜로 변환하기 위해 각각의 단위를 곱 연산 후(1000*60*60*24 = 1일) 앞의 계산 결과와 나눔

 

4.2) 실행결과

실행결과 1
실행결과 2
실행결과 3

반응형
반응형
반응형

목차

1. 개요

2. logging이란

3. log4j란

4. log4j 설치 및 설정

5. 예제


1. 개요

 서버는 클라이언트와 정보를 주고받는다. 다양한 이유로 그 과정에서 발생하는 여러 사건이나 정보들을 기록으로 남기는데 이 기록을 log라고 한다. 그리고 log를 남기는 행위를 '로깅'이라고 하는데, 모든 서버에 기본적으로 들어가는 개념이기 때문에 오늘은 이 logging에 대해 공부하는 시간을 가져보도록 하자. 구동 환경은 이클립스와 tomcat 7.0이다.


2. logging이란

 로깅이란 시스템 동작 시 시스템 상태/작동 정보를 시간의 경과에 따라 기록하는 것이다. 그 기록을 '로그'라고 한다.

 한마디로 '로깅 = 로그 기록'이다.

 로깅은 많은 부분에서 사용되는데, 사용자의 패턴이나 시스템 동작 분석에 사용되거나 해킹 사고가 발생할 경우 비정상 동작의 기록을 통해 추적하는 데 사용한다. 백엔드 개발자들의 경우 개발 과정에 있어 디버깅에 활용할 수도 있다. 이 외에도 다방면으로 쓰인다.

 그렇다면 Spring과 같은 Java환경에서 로깅을 하려면 어떻게 해야 할까? 시스템 동작에 대한 기록을 남기는 것이기 때문에 흔히 알고 있는 system.out.println()과 같은 메소드를 사용할 수 있겠다. 하지만 이는 메모리면에서 비효율적인 데다 클라이언트의 접속량이 많아질수록 안정적으로 실행되지 못하는 등 여러 문제를 안고 있다. (문제에 대해 궁금하다면 직접 구글링을 추천)

 이 문제에 대한 솔루션이자 Java 환경의 logging 시스템이 바로 log4j이다. log4j에 대해 본격적으로 알아보도록 하자.


3. log4j란 

 Java 환경의 로깅 시스템을 제공하는 라이브러리.

 Logger, Appender, Layout 등 다양한 컴포넌트가 사용되며, 로그 레벨을 분류하여 로그 정보를 출력시킬 수 있다.

 

 3.1) 컴포넌트

컴포넌트 설명
Logger 로그의 주체, 로그 파일을 작성하는 클래스
Appender 로그를 출력하는 위치
Layout Appender의 출력포맷(일자, 시간 등)을 설정하여 로그 내용으로 지정하는 속성. 

  Layout의 종류는 여러 가지가 있지만 일반적으로 디버깅에 가장 적합한 PattenLayout을 사용한다.

  PattenLayout이란 출력 포맷을 정해진 일련의 패턴을 사용하여 설정하는 것인데 패턴 정보는 아래와 같다.

패턴 설명
C 클래스명 출력
d 로그 시간 출력
F 파일명 출력. 수행한 메소드와 라인번호가 함께 출력.
L 라인 번호 출력
m 로그로 전달된 메시지 출력
M 로그를 수행한 메소드명 출력
n 개행
p 로그 이벤트명 (DEBUG 등)
r 로그 처리시간(milliseconds)
t 로그 이벤트가 발생된 쓰레드 출력

 위의 패턴을 잘 조합하여 로그에 대한 출력 포맷을 설정한다.

 

 3.2) 로그 레벨

 로그는 기본적으로 6개의 레벨을 갖는다. 아래로 갈수록 낮은 레벨이다.

로그 레벨 설명
fatal 시스템 문제와 같은 아주 심각한 에러가 발생한 상태를 나타냄.
error 요청을 처리하는중 문제가 발생한 상태를 나타냄.
warn 처리 가능한 문제이지만, 향후 시스템 에러의 원인이 될 수 있는 경고성 메시지를 나타냄.
info 로그인, 상태변경과 같은 정보성 메시지를 나타냄.
debug 개발시 디버그 용도로 사용한 메시지를 나타냄.
trace 디버그 레벨이 너무 광범위한것을 해결하기위해서 좀더 상세한 상태를 나타냄

 debug(), warn(), error()와 같이 메소드를 사용해서 로그 정보를 얻을 수 있으며, 로그 레벨 설정을 통해 로그를 통제할 수 있다. 이는 Logger의 Level 메소드를 통해 이루어지며, 지정한 로그 레벨보다 낮은 로깅 이벤트는 무시된다이는 뒷부분의 예제를 보면 이해할 수 있을 것이다.


4. log4j 설치 및 설정

 1) http://logging.apache.org/log4j/2.x/download.html 에서 최신버전.zip 파일 다운.

log4j 홈페이지

 2) 압축을 풀면 많은 파일이 있는데 그중 log4j-api-2.x.jar 와 log4j-core-2.x.jar 파일을 복사.

 

 3) 이클립스를 실행시켜 Dynamic Web Project를 생성하고 프로젝트 폴더에 lib 폴더를 생성.

 

 4) lib 폴더에 복사한 두 파일을 붙여넣기.

lib에 파일 붙여넣기

  5) 라이브러리를 추가는 '프로젝트 우클릭 → properties → Java Build Path → Libraries → Add JARs' 클릭

  

  6) lib에 있는 두 파일을 선택 후 Apply 클릭하면 라이브러리 설정이 완료.

프로젝트에 log4j 라이브러리 추가


5. 예제

  1) project  Java Resources → src 경로에 log4j2.xml 파일 생성 후 아래의 소스코드 추가.

     (log4j2.xml은 로깅에 대한 기본 환경 설정 파일)

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Console name="STDOUT" target="SYSTEM_OUT">
            <PatternLayout pattern="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="STDOUT" />
        </Root>
    </Loggers>
</Configuration>

  4번 라인 - 로그를 출력하는 위치(Appenders)를 Console로 설정. 로깅 이름은 STDOUT으로 설정.

  5번 라인 - patten을 시간, 로그 이벤트명, 쓰레드, 클래스명, 파일명, 라인 번호, 메시지, 개행으로 설정

  9~10번 라인 - STDOUT에 대한 Root level을 debug로 설정(서블릿에서 호출될 로그 중 debug보다 낮은 로그 레벨은 무시)

  

  2) proejct에 servlet 클래스를 생성하고 아래의 코드를 입력. 

  (servlet 생성 및 기본 개념을 모르겠다면 클릭)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package servlet;
 
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
 
/**
 * Servlet implementation class log
 */
@WebServlet("/log")
public class log extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private Logger logger = LogManager.getLogger(log.class);
    /**
     * @see HttpServlet#HttpServlet()
     */
    public log() {
        super();
        // TODO Auto-generated constructor stub
    }
 
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
        logger.error("error message");
        logger.warn("warn message");
        logger.info("info message");
        logger.debug("debug message");
        logger.trace("trace message");
    }
 
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
 
}
 

  18번 라인 - log 클래스에 대한 Logger객체 생성

  33~37번 라인 - 5가지의 로그 메소드 호출

 

  3) Servlet 파일 Run

실행 결과

  현재 servlet 파일에서 logger를 error, warn, info , debug, trace 총 5개 레벨에 대한 출력을 시도했으나 제일 낮은 레벨인 trace를 제외한 4개만 console로 출력된 상태이다. 이유는 앞서 언급했던 로그 레벨에 따른 통제와 관련이 있다.   log4j2.xml 파일에서 STDOUT 로깅 데이터에 대한 level이 debug로 설정되었기 때문이다. trace를 출력시키고 싶다면 xml 파일 9번째 줄의 debug 대신 trace를 넣어주면 된다.

 

  4) tomcat 서버 에러 발생 시

   tomcat 서버 에러가 발생할 경우 tomcat에 서버에 대해서도 log4j 라이브러리를 추가시켜줘야 한다. log4j를 사용하여 얻은 로그가 기본적으로 WAS(tomcat)에 남기 때문이다.

   추가 방법은 tomcat Server Overview → Open launch configuration → Classpath → UserEntries → Add JARs를 클릭하여 lib 폴더에 있는 두 라이브러리를 클릭하면 된다.

tomcat Overview
log4j 라이브러리 추가

 

시간이 늦은 관계로 log4j을 통한 롤링은 다음에 포스팅하도록 하겠다.

반응형
반응형

목차

1. 개요

2. Servlet 이란?

3. Servlet 동작 구조

4. 예제 및 실습

5. 실행화면


1. 개요

 저번 게시물에서 Spring MVC 모델에 대해 공부하던 중 Dispacher Servlet이란 개념이 애매모호한것 같아 이를 구조적으로 이해하기위해 선행 학습 개념인 Servlet에 대한 공부를 진행하였다.

 eclipse EE와 tomcat을 연동하여 WAS 구조를 만들고, Servlet을 이용하여 Client - Web Server - Web Container간 통신 구현 및 구조를 이해해보도록 하자.


2. Servlet 이란?

 Servlet이란 JAVA를 이용하여 동적 페이지를 생성하는 서버측 프로그램이다.

 CGI(Common Gateway Interface)라고도 하는데 CGI란 사용자의 입력을 받아 동적 페이지를 만드는 것이다.

 사용자의 입력에 따라 결과가 달라지는 것, 예를들어 쇼핑몰 로그인 후 나오는 자신의 닉네임같은 것이다.

 즉, JAVA로 구현된 CGI라고 생각하면 된다.

 

 개념은 알았으니 Servlet 동작 구조에 대해 살펴보도록 하자. 


3. Servlet 동작 구조

Servlet 동작 구조

 1) 클라이언트의 요청이 있으면 Web Server에게 요청이 전달된다.

 

 2) Web Server는 정적인 데이터(HTML, 이미지, css 등)만을 처리하고, 동적 데이터(Servlet, DB, 로직 등)는 Web Container에게 전달한다.

* Web Container : Servlet 클래스 또는 JSP 파일을 실행하기 위한 실행 환경을 제공하는 컨테이너

 

 3) Web Container는 web.xml파일을 참조하여 해당 Servlet에 대한 쓰레드를 생성한다. 그리고 httpServletRequest와  httpServletResponse 객체를 생성하여 이 쓰레드에게 전달한다.

 쓰레드를 생성하는 이유는 클라이언트에게 효율적으로 웹 페이지를 제공하고, 서버의 부하를 막기 위함이다.

 이로써 통신 객체를 가진 쓰레드가 만들어진다.

* 쓰레드 : 여러가지 작업을 동시에 수행할 수 있도록 복제(나눈)한 것

 

 4) Container가 Servlet을 호출한다. 

 

 5) 호출된 Servlet의 작업을 담당하는 쓰레드(3에서 생성된 쓰레드)는 로직이 정의된 doPost()doGet() 메소드를 호출한다. 이 두 메소드는 Servlet class에 정의되어있다.

 

 6) 호출한 메소드의 로직을 컴파일한 후 생성된 동적 페이지를 (3)번 에서 생성했던 httpServletResponse객체에 담아 Web Container에게 넘겨준다.

 

 7) Web Container는 전달받은 response 객체를 HTTPResponse 형태로 바꿔 웹 서버로 전송함과 동시에 생성했던 쓰레드와 httpServletRequest, httpServletResponse 객체를 종료 및 소멸시킨다.

 (HTTPResponse는 Web Server에서 Client 로의 응답 객체이다.)

 

 8) Web Server는 전송받은 HTTPResponse 객체를 HTTP 통신을 통해 클라이언트에게 전송하여 화면을 출력시킨다.


4. 예제 및 실습 (eclipes EE, tomcat 7.0)

 4.1) Dynamic Web Project 생성(web.xml 체크)

New - Dynamic Web Project
web.xml Check

 4.2) Servlet 생성

  - Package, class 이름을 입력.

  - class이름의 첫글자는 대문자로 입력.

create Servlet

 

 4.3) Servlet 코드 입력(test1.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package one;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
/**
 * Servlet implementation class Two
 */
@WebServlet("/Two")
public class Two extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public Two() {
        super();
        // TODO Auto-generated constructor stub
    }
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
        PrintWriter out = response.getWriter();
        out.println("<html>"+"<body>"+"<h2>Hello World</h2>"+"</body>"+"</html>");
        
    }
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
}
 
 
 

 

4.4) web.xml 코드 입력

 <servlet-name> 태그에는 프로젝트의 이름을, servlet-class에는 클래스의 주소(Package.class형식)를 입력.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
  <display-name>test1</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
      <servlet-name>test1</servlet-name>
      <servlet-class>one.Two</servlet-class>
  </servlet>
</web-app>
 

5. 실행화면

Servlet에 들어있던 HTML 코드

 URL의 끝이 test1이 아니라 Servlet 명인 Two를 추가적으로 입력해야한다.

 URL 설정은 해당 Servlet 파일 또는 web.xml 파일 내에서 가능하다.

 

 페이지에 Servlet에서 정의한 로직이 출력되면 성공한 것이며, 이렇게 eclipse, tomcat을 사용한 Servlet 실습을 마치도록 하겠다.

반응형

+ Recent posts