Pages

2014년 12월 19일 금요일

[SPRING] Interceptor & Filter

스프링에서 전후처리기 를 담당하는 Interceptor Filter가 있습니다.

기능만 보면 Interceptor와 Filter는 비슷해 보입니다. Filter로 해야 되는 일들은 

Interceptor로 구현 할 수 있습니다.

Spring Interceptor 설정파일을 보면 

<spring-servlet.xml>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     <mvc:interceptors>
 
        <mvc:interceptor>
 
           <mvc:mapping path="/api1/*" />  
 
           <mvc:mapping path="/api2/*" />  
 
           <mvc:mapping path="/api3/*" />  
 
           <bean class="com.test.interceptor.RequestInterceptor" />
 
         </mvc:interceptor>
 
    </mvc:interceptors>

위 부분은 api1,2,3 호출되기전에 RequestInterceptor Class를 먼저 호출

하는 것입니다. 

Filter 부분 설정을 보면

<web.xml>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    <filter>
 
        <filter-name>필터이름</filter-name>
 
        <filter-class>com.test.filter.RequestFilter</filter-class>
 
    </filter>
 
    <filter-mapping>
 
        <filter-name>필터이름</filter-name>
 
        <url-pattern>/*</url-pattern>
 
    </filter-mapping>
위 부분은 필터먼저 호출하고 페이지를 호출하겠다는 것입니다.

이렇게 보면 위 두 Interceptor와 Filter 부분이 비슷하다고 볼 수 있습니다.

아래 그림을 보면 Spring 에서 Request가 어떻게 흐르는지 알 수 있습니다.


일단 호출되는 시점이 다르다. 

Interceptor나 Filter나 Controller에 진입하기 전에 작업을 처리를 위해 사용하므로 

차이 없어 보일 수 있으나 위의 그림을 보면 호출되는 시점이 다르다는걸 알 수 있

습니다.

Filter의 경우는 Interceptor와 다르게 설정 정보를 web.xml에 작성합니다.

멤버함수의 용도도 다릅니다.

Interceptor 
preHandle() : Controller  진입 전
postHanle() : Controller 진입 후 view로 보내기 전
afterCompletion() : view까지 끝나고 나서


Filter
 - init() : filter 인스턴스 초기화
 - doFilter() : 전/후 처리
 - destroy() : filter 인스턴스 종료


doFilter 함수는 아래처럼 작성 됩니다.



1
2
3
4
5
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 전 처리   
        chain.doFilter(request, response);
        // 후 처리
}
Filter는 doFilter 함수에서 전후 처리를 모두 담당하는데 doFilter가 요청 전과 후, 

두번 호출되는 방식입니다.

Filter는 J2EE 표준 스펙에 나와있는 Servlet 기술 중 일부라서 web.xml에 기술

는 것이며, Interceptor는 비슷한 기술이지만, Spring에 녹인것으로 보입니다.

Spring 기반으로 한다면 당연히 Filter보단 Interceptor를 사용하고 Spring 없이 

단순 Servlet구현이라면 Filter를 사용해서 사용하면 될 것 같습니다.





[자료] 코드 품질 관리 도구 - Sonar



Sonar 설치 방법 및 기본 사용 방법




2014년 12월 17일 수요일

옥희와 함께하는 <자바 퍼즐러> 출간 이벤트 당첨자 발표

얼마전에 옥희(OKJSP)에서 이벤트가 있어서 참여 앴었는데 당첨되었네요.

1등은 아쉽지만 그래도 감사^^

옥희와 함께하는 <자바 퍼즐러> 출간 이벤트 당첨자 발표

2014년 12월 11일 목요일

[JAVA] StringBuffer와 StringBuilder 성능 비교

StringBuilder와 StringBuffer를 비교하는 프로그램을 만들 수 있습니다. 

package com.littletutorials.tips;
public class ConcatPerf {
    private static final int ITERATIONS = 100000;
    private static final int BUFFSIZE = 16;

    private void concatStrAdd() {
        System.out.print("concatStrAdd   -> ");
        long startTime = System.currentTimeMillis();
        String concat = "";
        for (int i = 0; i < ITERATIONS; i++) {
            concat += i % 10;
        }
        long endTime = System.currentTimeMillis();
        System.out.print("length: " + concat.length());
        System.out.println(" time: " + (endTime - startTime));
    }

    private void concatStrBuff() {
        System.out.print("concatStrBuff  -> ");
        long startTime = System.currentTimeMillis();
        StringBuffer concat = new StringBuffer(BUFFSIZE);
        for (int i = 0; i < ITERATIONS; i++) {
            concat.append(i % 10);
        }
        long endTime = System.currentTimeMillis();
        System.out.print("length: " + concat.length());
        System.out.println(" time: " + (endTime - startTime));
    }

    private void concatStrBuild() {
        System.out.print("concatStrBuild -> ");
        long startTime = System.currentTimeMillis();
        StringBuilder concat = new StringBuilder(BUFFSIZE);
        for (int i = 0; i < ITERATIONS; i++) {
            concat.append(i % 10);
        }
        long endTime = System.currentTimeMillis();
        System.out.print("length: " + concat.length());
        System.out.println(" time: " + (endTime - startTime));
    }

    public static void main(String[] args) {
        ConcatPerf st = new ConcatPerf();
        System.out.println("Iterations: " + ITERATIONS);
        System.out.println("Buffer    : " + BUFFSIZE);

        st.concatStrBuff();
        st.concatStrBuild();
        st.concatStrAdd();
    }
}

결과 
Iterations: 100000
Capacity : 16
concatStrBuff -> length: 100000 time: 16
concatStrBuild -> length: 100000 time: 15
concatStrAdd -> length: 100000 time: 10437
위 결과를 보듯이 일반 문자열 연결은 상당한 시간이 소요된다는 것을 알 수 
있습니다. 컴파일러는 concatStrAdd() 메소드를 를 수행할때 StringBuilder를 
사용하고 있습니다. 
하지만 이는 For문이 돌때 마다 Instance를 하나씩 만듭니다. StringBuffer와 
StringBuilder를 위해 초기 값을 크게하면 큰 차이가 없습니다. 분명히, 10만번의 
반복에서 StringBuffer와 StringBuilder의 수행결과는 별 차이가 없습니다.
그렇다면 Iterations 값을 100000000 (1억) 으로 하고 수행을 해보면 결과가 아래와 
같이 나옵니다. 물론 concatStrAdd()는 수행에서 제외 합니다. 끝나는 시간이 상당히 
오래 걸릴 것이기 때문입니다. 
결과
Iterations: 100000000
Capacity : 16
concatStrBuff -> length: 100000000 time: 15142
concatStrBuild -> length: 100000000 time: 10891
StringBuilder가 동기화를 피하기 때문에 확실히 더 빠르다는 것을 알 수 있습니다.
그럼 BufferSize를 100000000 으로 하고 다시 테스트를 하면 아래와 같은 결과를 
얻을 수 있습니다.
결과
Iterations: 100000000
Capacity : 100000000
concatStrBuff -> length: 100000000 time: 14220
concatStrBuild -> length: 100000000 time: 10611
StringBuilder가 평균적으로 보면 30% 정도의 속도가 빠르다는 것을 알 수 있습니다.
하지만 기억해야 할 것은 Thread safe 하지 않다는 것입니다.
Thread safe 관련 => Thread safe와 none Thread safe의 비교

[JAVA] Thread safe와 none Thread safe의 비교



Thread 관련된 문제는 항상 강조하는 게 "버그 잡기 힘들다." 입니다.

1. 원자성


원자성이란 CPU가 처리하는 하나의 단일 연산을 말합니다.

int a=1; 과 같은 코드는 원자성입니다.
int a=1+1; 도 원자성입니다.
그러나
a++;은 원자성이 아닙니다.
a++은 a= a+1 과 같은 의미인데, (a값을 읽는다) + (읽은 값에 1을 더해서 a에 대입한다.) 라는 2개의 원자성 연산의 조합입니다.
원자성을 가진 연산이 2개 이상 있을 때 그것은 원자성이 보장되지 않습니다. 아래의 코드를 실행시켜보세요.
public class AtomicTest {
    private int a = 0;
    public int incrementAndGet() {
        return a++;
    }
    public static void main(String[] args) {
        final AtomicTest test = new AtomicTest();
        for (int i = 0; i < 100; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        System.out.println(test.incrementAndGet());
                    }
                }
            }.start();
        }
    }
}

100개의 Thread가 1000번씩 증가시켰으면 100,000이 나와야 할 것 같지만 보통 그 보다 좀 덜 나옵니다.

초기값이 0인 a에 대해 a++연산을 Thread A, B가 처리할 경우,
1. A Thread가 a의 값을 읽음. (a=0)
2. B Thread가 a의 값을 읽음. (a=0)
3. A Thread가 a의 값 계산 후 대입. (a=0+1 = 1);
4. B Thread가 a의 값 계산 후 대입. (a=0+1 = 1);
와 같이 흘러가면 결국 a=1이 됩니다. 두번 돌았으니 2가 되어야 하지만 말입니다.

원자성 연산의 조합은 절대 원자성 연산이 아닙니다.




2. Thread safe


Thread safe란 여러 개의 쓰레드에서 동시에 호출해도 문제가 되지 않는 클래스입니다. Thread safe에 대해서 정확히 알려면, 변수의 scope과 자바의 메모리 관리 등에 대해서 좀 알아야 합니다. 자바 기초 책에 나오는 클래스 변수, 지역 변수 등을 명확히 이해해야 하며, 메인 메모리와 워킹 메모리에 대해서 대략적인 가닥은 잡으셔야 합니다.


Thread safe 하게 프로그램을 만들려면 일반적으로 synchronized 구문을 씁니다.

StringBuffer 와 StringBuilder, Vector 와 ArrayList, Hashtable과 HashMap 같은 것들이 synchronized가 걸려있고 안걸려 있고 정도의 차이만을 보이는 대표적인 클래스들입니다.(물론, 그게 전부는 아니지만 핵심입니다.)

StringBuffer, Vector, Hashtable 등이 자바 초기에 등장한 애들입니다. 얘들은 synchronized가 걸려있는 대표적인 애들입니다. synchronized가 걸리면 안 걸린 것에 비해서 당연히 느립니다. 즉, 정말 synchronized 가 걸려있어야 하는지를 보고 그렇다면 그런 애들을 쓰면 됩니다.


먼저 StringBuffer는 일반적으로 쓸 일이 없습니다. 여러 개의 Thread에서 하나의 String을 조합하는 것은 일반적으로 굉장히 이상한 일입니다. 다만 StringBuffer 자체를 인자로 받는 라이브러리를 쓸 때는 어쩔 수 없지만 일반적으로는 쓸 일이 없습니다. 보통은 StringBuilder를 사용하면 됩니다.


Vector와 Hashtable 등을 쓸 수는 있지만, iterator 등을 돌 때는 주의해야 합니다. iterator를 돌면서 추가 삭제를 하는 것은 위험합니다. 다음 프로그램은 에러를 발생시킵니다.(쓰레드 관련 프로그램이 늘 그렇듯... 안 발생할 수도 있습니다.)
public class VectorIteratorTest {
    public static void main(String[] args) {
        final Vector < Integer > a = new Vector < Integer > ();
        for (int i = 0; i < 100; i++) {
            a.add(i);
        }

        new Thread() {
            public void run() {
                for (int i = 100; i < 1000; i++) {
                    a.add(i);
                }
            }
        }.start();

        for (Integer integer: a) {
            System.out.println(integer);
        }
    }
}

Exception in thread "main" java.util.ConcurrentModificationException

 at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
 at java.util.AbstractList$Itr.next(AbstractList.java:343)
 at thread.VectorIteratorTest.main(VectorIteratorTest.java:24)

요런일이 생깁니다. Thread safe 하기 때문에 문제가 안 될 것 같지만, iterator 부분이 문제 입니다. 일단 요기 를 읽어보시고...

전부 단일 연산인 것 같지만 iterator를 도는 게 단일 연산인 것은 아니기 때문에 생기는 문제입니다. 저걸 단일 연산으로 맞추고 싶다면 iterator 바깥부분을 synchronized (a) { } 로 감싸주어야 합니다.


3. 잘못 쓰기 쉬운 라이브러리


date format을 처리하기 위해 SimpleDateFormat 클래스를 씁니다. 이놈은 thread safe 하지 않습니다. new 로 매번 생성하거나 생성된 객체를 clone 떠서 사용하기를 권장합니다.


Jaxb Marshaller는 xsd에서 나온 bean 객체를 이용하여 xml을 만들어낼 때 쓰는 놈인데, 이 놈도 thread safe 하지 않습니다. 게다가 이놈은 생성비용도 굉장히 비쌉니다. 생성 과정을 전부 살펴보진 않아서 정확히는 모르겠지만, 동시에 수백개를 생성하게 되면 생성 속도가 몇 초 단위로 나오더군요. 이건 pool을 사용해서 처리했습니다. jakarta common에 있는 common-pool 을 사용했습니다.


Servlet이나 JSP는 Flyweight 로 구현되었습니다. 즉 instance가 1개이기 때문에 동시에 여러 thread가 접근하면 문제가 생길 수 있습니다. 흔히 디비에서 조회한 결과를 멤버 변수에 저장하는 실수를 범하곤 하는데, 인스턴스가 1개이기 때문에 엉뚱한 사람이 그 결과를 보게 될 수 있습니다. 즉 Servlet이나 JSP는 Thread safe하게 제작되어야 합니다.!
다음 예제는 잘못된 서블릿입니다.

public class WrongServlet extends HttpServlet {
    private static final long serialVersionUID = 1 L;
    private String userName;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        fillUserName(request);
        response.getWriter().write(userName);
    }
    private void fillUserName(HttpServletRequest request) {
        userName = (String) request.getSession().getAttribute("USER_NAME");
    }
}

여러 개의 request가 들어오면 A thread가 set한 userName을 B thread가 읽어 들일 수 있습니다. 


4. Thread 관련된 것은 꼭 테스트를!!

Thread에 관련된 문제는 말씀드렸다시피 잘못 짜도 일반적으로 발생하지 않습니다. 어느 순간 발생하기도 하기 때문에 잡아내기가 정말 힘듭니다. 결과의 정합성에 대해서 "Thread 차원에서" 테스트를 반드시 거치기 바랍니다.


2014년 12월 9일 화요일

[ 자료 ] GDG Korea DevFair 2014

GDG Korea DevFair 2014 코드랩 자료


코드랩

- 개인용 영상통화 만들기 (GAE+WebRTC) (이원제, 레진엔터테인먼트)
https://github.com/GDG-Korea/WebRTCOnGAE

- Go Bot 가지고 놀기 (김용욱, GDG 코리아 안드로이드)
http://github.com/GDG-Korea/GO-Bot

- Holo to material (양찬석, 구글 코리아)
bitly.com/1yndGWd

- Glide (양찬석, 구글코리아)
bit.ly/1ynEtSr

- Polymer : Your first application (GDG 코리아 웹테크)
http://goo.gl/KmnCTr

- Polymer : Build a Material Design Mobile Web App with Paper Elements (문현경, GDG 코리아 웹테크)
http://goo.gl/SCrkmm

[Android] Migrating to Android Studio

앱 개발/관리용 통합개발환경(IDE) 첫번째 스테이블 버전 이며
기존 이클립스(Eclipse)를 IDE로 대체한다고 합니다.

If you're currently using Eclipse with ADT, we recommend you migrate toAndroid Studio as soon as possible, because the ADT plugin for Eclipse is no longer in active development.
To migrate existing Android projects, simply import them using Android Studio:
  1. In Android Studio, close any projects currently open. You should see theWelcome to Android Studio window.
  2. Click Import Non-Android Studio project.
  3. Locate the project you exported from Eclipse, expand it, select the build.gradle file and click OK.
  4. In the following dialog, leave Use gradle wrapper selected and click OK. (You do not need to specify the Gradle home.)
Android Studio properly updates the project structure and creates the appropriate Gradle build file.

Sorting Algorithm Animations, Code, Analysis, and Discussion

Sorting Algorithm Animations, Code, Analysis, and Discussion

[SITE] Web Framework

Web Framework 란 사이트가 있습니다.

JSP - AJAX - jQuery - Spring FrameWork 를 주로 개발 하는 개발자라면

Front-End에 대한 FrameWork도 접해보시는 좋은 사이트가 아닐까 생각합니다.

너무나도 정리가 잘 되어 있네요. 

2014년 12월 4일 목요일

[YouTube] 수능생 감동몰카 "난 엄마한테 왜 그랬을까?"

이 동영상을 보면 철없던 시절이 생각 납니다.

부모님께 감사하며 또한 부모가 된 입장에서 아이에게 더욱 더 사랑을 줘야

겠다는 생각이 많이 듭니다.

동영상을 보고 난 후 코 끝이 찡해지네요.

[수능생 감동몰카] 난 엄마한테 왜 그랬을까?




[JAVA] replaceAll(" ","") trim() 으로 제거되지 않는 공백 제거

String str =originalString.replaceAll(" """);

등으로 삭제되지 않는 공백문자를 제거 해 보자.
상기 코드로 삭제되지 않는 이유는.. cjk 문자셋에서 나타나는 IDEOGRAPHIC SPACE 라 불리는 유니코드 \u3000 , HTML 표현으로는 &#12288; 문자로 폰트 지원이 없으면 눈에 보이지 않는(display 되지 않는) 코드로만 존재하는 공백이기 때문.
http://www.fileformat.info/info/unicode/char/3000/index.htm(새 창으로 열기)

이럴 경우 다음과 같은 정규식을 통해 제거가 가능.

모든 공백 제거
String str =originalString.replaceAll("\\p{Z}""");


앞뒤 공백만 제거(trim)
String str =originalString.replaceAll("(^\\p{Z}+|\\p{Z}+$)", "");

[JAVA] SIMPLE DATE FORMAT AND Thread Safe

SimpleDateFormat을 static 변수로 잡아서 사용한다면 Thread Safety에 유의해야만 한다.

SimpleDateFormat은 Javadoc에 따르면 thread-safe하지 않다.

따라서 사용자가 Thread Safety를 보장해주거나,

thread-safe한 DateFormat 클래스를 사용해야만 한다.

다음은 SimpleDateFormat 클래스가 thread-safe하지 않음을 보여주는 예제이다.

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
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
 
public class ProveNotSafe {
    static SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
    static String testdata[] = { "01-Jan-1999", "14-Feb-2001", "31-Dec-2007" };
 
    public static void main(String[] args) {
        Runnable r[] = new Runnable[testdata.length];
        for (int i = 0; i < r.length; i++) {
            final int i2 = i;
            r[i] = new Runnable() {
                public void run() {
                    try {
                        for (int j = 0; j < 1000; j++) {
                            String str = testdata[i2];
                            String str2 = null; /*synchronized(df)*/ 
                            {
                                Date d = df.parse(str);
                                str2 = df.format(d);
                            }
                            if (!str.equals(str2)) {
                                throw new RuntimeException("date conversion failed after " + j + " iterations. Expected " + str + " but got " + str2);
                            }
                        }
                    } catch (ParseException e) {
                        throw new RuntimeException("parse failed");
                    }
                }
            };
            new Thread(r[i]).start();
        }
    }
}
 

실행 결과는 다음과 같다.

Exception in thread "Thread-2" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 31-Dec-2007 but got 14-Dec-2007
 at ProveNotSafe$1.run(ProveNotSafe.java:26)
 at java.lang.Thread.run(Thread.java:619)
Exception in thread "Thread-1" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 14-Feb-2001 but got 14-Feb-20100
 at ProveNotSafe$1.run(ProveNotSafe.java:26)
 at java.lang.Thread.run(Thread.java:619)
Exception in thread "Thread-0" java.lang.RuntimeException: date conversion failed after 0 iterations. Expected 01-Jan-1999 but got 14-Feb-20100
 at ProveNotSafe$1.run(ProveNotSafe.java:26)
 at java.lang.Thread.run(Thread.java:619)


주석 처리된 synchronized (df)를 활성화시키면 문제는 해결된다.

동기화 방식은 불필요한 병목이 될 수 있으므로 이상적으로는 쓰레드마다 자신의 

SimpleDateFormat 객체를 갖도록 하는 것이다.