gzip ?
gzip은 리눅스나 유닉스 체제에서 사용하는 압축 방식인데 .. WEB에서 (사용자)브라우저에게 압축해 내용을 보내주는 것이다. HTTP HEADER에 '이건 gzip이야 .. 그러니 알아서 처리해줘!'하고 보내주면 브라우저가 분석해 gzip압축내용을 해제 후 사용자에게 보여준다.
사실 대부분의 브라우저들이 gzip을지원하고 있고 .. 지원한지도 오래되었지만 이제서야 직접 테스트해 봤다. 쌩자로 html을 만들어(서버사이드에서 연산된 후 사용자에게 보여질 때) 사용자에게 보여주는 경우가 일반적이지만 gzip의 압축을 통해 사용자에게 보내주면, 트래픽이 감소할 것이고 사용자는 보다 빠른 응답을 받을 수 있는 것이다.
필자가 보기엔 이거 .. *.js,*.css, *.html, *.htm같은 정적인 페이지만 영향이 있는줄 알았는데(정적페이지) *.jsp도 잘 된다. 본 테스트 데이터는 단순히 text기반의 정보이므로 당연히 무지막지한 압축율을 자랑한다. 그리고 이미지나 압축된 파일 등은 gzip으로 압축되지 않는다. 오직 text기반만 적용 된다. (블로그를 검색해 보니 60%정도 압축된다고 한다.)
gzip.html (정적 페이지 테스트)
코드보기
결과>
gzip필터 미사용(전송량 183KB)
gzip사용(전송량 937B)
gzip.jsp (동적 페이지 테스트)
코드보기
gzip 미사용(전송량 9.44MB)
gzip사용(전송량 33KB)
결과를 표로 보자면 ...
gzip 미사용
gzip 사용
.html
183KB
937B
.jsp
9.44MB
33KB
이제 효능을 확인했으니 적용 방법을 살펴보자. 라이브러리 추가 후 filter를 추가해 주면 된다. 물론 기존의 filter가 있다면 순서 등등의 문제가 이르킬수 있으니 순서정의를 잘 해야 한다.
객체의 정보, 필드에 접근 및 수정, 메소드 호출까지 해봤으니 이번엔 직접 동적으로 객체를 생성해 보자. 예제를 간략화 하기 위해 이전에 사용한 빈을 조금 수정했다. 수정된 빈은 다음과 같다.
SampleBean.java
리플랙션을 공부하기 위해 구글링 등을 하다 처음으로 이 페이지를 봐서 앞 포스트 부터 순차적으로 바왔다면 상관 없지만 .. 어쩌면 다른 블로그에 써있는 내용에 아래와 같은 코드를 볼 수 있었을 것이다.
여기서 사용한 Object[]는 말 그대로 Object형 배열이다. 5.0에선 ... 을 이용해 파라미터를 마구마구 넣어도 되었지만 아마 이전엔 저런식으로 Object배열을 넘겻을 것이다. 말 그대로 Object형으로 값을 넣고 배열을 만드는데 .. 여기서 궁금한 점은 '왜 프리미 티브 타입이 들어가 있는가?' 일것이다.
디버그로 본 상태 ..
위 그림처럼 프리미티브는 자동으로 랩퍼 클래스로 전환되어 저장한다. 따라서 Object배열이 가능한 것이다. 그로인해 사용자가 작성한 클래스건 뭐건 모두 파라미터에 적용이 가능 하다. Object는 최상위 클래스니까 ..
그럼 이제 실제 객체를 동적으로 생성하는 코드를 살펴보자.
Exam_ReflectionInstance.java
필드와 메소드에서 사용했던대로 사용하면 된다. Constructor를 통해 생성자를 찾는다. 물론 인자값이 어떤게 들어가는지를 통해 찾는다. 생성자 모두를 배열로 가져올수도 있다. 경우에 따라선 그것으로 생성자를 찾는경우도 있을지 모르겠다.
.newInstance()로 인자를 넣어 생성한다. 어렵지 않겠지만 궁금한것은 .class일 것이다.
Class cls = String.class 라고 하면 이런방식이라고 알아두면 된다.
ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class cls = classLoader.loadClass("java.lang.String");
메소드 호출에 이어 이번엔 필드값을 수정해 보자. 이번에도 바로 코드부터 살펴보자.
Exam_ReflectionField.java
SampleBean.java
sampleBean의 필드를 수정한다. 내부엔 Object도 있다.(동일한 SampleBean) 보통 빈이라 함은 필드가 private이지만 public로 한 이유는 접근때문에 그렇다.
리플랙션이라 해서 private나 final등의 접근이 예외적일 순 없다. 따라서 접근이 가능한 public로 수정한 것이다. 만약 private로 되어있다면 IllegalException이 날것이다. 이유는 '찾을 수 없어서'이다.
프리미티브 타입은 그대로 사용할 수 있다. getInt, setInt, getFloat, setFloat .. 그렇다면 String이 없는 이유는 ? 사실 String는 당연히 클래스 이므로 그렇다. 이런경우 set(Object)를 이용하면 된다.
직관적인 코드라 주석으로 충분한 설명이 되었을 듯 하다. 한가지 조심해야 할 사항은 위 코드는 jdk 5.0에서 사용한 코드다. 따라서 그 이하는 몇몇부분 문제가 발생할 수도 있다. Method invokeMethod = cls.getMethod("append", new String().getClass());
부분이다. 이건 메소드를 찾는 부분인데 뒤에 인자가 수십개가 되면 어떻게 될까 ?
jdk 5.0에선 수십개라면 그냥 주루룩 넣어주면된다. 프리미티브 타입이라면? 자동으로 박싱되어 랩퍼 클래스로 될 것이다. 그리고 안에선 Object[] 로 들어가게 될 것이다.(이점에 대해 나중에 자세한 포스트로 다룰 예정이다.)
(원리는 ... 인데 .. 이것은 직접 소스를 까보는게 좋을 듯 하다.)
먼저 "append"라는 이름의 메소드를 찾고, 그 메소드가 가지는 인자는 어떤것들이다. 라는 조건으로 검색을 했다. 그러면 Method를 반환 하는데 .. 그것으로 invoke를 수행한다. invoke수행시 수행할 객체를 앞에 넣어주고, 들어갈 파라미터를 주루룩 넣어준다. 위와 같이 여기도 ... 방식으로 무한으로 넣어주면 된다.
Java Reflection ?
리플랙션이란 부분에서 알 수 있듯 지금 실행중인 프로그램에서 사용하는 객체를 비춰준다. 즉, 내가 사용하고 있는 객체의 정보 가져오기, 객체의 메소드 호출 등의 작업을 할 수 있도록 해준다.
친절하게도 기본 패키지에 리플랙션이 포함되어 있다. 그렇다고 너무 난무해서 사용해선 안된다. 리플랙션은 성능에 영향을 주기 때문이다. 직접 사용한적은 없지만 아마 여러 분야에서 도움을 받은 것이 리플랙션이 아닐까 싶다.
얼마전까지 포스트 한 iBATIS를 보더라도 그렇다. XML에 등록한 패키지의 클래스는 동적으로 수행되어 java.util.List, java.util.Map등에 포함해 반환해 준다. 바로 이부분이 리플랙션을 이용해 동적으로 객체를 만들고 조작해 돌려주는 것 이다.
Class Information !!
맛보기 리플랙션의 사용법을 알아보자. 먼저 리플랙션에 대해선 부분별로 나눠 포스트 할 생각인데, 먼저 객체의 정보를 가져오는 방법을 설명하도록 하겠다.
아래의 코드는 꽤 길어보여 어려워 보이지만 실제로 어렵지 않다.
정말 많이 사용하는 java.lang.String를 생성해 그 정보를 확인 했다. 코드에선 getClass(). 으로 접근했지만 아마 Class객체를 생성해 사용하는것이 바람직 할 것이다.(계속 호출하니까 ...)
getModifierInfo(Object object) 메소드는 사용할 것을 대비해 작성했지만 .. Modifier.toString(int mod)에 넣어주면 알아서 확인해 주니 .. 사용하진 않았다.
메소드 명이 직관적이라 큰 설명은 필요 없겠지만 메소드와 필드 목록을 가져오는 부분에서 getDeclared ... , get ...의 차이점이 궁금할 것이다. 실행해보면 알 수 있지만 getDeclared는 객체의 모든 정보를 가져온다. 반면 get ... 는 현재 접근이 가능한 부분만 나오게 된다. 따라서 private,protected는 나올 수가 없다.(상속관계라면 protected는 가능 할 것이다.)
웹 개발을 하다보면 반드시 마주치는게 아닐까 싶다. Redirect ? Dispatcher ? 둘다 하는일은 비슷하지만 동작 원리는 근본적으론 완전히 틀리다. 아마 스트럿츠를 개발해본 사람이 있다면 Dispatcher에 익숙해 있을 것 같다.
쉽게 설명 하자면 Dispatcher는 서버에서 페이지를 돌리고(위임), Redirect는 클라이언트에게 '지정된 페이지로 이동'하라고 지시 한다. 두 방식을 설명할 초간단 서블릿을 작성해 보았다.
DispatcherTest.java
RedirectTest.java
Dispatcher
Redirect
주소창을 보면 확실히 알 수 있다. DispatcherTest.java의 경우 URL은 그대로지만 페이지는 바뀌었다. RedirectTest.java는 URL자체가 바뀌었다. 스트럿츠에서 컨트롤러 -> 뷰 로 이동하는 것이 바로 이렇게 구현 된 것이다. 그렇다고 아무렇게나 막 쓸수 있을까 ..?
그것은 아니다. 단 한번이라도 출력이 되었을 경우(OutputStream을 이용해 출력된 것) IllegalStateException이 발생하게 된다. 이것은 당연한 것이다. 페이지를 전환하려고 하는데 한번이라도 flush를 하게 되면 뭔가 모양이 이상하니 말이다. 그렇다고 반드시 일어나는 것은 아니다.
운이 좋으면 flush가 되지 않으면 예외가 발생하지 않는다. 그렇다고 믿으면 안된다. 이것은 어디까지나 버퍼가 차지 않아 출력하지만 않은것이니까 ....
싱글턴 패턴을 작성하고 궁금한게 생겼다. synchronized () {} 에서 () 안에 들어가는게 .class였는데 .. 다른데 보면 this라는걸 사용하기도 한다. 그래서 차이점이 뭔지 궁금했는데 .. 여기저기 물어봐서 결국 알게 되었다. 그냥 뭐 단순하게 '지금 내 인스턴스'를 락 걸던가 .. 아니면 '.class의 모든 인스턴스'에 락을 걸것인지의 차이점이다. 이에 대한 샘플 소스는 다음과 같다.
문자열에 대한 값을 체크 할 때 간단한 길이계산 정도야 if (String.length() > 0) 등을 이용해 작성할 수 있다. 하지만 복잡한 문자열(예를들어 주민등록 번호, 혹은 이메일 형식)등을 점검하고자 할땐 조건문이 꽤 복잡해 진다.
이런 경우를 대비해 java에선 '정규표현식(Regular expression)을 1.4부터 제공한다.
정규표현식?
문자열에 매치여부를 확인할 수 있고, 치환(replace)할 수 있는 문자열 형식
(필자 임의로 작성했습니다. 정확한 해석은 검색을 통해 ..)
그대로 해석하자면 '정규표현식'이란 문자열에 대한 형식이 있다. 그리고 우리가 검증하고자 하는 문자열이 '작성된 정규 표현식에 매치여부를 확인'할 수 있고, 특정 부분을 '치환할 수 있다'는 뜻이다.
더 쉽게 보자면 이것은 '틀을 짜 놓고, 이 안에 들어가나?'를 보는 것 이다.
그렇게 되면 복잡한 조건문을 통해 검증해야 하는 문자열도 정해진 식을 통해 단번에 매치여부를 확인할 수 있는 것이다.
이제 정규표현식에 사용되는 식을 살펴보자.
정규식
설명
예제
.
임의의 한 문자(필수)를 의미 합니다.
ab.(abc, abd,abe) ..
?
바로 앞에 문자가 없거나 하나가 있음을 의미 합니다.
a?c (ac, abc, bc) ..
*
바로 앞에 문자가 없거나 하나이상 반복을 의미 합니다.
ab* (a, ab, aaa) ..
+
바로 앞에 문자가 하나이상 반복을 의미 합니다.
ab+ (ab, abb, abbb) ..
^
문자열의 시작을 의미 합니다.
^ab (abc, abcd, abcde) ..
[^]
^이후의 괄호안 형식을 제외함을 의미 합니다.
[^ab]cd (ecd, fcd, gcd) ..
$
문자열의 끝을 의미 합니다.
abc$ (pupu abc, story abc) ..
[]
[]안의 형식 일치를 의미 합니다.
[abc] (a, b, c, ab, bc, abc) ...
{}
{}앞 문자열(혹은 문자) 반복 갯수를 의미 합니다.
ab{2} (abab)
ab{2,} (2개이상)
ab{1,2} (1부터 2까지)
()
()안의 내용 을 하나의 묶음으로 사용 함을 의미 합니다.
(ab){2} (abab)
(ab)+ (ab, abab, ababab ..)
|
or연산을 의미 합니다.
(a|b|c) (a, b, c, ab,abc ..)
[0-9]
(부터 - 까지)의 숫자를 의미 합니다.
[0-9] (0, 1, 2, 3, 4 ..)
[a-z]
(부터 - 까지)의 소문자를 의미 합니다.
[a-z] (a, b, c, d ..)
[a-zA-Z]
(부터 - 까지)의 대,소문자를 의미 합니다.
[a-zA-Z] (a, b, A, B ..)
\p(Alpha)
대,소문자 아파벳을 의미 합니다.
(a, b, c, D, E, F ..)
\p(Digit)
숫자를 의미 합니다.
(1, 2, 3 ..)
\p{Alnum}
대,소문자 아파벳, 숫자를 의미 합니다.
(a, b, c, d, e, 1, 2, 3 ..)
\d
숫자를 의미 합니다.
(1, 2, 3, 4 ..)
\D
숫자가 아닌 것을 의미 합니다.
(a, b, d, E ..)
그냥 보기엔 복잡하기만 하다. 필자도 처음봤을땐 '외계어'로 보여서 도무지 손대고 싶지 않았다. 그런데 이게 .. 계속 왜 안되는지 이해를 못하면서 헤딩하다 보니 .. '특별한 매력'을 느끼게 되었다. --b
우리가 처음 자바(그 외 다른 언어 등등)을 배울때 생각해 보자. public ? static ? void ? main ? request ? response ? session ? 어느것 하나 생소하지 않은것이 없었다. 영어는 어떠한가 ? 영어 알파벳을 처음 봤을 땐 '문자' 가 아닌 '기호'로 받아드리는게 맞는것 같다. 왜냐면 처음 보는거니까 ..
이제 사용법을 살펴보자. 몇가지 예제를 살펴보면 더욱 쉽게 이해할 수 있다.
RegexSample .java
별로 길지 않은 코드다. regex에 형식을 지정하고 useStr의 내용이 맞는지 여부를 확인했다. 정규표현식은 자바에서 제공하는(1.4 이후) java.util.regex.* 패키지가 있다. 그리고 String 에서도 손쉽게 사용할 수 있다. 만약 사용시 좀 더 강력한 정규표현식(예를들면 매치되는 문자열의 그룹이라던가 하는)을 이용하고자 할땐 패키지를 사용하는 것이 좋다.
이번엔 좀 더 난이도 있는 표현식을 살펴보자.
RegexSample .java
이것은 이메일을 검증하는 코드다. 표현식이 좀 복잡해 보이지만 하나하나 살펴보자.
// 숫자, 혹은 영문자가 오게된다. 마지막에 +가 있으므로 반드시 하나 이상의 문자가 와야 한다.
\p{Alnum}+
// @문자가 온 뒤 위와 같은 형식이다.
@\p{Alnum}+
// .문자가 온 뒤 위와 같은 형식이다. '.'앞에 \가 온 이유는 정규표현식에서 '.'문자를 사용한다.
// 따라서 '이것은 정규표현식이 아닌 그냥 내가 원하는 특수 문자'임을 뜻한다.
\.\p{Alnum}+
결과는 처음 배열 문자만 통과되고 나머지는 false를 반환한다. 실제로 아주 쉽다. 단지 처음보는 문자열을 코드로 사용함에 있어 복잡해 보이는것은 사실이다. 이것은 점점 많은 예제를 통해 익숙해질 수 있다.
이제 좀 더 고급 표현식을 사용하기 위해 패키지에 포함된 라이브러리를 이용해 보자.
RegexSample .java
주석 내용만 살펴봐도 별 부담없는 소스다. 이제 패키지를 이용한 강력한 기능을 살펴보자. 만약 사용자가 txt형식의 큰 문서 파일을 받았다고 가정 하자. 그 문서 파일 중 '특정 규약에 맞는 문자열이 몇번이나 반복되어 나오는가?'를 분석하고자 한다. 우리가 지금까지 알아본 방법은 '임의의 하나 문자열이 지정된 형식에 맞는가?'에 대한 방법을 알아왔다. 즉. '단건'에 대한 정보를 넘겨 그 '단건'정보가 일치하는지를 알아본 것이다.
큰 문서에 대해서 '얼마나 많이 지정된 패턴이 반복되는가?'를 알아보려고 한다면 어떻게 해야할까 ? 우리가 DAO등을 사용할 때 row를 가져오는 방법으로 루프돌린 방법은 다음과 같다.
간단히 위와같이 사용한다. 우리가 위에 살펴본 .find()메소드도 같은 역할이다. 정규표현식에 대해 문서를 검색하고, 매치되는 부분을 찾는다. 후에 다음 부분을 차자게되고 끝까지 찾아 더이상 없다면 false를 반환하는 것 이다. 이제 좀 복잡한 코드를 만들어 보자.
RegexSample .java
복잡해 보이지만 간단하다. for문을 통해 반복하며 문자열을 만드는데 '@gmail.com'을 추가할지 안할지 정한다. 추가되면 정규표현식에 맞게되고, 그렇지 않으면 맞지 않게된다. while(boolean) 돌며 매치되는 문자열을 검색하고, 더이상 내용이 없으면 false를 반환하므로 빠져나온다.
지금까지 살펴본 예제는 단순하다. 정규표현식이 어려운것은 바로 '처음 손대기가 까다로워 보인다'라는 것이 가장 큰게 아닌가 싶다. 표현식을 만드는게 복잡해 보이지만 익숙해지면 매력이 있고, 또 벨리데이션 체크에 있어 여러개의 조건이 들어갈 필요도 없다. 라이브러리 추가도 필요없다. String에서 바로 사용하거나 패키지를 사용하면 된다.
필자도 처음에 어려워서 그만둘까 했지만 하다보니 정말 재미있어서 계속 빠지게 되었다. 이글을 보고 있고, 정규표현식에 관심있는 많은 개발자들이 허접한 이 글을 통해서 코드작성에 좀더 빠르게, 좀더 아름답게 하는데 보템이 되었으면 한다.