반응형

1. 개요

 예전에 서비스 내에서 HTTP을 사용해 HR 시스템에서 정보를 가져오는 로직에 문제가 발생한 적이 있었다.

 원인은 사용하는 HttpClient객체가 static으로 선언되어 있어 멀티 쓰레드 환경에서 통신이 꼬여버린 것이다.

 해결방안으로 HttpClientBuilder를 사용해 PoolingHttpClientconnectionManager, requestConfig 객체를 주입받은   CloseableHttpClient 객체를 싱글톤으로 등록 후 호출할때마다 재사용하는 방식을 사용했다.

 

 그런데 다른 프로젝트의 SM업무를 맡던 도중 HTTP 통신할때마다 DefaultHttpClient 객체를 생성하고 있었다. 또한 인스턴스를 통신할때마다 생성하는 부분은 있지만 close시키는 부분이 없어서 DefaultHttpClient가 어떤녀석인지, 그리고 CloseableHttpClient와 무슨 차이점이 있는지 궁금해졌다.


2. CloseableHttpClient와 DefaultHttpClient

  이 두 클래스 모두 HttpClient 인터페이스의 구현클래스이다. 하지만 이녀석들의 차이에 대해 상세하게 정리된 내용을 찾지못해 실제 테스트를 통해 알아보기로 했다.


2.1. DefaultHttpClient, CloseableHttpClient 테스트코드 1.

 각각의 클래스에 DefaultHttpClient, CloseableHttpClient 인스턴스 생성 후 HTTP GET 통신을 하는 코드를 작성하였다.


2.2. CloseableHttpClient

DefaultHttpClient를 사용하여 HTTP GET 통신을 하는 코드이다.

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
public class UseHttpClient {
 
    private static final String URL = "http://www.naver.com";
    
    public static void main(String[] args) {
        HttpClient httpClient = null;
        try {
            httpClient = new DefaultHttpClient(); // httpClient 4.3버전 이후 deprecated 처리.
            
            HttpGet httpGet = new HttpGet(URL);
            
            HttpResponse response = httpClient.execute(httpGet);
            
            System.out.println(":: DefaultHttpClient Response ::");
            System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
 
            BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            String inputLine;
            StringBuffer responseBuf = new StringBuffer();
            
            while((inputLine = reader.readLine()) != null) {
                responseBuf.append(inputLine);
            }
 
            reader.close();
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
cs

17번째 라인부터는 응답 데이터를 읽어오는 부분인데 네이버 홈페이지 코드가 방대하게 나와서 굳이 출력하진 않았다.

중요한건 DefaultHttpClient 인스턴스를 사용해 HTTP 통신을 한번 했다는 점이다.


2.2. CloseableHttpClient

 CloseableHttpClient를 사용하여 HTTP GET 통신을 하는 코드이다.

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
public class UseCloseableHttpClient {
 
    private static final String URL = "http://www.naver.com";
    
    public static void main(String[] args) {
        CloseableHttpClient httpClient = null;
        try {
            httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(URL);
            
            CloseableHttpResponse response = httpClient.execute(httpGet);
            
            System.out.println(":: CloseableHttpResponse ::");
            System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
            
            BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            String inputLine;
            StringBuffer responseBuf = new StringBuffer();
            
            while((inputLine = reader.readLine()) != null) {
                responseBuf.append(inputLine);
            }
 
            reader.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}
cs

마찬가지로 16번째 라인부터는 응답 데이터를 읽어오는 부분이다.

여기서도 중요한건 CloseableHttpClient 인스턴스를 사용해 한번 통신했다는 점이다.


2.3. 첫번째 테스트로 인해 알게된 차이점

 첫번째 테스트로 인해 알게된 이 두 클래스의 차이점을 정리해보았다. 생성방식이 달랐고, CloseableHttpClient 클래스는 close 메서드가 있었다. 왜 있지?? 아직까지는 큰 차이를 느끼진 못해 다음 테스트를 진행하였다.

  DefaultHttpClient CloseableHttpClient
생성 new DefaultHttpClient() HttpClients.createDefault()
close 여부 X O

3.1. DefaultHttpClient, CloseableHttpClient 테스트코드 2.

 close라는 부분이 눈에 밟혀 각각의 인스턴스에서 HTTP 통신 메서드인 execute를 두번씩 호출해보았다.


3.2. DefaultHttpClient

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
public class UseHttpClient {
 
    private static final String URL = "http://www.naver.com";
    
    public static void main(String[] args) {
        HttpClient httpClient = null;
        try {
            httpClient = new DefaultHttpClient(); // httpClient 4.3버전 이후 deprecated 처리.
            
            HttpGet httpGet = new HttpGet(URL);
            
            HttpResponse response = httpClient.execute(httpGet);
            HttpResponse response2 = httpClient.execute(httpGet); // 추가한 코드
            
            System.out.println(":: DefaultHttpClient Response ::");
            System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
            System.out.println(":: response 2 result code : "+response2.getStatusLine().getStatusCode()); // 추가한 코드
 
            BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            String inputLine;
            StringBuffer responseBuf = new StringBuffer();
            
            while((inputLine = reader.readLine()) != null) {
                responseBuf.append(inputLine);
            }
 
            reader.close();
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
cs

13번 라인과 17번 라인에 호출 및 응답 코드를 출력하는 코드를 추가하였다.

실행 결과 다음과 같은 Exception이 발생하였다.

13번 라인에서 Exception 발생

예외 메시지는 "연결이 여전히 할당되어 있습니다. 다른 연결을 할당하기 전에 연결을 해제해야 합니다." 라는 뜻이다.

12번 라인에서 수행된 연결이 아직 끊기지 않은 상태에서 13번 라인의 execute 코드가 실행되어 그런 것같다. 그렇다면 과연 언제 끊기는 걸까? 생명주기가 궁금해졌지만 이는 더 파고들어야 알수있을 것 같다. 일단 여기서 확인된 점은 DefaultHttpClient 클래스는 생명주기가 끝나기 전까지 한번의 HTTP 요청을 수행할 수 있다는 것이다.


3.3. CloseableHttpClient

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
public class UseCloseableHttpClient {
 
    private static final String URL = "http://www.naver.com";
    public static void main(String[] args) {
        CloseableHttpClient httpClient = null;
        try {
            httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(URL);
            
            CloseableHttpResponse response = httpClient.execute(httpGet);
            CloseableHttpResponse response2 = httpClient.execute(httpGet); // 추가한 코드
            
            System.out.println(":: CloseableHttpResponse ::");
            System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
            System.out.println(":: response 2 result code : "+response2.getStatusLine().getStatusCode()); // 추가한 코드
            
            BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            String inputLine;
            StringBuffer responseBuf = new StringBuffer();
            
            while((inputLine = reader.readLine()) != null) {
                responseBuf.append(inputLine);
            }
 
            reader.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}
cs

마찬가지로 11, 15번 라인에 통신 및 응답코드를 출력하는 코드를 추가하였다.

실행 결과, 예외가 발생하지 않으며, 두 요청 모두 response Code 200을 응답받았다.

그렇다면 이 CloseableHttpClient의 생명주기는 어떻게 될지가 궁금해졌다. 마침 close 메서드도 있으니 추가 테스트를 진행해보았다. 그런데 예상치 못한 예외 코드가 출력되었다.


3.4. CloseableHttpClient close()

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
public class UseCloseableHttpClient {
 
    private static final String URL = "http://www.naver.com";
    public static void main(String[] args) {
        CloseableHttpClient httpClient = null;
        try {
            httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet(URL);
            
            CloseableHttpResponse response = httpClient.execute(httpGet);
            httpClient.close(); // 추가한 코드
            CloseableHttpResponse response2 = httpClient.execute(httpGet);
            
            System.out.println(":: CloseableHttpResponse ::");
            System.out.println(":: response 1 result code : "+response.getStatusLine().getStatusCode());
            System.out.println(":: response 2 result code : "+response2.getStatusLine().getStatusCode());
            
            BufferedReader reader= new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            String inputLine;
            StringBuffer responseBuf = new StringBuffer();
            
            while((inputLine = reader.readLine()) != null) {
                responseBuf.append(inputLine);
            }
 
            reader.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}
cs

 

11번 라인에서 Exception 발생

커넥션 풀이 종료됐다는 예외였다!

아. 생성한 CloseableHttpClient 인스턴스는 기본적으로 커넥션 풀을 지원한다는 것을 알게되었고, close 메서드는 이 인스턴스에 할당된 커넥션 풀을 close 시키는 것이었다.

3.2 테스트에서 2번을 연속으로 호출했을 때 예외가 발생하지 않았던 이유도 요청을 커넥션 풀을 통해 요청이 처리되기 때문이었다.


3.4. 두번째 테스트로 인해 알게된 차이점

  DefaultHttpClient CloseableHttpClient
생성 new DefaultHttpClient() HttpClients.createDefault()
close 여부 X O
HTTP 통신 횟수 1 ConnectionPool 설정에 따라 다름
ConnectionPool X O

 

이제 DefaultHttpClient에서 발생했던 예외의 원인과 생명주기는 무엇이고, CloseableHttpClient의 커넥션 풀이 어디서 설정되는지 확인해야한다. 이는 생성부분의 내부 코드를 확인해야한다.

 

이는 다음 포스팅에 정리하도록 하겠다.

 

- 혹, 글의 내용 중 맞지 않는 부분이나 수정할 사항이 있다면 꼭 댓글 부탁드립니다. 정말 감사히 받아드리겠습니다!

반응형
반응형

1. 개요

 코딩테스트를 하면 자주 나왔던 Iterator. 이게 무엇인지, 또 왜 사용하는지 알아보았다.


2. Iterator란?

 Iterator란 자바의 컬렉션(Collection)에 저장되어 있는 요소들을 순회하는 인터페이스이다.


3. Collection?

 Collection이란 자바에서 제공하는 자료구조들의 인터페이스로 List, ArrayList, Stack, Quque, LinkedList 등이 이를 상속받고있다. 즉, 이러한 컬렉션 인터페이스를 상속받는 클래스들에 대해 Iterator 인터페이스 사용이 가능하다.

Collection 구조 / 출처 : 위키백과


4. 사용 이유

 컬렉션 프레임워크에 대해 공통으로 사용이 가능하고 사용법이 간단하기 때문이다.

 저 위 그림에 나와있는 클래스, 인터페이스에서 모두 사용이 가능하다.

 

 Iterator를 사용하려면 정의 방법과 메서드 3개만 알면 된다.

 

 정의방법은 Iterator<T> iterator = Collection.iterator(); 이고,

 메서드는 다음 요소가 있는지 판단하는 hasNext(), 다음 요소를 가져오는 next(),  가져온 요소를 삭제하는 remove()가 끝이다. 아래 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class IteratorTest {
 
    public static void main(String[] args) {
        
        List<Integer> list = new ArrayList<Integer>();
 
        for(int i = 0;i <= 100; i++) {
            list.add(i);
        }
        
        Iterator<Integer> iter = list.iterator();
        
        while(iter.hasNext()) {
            int data = iter.next();
            System.out.print(data);
        }
        
    }
    
}
cs

5~9번 라인에서 Collection 인터페이스를 상속받는 ArrayList 객체를 생성하고 0부터 100 값을 add 한다.

11번 라인에서 Iterator<T> iterator = Collection.iterator(); 형식에 맞게 Iterator<Integer> iter = list.iterator(); 를 사용하여 Iterator를 참조한다.

13번 라인에서 hasNext() 메서드를 사용하여 다음 요소가 있는지 확인한다. (있으면 true, 없으면 false를 반환)

14번 라인에서 next() 메서드를 사용해 다음 요소의 값을 조회한다.


5. Iterator과 반복문

 Iterator를 통한 순회는 반복문을 통한 순회와는 메모리적으로 중요한 차이가 있다.

LinkedList를 통해 예를 들어보겠다.

 

1
2
3
4
5
6
7
8
9
10
11
    public void linkedListTest() {
        LinkedList<Integer> list = new LinkedList<Integer>();
        
        for(int i = 0;i <= 100; i++) {
            list.add(i);
        }
        
        for(int i = 0; i<= 100; i++) {
            list.get(i);
        }
    }
cs

 

Linked List의 메모리구조

add 메서드를 이용해 데이터 입력이 다 끝나면 위 그림과 같은 구조가 된다.

그리고 get(0)부터 get(100)까지를 수행하게 되는데 이는 0부터 100까지 총 101번의 요소를 조회하는게 아니다.

get(int index) 메서드는 시작 주소부터 index 만큼 요소들을 밟아가며 조회하는 메서드이기 때문이다.

만약 5번째 값을 조회한다면 처음 시작주소부터 시작하여 다음주소를 타고... 타고.. 를 총 5번 반복해야한다.

get 메서드가 실행되며 i 값이 증가할 때마다 메모리적으로 조회해야 하는 요소는 1번, 2번, 3번, 4번... 101번까지 증가하는 것이다. 총 5151번을 조회해야 한다.

 

이에반해 Iterator는 1부터 101번째까지의 요소에 대해 내부적으로 객체로 생성한 후 순차적으로 조회한다.

처음 주소로 돌아갈 필요가 없기때문에 next 메서드를 통해 조회 시 요소의 개수인 101번만 조회를 하게된다.

 

그렇다면 드는 생각. 속도면에서 훨씬 빠르지않을까?

 

훨씬 빠를것이라고 생각했으나... Iterator를 구현하기 위해 객체를 생성하는 부분에서 시간이 더 걸린다고 한다.

물론 그 차이는 크지 않지만...

 

결론.

 Iterator는 컬렉션 프레임워크에 대한 인터페이스이고, 사용법이 쉽다.

 하지만 반복문보다 속도면에서 조금 느리다는 평이 있다.

반응형
반응형

1. 개요

 - List에 대해 알고, 배열과 연결리스트의 개념과 차이를 이해한다.

 

2. 리스트(List)란?

 - 순서가 의미를 갖는 데이터들의 집합이다.

 - 삽입, 삭제, 검색 등의 기본적인 연산이 가능한 자료구조이다.

 - 리스트를 구현하는 대표적인 두가지 방법은 배열과 연결리스트이다.

 

즉, 리스트란 첫번째에는 A가, 두번째에는 B가 세번째에는 C가 들어있는 것처럼 순서마다 특정 데이터를 갖고 있고, 이 순서를 통해 삽입, 수정, 삭제, 검색을 할 수 있는 자료구조를 말한다.

 

3. 배열이란?

 - 같은 종류의 데이터들이 메모리상에 순차적으로 저장되어 있는 자료구조이다.

 - 같은 종류의 데이터들이기때문에 각각 데이터들의 크기가 같고, 메모리상에 순차적으로 저장되어 있기 때문에 주소 값 계산이 쉽고, 랜덤 액세스가 가능하다.

 - 조회할때는 빠르나 중간 데이터를 삭제, 추가 시에는 시간이 많이 걸린다.

 - 배열의 구조는 다음과 같다.

배열의 구조

 - int 형은 정수형 자료형으로 4Byte이다. 주소를 보면 4byte 간격이란 것을 알 수 있다. 내가 배열의 5번째 값을 조회하고 싶다면 처음 주소 값에서 자료형 크기 * 5 해준 값(주소값)을 구한 후 바로 접근하면 된다. 즉, 배열에서 특정 데이터를 조회하려면 자료형과 인덱스를 통해 주소 값 계산 후 바로 접근한다. (참고로 이 방식이 랜덤 액세스 방식이다.) 대신 중간에 데이터를 추가하거나 삭제 한다면 빈 자리를 매꾸기 위해 아주 많은 데이터들이 움직여야한다. 예를들면, 3번째 인덱스에 위치한 4000 값을 삭제한다면, 5000이 그 그 자리로 이동해야하고, 5000 자리로는 6000이 이동해야하고... 이게 끝까지 반복되어야한다. 만약 100000개 크기의 배열에서 1번째 값을 삭제한다면 100000-1번 움직여야한다.

 

4. 연결리스트

 - 같은 종류의 데이터들이 메모리상에 비순차적으로 저장되어 있는 자료구조이다.

 - 크기의 제한이 없다.

 - 다른 데이터의 이동 없이 중간에 삽입, 삭제가 가능하다. 

 - 랜덤 엑세스가 불가능하다.

 - 연결리스트의 구조는 다음과 같다.

 - 연결리스트의 값에는 총 2가지 정보가 들어간다. 하나는 메모리 주소에 해당하는 데이터. 하나는 다음 주소 값이다. 그리고 이 주소 값을 거칠때마다 인덱스가 증가한다. 예를들어 이 연결리스트의 2번째 인덱스에 해당하는 값을 조회하려면 시작주소인 00ffff00에 저장된 다음 주소인 00ffff08이 된다. 만약 100000개 크기의 연결리스트에서 100000번째 값을 조회하려면 주소 계산이 되지 않기 때문에 무작정 메모리 시작주소부터 다음 주소를 100000번 까야한다. 즉 조회에 시간이 오래걸린다. 대신, 어떤 값을 삭제하거나 추가할때에는 메모리를 찾은 후 다음주소만 바꿔주면 되기 때문에 배열에 비해 시간이 적게 걸린다. 예를들어 3번째와 4번째 사이에 3500을 추가하고싶다면 먼저 3번째 주소를 찾은 후(00ffff18) 메모리 빈 공간(00ffff10)에 3500값을 만들고 3500의 다음 주소를 3번째 인덱스의 다음 주소로 저장한다. 그리고 3번째 인덱스의 다음 주소에는 3500값의 주소로 저장한다.

 

3500 추가

 

5. 마치며

 가려운 등이 긁힌 느낌이다.. java에서 왜 배열 길이를 선언하고 후에 못늘리는지,(메모리에 순차적으로 들어가기 때문에 나중에 길이를 늘리게 되면 현재 다른 데이터를 위해 할당된 메모리 주소를 침범할 수 있기 때문) 굳이 길이 제한이 없는 ArrayList같은 연결리스트를 놔두고 왜 배열을 쓰는지 (랜덤 액세스로 조회 시간이 엄청 짧음), 그리고 이 둘의 차이, 메모리 구조 등을 확실히 이해할 수 있는 좋은 공부였다. 너무좋다...ㅎㅎ

반응형
반응형

1. 개요

Generic 프로그래밍, Generic 프로그래밍을 사용하는 이유를 예제를 통해 알아보자.


2. Generic 프로그래밍이란?

 제네릭 프로그래밍이란 하나의 데이터가 특정 데이터 타입에만 종속되지 않고 여러 데이터 타입을 가질 수 있는 기술에 중점을 두어 재사용성을 높일 수 있는 프로그램 방식이다. - 위키백과

 

 무슨 말인지 이해가 잘 안간다. 일단 Generic이 무슨 의미를 갖는지 확인해보자.

 

 Generic의 사전적 의미는 '포괄적인, 총칭, 일반적인' 이다.

 

 이를 Generic 프로그래밍의 정의와 혼합하여 생각해보니 '데이터를 포괄적으로 사용할수 있도록 하는 프로그래밍, 어떤 데이터 타입도 가질 수 있도록 일반화시키는 프로그래밍' 으로 이해를 해보았다.

 

 그렇다면 데이터를 포괄적으로 사용한다는 것이 프로그래밍에서 어떤 의미를 가질까?

 어떤 데이터가 A가 될수도, B가 될수도, C가 될수도 있다라고 생각이 되는데... 예제를 통해 이해해보자.


3. 예제

Box.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Box<T> {
 
    private T t;
    
    public void set(T t) {
        this.t = t;
    }
    
    public T get() {
        return t;
    }
}
cs

이 Box 클래스는 제네릭한 클래스이다. T란 녀석이 중간 중간 껴있는 것을 확인할 수 있는데,

T는 내가 생성한 클래스도, 타입도 아니다. Generic 한 클래스를 만들기 위해 사용하는 제네릭 변수이다.

 

Box 객체 생성시 제네릭 변수 T로 들어온 데이터 형을 아래와 같이 변환(?) 시켜준다.

main 메서드의 예제를 보면 이해할 수 있을 것이다.

Generic Class

 

 

Main.java

1
2
3
4
5
6
7
8
9
10
11
public class Main {
 
    public static void main(String[] args) {
        Box<Integer> box = new Box<Integer>();
        box.set(10);
        
        Integer i = box.get();
        System.out.println(i.intValue());
    }
}
 
cs

다음과 같이 Box<Integer> box = new Box<Integer>() 로 생성하면, 앞서 이미지의 T 값이 Integer가 되는 것이다.

그럼 Box 클래스는 Integer 타입의 데이터를 관리할 수 있는 객체로 활용되게 된다.

 

Box<String> box = new Box<String>() 으로 생성하면 Box 클래스는 String 타입의 데이터를 관리하고, Human 으로 생성하면 Human 타입의 데이터를 관리하게 된다. 이제 Box라는 클래스가 특정 데이터 타입에 종속되지 않는 Generic한 클래스가 된 것이다.

 

그래도 이해가 잘 안된다면 ArrayList를 생각해봐도 좋을 것 같다.

ArrayList 생성 시 new ArrayList<String>, new ArrayList<Integer>, new ArrayList<Human> 형태로 작성하게 되는데, 입력한 데이터 타입에 따라 add할 수 있는 데이터 타입이 정해지게 된다. Generic과 비슷한 형태와 성질을 띄는 것 같지 않는가?

 

덧붙이면 예제에는 제네릭 변수를 T로 사용했는데 굳이 T가 아니어도 된다. K, V, A 등의 값으로 사용해도 된다.

 

Pair.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Pair<K,V> {
 
    private K key;
    private V value;
    
    public void set(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public K getKey() {
        return key;
    }
    
    public V getValue() {
        return value;
    }
}
cs

Pair 클래스의 제네릭 변수를 K, V로 하여 클래스를 작성하였다.

그리고 main 메서드에서 K에는 String 타입을, V에는 Integer 타입으로 Pair 객체를 생성하였다.

 

1
2
3
4
5
6
7
8
9
10
public class Main {
 
    public static void main(String[] args) {
 
        Pair<String, Integer> pair = new Pair<String, Integer>();
        pair.set("test"9);
        System.out.println(pair.getKey()); // test
        System.out.println(pair.getValue()); // 9
    }
}
cs

 

그런데 생각해보니 Object로 대체해도 Generic한 클래스가 되지 않을까?

Box 클래스의 T를 빼버리고, Object 타입으로 넣으면 Object 타입은 모든 클래스의 슈퍼클래스이기 때문에 정형화시킬 수 있지 않는가? 그런데 왜 굳이 Object를 쓰지 않고 Generic을 사용할까?

 


4. Generic 사용 이유

Box.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Box {
 
    private Object t;
    
    public void set(Object t) {
        this.t = t;
    }
    
    public Object get() {
        return t;
    }
}
cs

제네릭 변수를 빼고 Object로 치환하였다.

 

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
 
    public static void main(String[] args) {
 
        Box box = new Box();
        box.set(10);
        
        //Integer i = box.get(); // 컴파일 에러
        Integer i = (Integer) box.get();
        System.out.println(i.intValue());
    }
}
cs

그리고 main 메서드에서 Box 객체를 생성하였더니 box.get() 부분에서 컴파일 에러가 발생하였다.

Integer i = box.get() 은 슈퍼클래스인 Object를 서브 클래스인 Integer가 참조하려는 형태이기 때문에 에러가 발생한다.

그래서 강제로 캐스팅하는 코드를 추가해주고 있다.

 

반면에 Generic 클래스를 사용하면 캐스팅하는 코드가 없다. Object를 사용하는 것보다 효율적이다.

 

추가적으로 Generic을 사용하면 컴파일 시점에 잡을 수 없었던 타입 에러를 검출 할 수 있다. 

 

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
 
    public static void main(String[] args) {
 
        Box box = new Box();
        box.set(10);
        
        //Integer i = box.get(); // 컴파일 에러
        String i = (String) box.get();
        System.out.println(i);
    }
}
cs

다음과 같이 9행을 String으로 수정하였다.

box.get 이라는 메서드는 Object 형태의 데이터를 return 하기 때문에 컴파일 시점에서는 Integer로 받아도, String으로 받아도 상관없다. 그래서 컴파일 에러가 발생하지 않는다. 하지만 런타임 시 10이라는 정수 타입의 변수라 들어가게 되고, 9번 라인에서 Integer를 String 타입으로 변환하려 하니 castException이 발생하게 된다.

castException

Generic은 캐스팅 할 필요가 없기 때문에 이러한 문제가 발생하지도 않는다.

 

이처럼 Generic은 Object를 사용하는 방법보다 효율적임을 알 수 있다.

 


5. 마치며

오늘은 Generic에 대한 개념을 잡자는 목적으로 포스팅을 하였는데, 목적을 달성했다는 느낌이 든다.

기부니가좋다.

 

반응형
반응형

1. 개요

 추상클래스와 인터페이스에 대해 알아보자.


2. 추상클래스란?

 - 추상(abstract) 클래스란 추상 메서드를 포함한 클래스이다.

 - 추상(abstract) 메서드란 선언만 있고, 구현이 없는 메서드이다.

 - 추상 메서드가 하나라도 선언되어있으면 객체를 만들 수 없기때문에 서브클래스를 만드는 용도로 사용된다.

 - 추상 메서드와 추상 클래스는 abstract 키워드로 표시한다.

 - 추상 클래스를 상속받는 클래스는 추상메서드를 구현해야한다.


3. 추상 클래스 사용 이유?

 객체도 못만들고, 번거롭게 abstract 키워드 추가하고, 구현하지 않는 추상메서드를 굳이 선언... 왜 쓸까?

 라는 의문이 들기 마련이다. 말보다는 직접 코드로 경험하면 이해할 수 있을 것이다. 다음 상황을 생각해보자.


4. 상황

 찰흙을 빚어 도형을 만들고, 만든 도형을 전시하는 프로그램을 구현한다. 이에 대해 요구사항을 부여하였다.

 1) 삼각형, 사각형, 원 모양의 도형을 만들 수 있다.

 2) 내가 만든 도형에 대해 관리 및 조회할 수 있다.

 3) 도형 전시를 위해 해당 도형의 넓이를 알아야 한다.

 

 직접 코딩을 하기 전에 생각해보자. 

 1번을 처리하기 위해서는 삼각형, 사각형, 원에 대한 클래스가 필요하다.

 2번을 처리하기 위해서는 내가 만든 도형을 배열이나 리스트로 관리해야한다. 삼각형 배열에는 삼각형 객체를, 사각형 배열에는 사각형 객체를, 원 배열에는 원 객체를 넣어 관리해도되지만, 3개의 배열을 각각 선언해야 하니 효율적으로 느껴지진 않는다. 그래서 도형이라는 클래스를 만들고 삼각형, 사각형, 원 클래스가 이를 상속하도록 구현한다. 이렇게 되면 도형이라는 자료형 하나로 모든 클래스를 관리할 수 있다.

 3번을 처리하기 위해서는 각 클래스마다 자신의 넓이를 구하는 메서드가 필요하다.

 이제 직접 예제를 통해 이를 구현해보겠다.


5. 예제

  

5.1. 클래스 생성

 - Triangle, Square, Circle, Figure(도형), Exhibition(전시), Main 클래스를 생성한다.

 - Triangle, Square, Circle은 Figure 클래스를 상속받는다.

 - 삼각형은 밑변, 높이. 사각형은 가로, 세로. 원은 반지름 값을 나타내는 멤버변수를 정의하고, 생성자를 생성한다.

 

5.2. 1번 요건 처리

 - 세 클래스를 다음과 같이 구현한다.

 - 클래스를 구현했으니 각 도형에 대한 객체를 만들 수 있게 되었다.

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
public class Triangle extends Figure{
 
    private int base;
    private int height;
    
    public Triangle( int baseint height) {
        this.base = base;
        this.height = height;
    }
}
 
 
/////////////////////////////////////
 
public class Square extends Figure{
 
    private int horizontal;
    private int vertical;
    
    public Square( int horizontal, int vertical) {
        this.horizontal = horizontal;
        this.vertical = vertical;
    }
}
 
/////////////////////////////////////
 
public class Circle extends Figure{
 
    private int redius;
    
    public Circle(int redius) {
        this.redius = redius;
    }
}
cs

 

5.3. 2번 요건 처리

 - 조회에 사용할 toString 메서드를 각 클래스에 추가한다.

 - 도형들을 관리할 Exhibition 클래스를 정의한다.

 

Trigangle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Triangle extends Figure{
 
    private int base;
    private int height;
    
    public Triangle( int base, int height) {
        this.base = base;
        this.height = height;
    }
    
    public String toString() {
        return "class : Triangle , base : "+base + ", height : "+height; 
    }
}
cs

 

Square.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Square extends Figure{
 
    private int horizontal;
    private int vertical;
    
    public Square( int horizontal, int vertical) {
        this.horizontal = horizontal;
        this.vertical = vertical;
    }
    
    public String toString() {
        return "class : Square , horizontal : "+horizontal + ", vertical : "+vertical; 
    }
}
cs

 

Circle.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Circle extends Figure{
 
    private int redius;
    
    public Circle(int redius) {
        this.redius = redius;
    }
    
    public String toString() {
        return "class : Circle , redius : "+redius; 
    }
}
cs

 

Exhibition.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Exhibition {
 
    private List<Figure> list;
    
    public void setList( List<Figure> list ) {
        this.list = list;
    }
    
    public void showList() {
        for(Figure figure : list) {
            System.out.println(figure.toString());
        }
    }
}
cs

 

이제 Main 클래스에 main 메서드를 생성하여 1,2 번 요건이 만족되도록 정의 후 실행시켜보자.

 

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
 
    public static void main(String[] args) {
        //삼각형, 사각형, 원 객체 생성
        Triangle triangle = new Triangle(4,3);
        Square square = new Square(5,5);
        Circle circle = new Circle(10);
                
        // 관리에 사용될 리스트 생성 및 추가
        List<Figure> list = new ArrayList<Figure>();
                
        list.add(triangle);
        list.add(square);
        list.add(circle);
                
        Exhibition exhibition = new Exhibition();
        exhibition.setList(list);
        exhibition.showList();
                
    }
}
cs

5~7번 라인에서 삼각형, 사각형 원 객체를 만든다.

10번 라인에서 도형을 관리할 list를 생성한다.

12~14번 라인에서 생성한 도형들을 list에 넣는다.

16~17번 라인에서 전시 객체를 생성하고 그 안에 만든 도형들을 set 한다.

18번 라인에서 도형들을 조회한다.

 

출력 결과를 확인해보면 다음과 같이 조회된다.

showList()

 

이제 마지막 요건인 도형의 넓이를 구하면 끝이다. 그리고 여기서 추상 클래스의 사용 이유에 대해 조금은 와닿을 수 있을 것이다.

 

5.4 3번 요건 처리

 - 각 도형마다 구하는 넓이 공식이 다르기때문에 삼각형, 사각형, 원 클래스에 넓이를 구하는 메서드를 각각 구현한다.

 - Exhibition 클래스에서는 구현한 메서드를 출력하는 메서드를 생성한다.

 

Triangle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Triangle extends Figure{
 
    private int base;
    private int height;
    
    public Triangle( int base, int height) {
        this.base = base;
        this.height = height;
    }
    
    public String toString() {
        return "class : Triangle , base : "+base + ", height : "+height; 
    }
    
    public double getArea() {
        return base * height * 0.5;
    }
}
cs

 

Square.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Square extends Figure{
 
    private int horizontal;
    private int vertical;
    
    public Square( int horizontal, int vertical) {
        this.horizontal = horizontal;
        this.vertical = vertical;
    }
    
    public String toString() {
        return "class : Square , horizontal : "+horizontal + ", vertical : "+vertical; 
    }
    
    public double getArea() {
        return horizontal * vertical;
    }
}
cs

 

Circle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Circle extends Figure{
 
    private int redius;
    
    public Circle(int redius) {
        this.redius = redius;
    }
    
    public String toString() {
        return "class : Circle , redius : "+redius; 
    }
    
    public double getArea() {
        //파이 값은 임의로 3.14로 가정한다.
        return redius * redius * 3.14;
    }
}
cs

 

Exhibition.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Exhibition {
 
    private List<Figure> list;
    
    public void setList( List<Figure> list ) {
        this.list = list;
    }
    
    public void showList() {
        for(Figure figure : list) {
            System.out.println(figure.toString());
        }
    }
    
    public void getFigureArea() {
        for(Figure figure : list) {
            //컴파일 발생
            System.out.println(figure.getClass().getSimpleName()+ " : "+ figure.getArea());
        }
    }
}
cs

 

 이로써 넓이를 구하는 것까지 구현했다고 생각했으나, Exhibition.java 코드의 18번째 라인에서 에러가 발생하게 된다.

발생 원인은 figure 객체에 getArea() 메서드를 찾지 못해서 발생한다.

하지만 분명 figure는 삼각형, 사각형, 원 클래스 중 한 객체를 참조하고 있고, getArea 메서드가 정의되어 있는데 왜 찾지 못하는 걸까? 바로 다이나믹 바인딩의 처리가 런타임시점에서 진행되기 때문이다.

 실제 코드를 돌리면 16번째의 figure 객체는 삼각형, 사각형, 원 객체 중 하나겠지만, 컴파일러 입장에서는 컴파일 시점에 이를 확인할 방법이 없기 때문에 에러가 뜨는것이다.

 

그럼 해결방안으로 Figure 클래스에 getArea 메서드를 생성하는 방법이 있다.

그렇게 되면 컴파일 시점에는 에러가 발생한 지점에서 getArea 메서드를 Figure 클래스의 메서드로 인식하여 에러를 떨구지 않을 것이고, 런타임 시에는 다이나믹 바인딩으로 인해 참조 객체의 getArea 메서드를 호출할 것이다.

 

하지만 Figure 클래스의 getArea가 전혀 사용되지 않는 상황인데 저렇게 메서드를 정의해두면, 불필요한 코드일 뿐더러, 타 개발자가 이를 봤을 때 당연히 이 메서드와 클래스가 어디서 사용되고 있을 것이라고 생각할 수 있다. 하지만 코드를 보면 실제로 Figure 클래스는 참조객체로써 사용되지 않고있다. 뭔가 혼란스럽지 않는가? 이러한 혼란 해소할 수 있는 것이 바로 추상 메서드, 추상 클래스이다.

 

 추상 메서드를 사용했기 때문에 Figure 클래스는 getArea 메서드를 선언만 해놓으면 된다. 그럼 타 개발자가 이 코드를 봤을 때 이 클래스가 객체로 생성되어 어디선가 사용되고 있지 않을까에 대해 생각할 필요가 없다. 개요에서 설명했듯이 추상클래스는 서브클래스를 위한 클래스이고, 객체로 생성이 불가능하기 때문이다. 또한 자식 클래스에서 정의해서 쓰고 있다라는 사실을 명확하게 알 수 있다.

 

Figure.java

1
2
3
4
5
public abstract class Figure {
 
    public abstract double getArea();
}
 
cs

 

실행화면

 

실행 결과를 통해 모든 요건을 만족하는것을 확인할 수 있다.


 

 

설명이 좀 중구난방 한것같지만 그래도 쥐어짜면서 글을 써내려가니 머릿속에서 확실히 조금 더 정리된 느낌이고, 이전에 공부했던 정형화나 다이나믹 바인딩도 복습할 수 있었다.

반응형
반응형

1. 개요

 상속 관계인 두 클래스의 메서드를 오버라이딩 한 후, 다형성의 원리를 적용하여 자료형과 참조 객체를 다르게 설정하였때 어떤 결과가 나올지 알아보았다.

 

 말이 조금 어렵게 느껴지니 예로 설명하면 다음과 같다.


 Parent라는 부모 클래스와 이를 상속받는 Child 자식 클래스가 있다.

 Parent 클래스에는 hi라는 메서드가 있기때문에 Child에서도 호출이 가능하나, 메서드를 재정의하고 싶어 메서드 오버라이딩을 하였다.

 객체 생성 시점에 Child child = new Child(); 형태로 생성 후 child.hi() 메서드를 호출해도 되지만, 다형성의 원리를 사용하여 Parent child = new Child(); 형태로 생성하였다.

 만약, 이 상황에서 child.hi() 메서드를 호출하면 Parent 클래스와 Child 클래스의 hi() 중 어떤 메서드가 실행될까?


 

 Child 객체를 참조하고있으니 Child 객체의 메서드가 호출될 것 같지만 자료형을 Parent이니 Parent의 메서드가 호출될 것 같기도 하다. 다형성에 대해 알아보고 이 고민을 해소해보자.


2. 다형성(Ploymorphism)이란?

 슈퍼클래스 타입의 변수가 서브클래스 타입의 객체를 참조할 수 있다는 성질이다.

 Child 클래스가 Parent 클래스를 상속받고 있다면 슈퍼 클래스 타입의 변수인 Parent가 Child 클래스를 참조할 수 있다는 의미인데, 예제를 보면 이해가 빠를 것이다. 다형성이란 말이 어렵지 사실 되게 익숙한 개념이다.

 

 그럼 예제를 통해 상속관계, 메서드 오버라이딩, 다형성이 적용된 객체를 생성해보도록 하겠다.


3. 예제

3.1. Parent.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Parent {
    
    protected String name;
    protected int age;
    
    public Parent(String name, int age) {
        this.name = name;
        this.age = age;
    }
    protected void hi() {
        System.out.println("안녕하십니까 올해로 "+age+"살인 "+name+"입니다.");
    }
}
cs

 

3.2. Child.java

1
2
3
4
5
6
7
8
9
10
11
public class Child extends Parent{
 
    public Child(String name, int age) {
        super(name, age);
    }
    
    @Override
    public void hi() {
        System.out.println("안녕? 내 이름은 "+name+"이고, "+age+"살이야! 만나서반가워~");
    }
}
cs

Parent 클래스를 상속받고 있으며 부모 클래스의 hi() 메서드를 오버라이드하여 재정의하였다.

 

3.3. Main.java

1
2
3
4
5
6
7
public class Main {
 
    public static void main(String[] args) {
        Parent child = new Child("심드류 아들",10);
        child.hi();
    }
}
cs

main 메서드에서 다음과 같이 자료형이 Parent인 child 변수에 Child 객체를 생성하여 초기화해주었다.

슈퍼클래스 타입(:Parent)인 변수(:child)가 서브클래스의 객체(new Child)를 참조하고 있다. 바로 이게 다형성이다.

그럼 이 상태에서 hi() 메서드를 실행하면 어떤 결과가 나올까?


4. 결과

Child 객체의 hi 메서드가 실행됨

 결과는 바로! Child 객체의 hi 메서드가 실행된다. 즉 자료형이 아닌, 참조 객체의 메서드가 실행되는 것이다.

 다형성의 원리를 사용하여 객체를 생성하고, 오버라이드된 메서드를 호출했을 때에는 슈퍼클래스의 메서드가 아닌, 서브클래스의 메서드가 호출된다. 즉, 참조 객체가 무엇이냐에 따라 호출하는 메서드가 동적으로 바뀔 수 있다는 뜻이다.

이러한 내용을 동적 바인딩(dynamic binding) 이라고 한다.


5. 정리

 상속관계에서 서브 클래스의 메서드를 오버라이딩 하고 다형성의 원리에 입각하여 객체 생성 후 메서드를 호출하면, 해당 메서드는 동적 바인딩된다.

 

반응형
반응형

1. 개요

 자주 쓰는 상속과 생성자의 개념에 대해 간단히 정리하고, 상속관계에서 발생할 수 있는 문제에 대해 알아보자.


2. 상속이란?

 상속이란 부모님에게 재산을 물려받듯이 부모 클래스의 멤버필드와 메서드를 자식클래스가 물려받는 것이다.

 상속을 통해 불필요한 중복을 제거할 수 있다.


3. 생성자란?

 객체에 대한 초기화 메서드이다. 멤버 변수를 초기화하거나 자원을 할당할 수 있다.

 클래스 내 생성자가 정의되어 있지 않을 경우 자바가 자동으로 no-parameter 생성자를 생성한다.

 

 여기까지가 많은 사람들이 알고 있는 생성자에 대한 간단한 정의이다. 하지만 다음과같은 내용도 있다.

더보기

 자식클래스의 생성자는 부모클래스의 생성자를 먼저 호출한다.

 명시적으로 호출하지 않을 경우 부모클래스의 no-parameter 생성자가 자동적으로 호출된다.

별게 아닌 것 같지만, 이 내용을 알지 못했을 때 발생할 수 있는 문제 상황이 있다. 한번 알아보자.


4. 상속, 생성자 예제

 상속 관계로 부모 클래스는 술(Alcohol), 자식 클래스는 럼(Rum)으로 하겠다. (럼: 해적들이 마시던 술)

 

4.1. Alcohol.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Alcohol {
 
    protected double frequency;
    protected double price;
    protected String flavor;
    
    public Alcohol(double frequency, double price, String flavor) {
        this.frequency = frequency;
        this.price = price;
        this.flavor = flavor;
    }
    
    public String whatFlavor() {
        return "flavor is "+flavor;
    }
}
cs

모든 술에는 도수, 가격, 맛이 존재한다. 그에 맞게 frequency, price, flavor 멤버필드를 선언해주었고, 모든 멤버필드의 값을 초기화해주는 생성자도 생성하였다. 추가적으로 whatFlavor라는 간단한 메서드도 정의하였다.

 

4.2. Rum.java

1
2
3
4
5
6
7
8
9
10
11
public class Rum extends Alcohol{
 
    private String rumType;
 
    public Rum(String rumType, double frequency, double price, String flavor) {
        this.rumType = rumType;
        this.frequency = frequency;
        this.price = price;
        this.flavor = flavor;
    }
}
cs

Alcohol 클래스를 상속받는 Rum 클래스를 정의하였다.

Alcohol 클래스를 상속받았기때문에 이에 대한 멤버필드와 메서드를 Rum 클래스에서 할당받게 된다. 즉, Rum 클래스의 멤버필드는 rumType, frequency, price, flavor가 된다. 마찬가지로 Rum 클래스의 생성자도 정의하였다.

...

...

...

혹시 이 예제에서 문제점을 발견했는가? 발견했다면 당신은 멋진사람...

 

4.3. 컴파일 에러

 이 예제는 언뜻보면 이상이 없어보이나 다음과 같이 컴파일 에러가 발생하는 코드이다.

컴파일 에러

 에러 내용은 다음과 같다. 

더보기

 Implicit super constructor Alcohol() is undefined.

 암시 적 슈퍼 생성자 Alcohol ()이 정의되지 않았습니다.

 바로 이게 앞서 언급했던 "자식클래스의 생성자는 부모클래스의 생성자를 먼저 호출한다." , "명시적으로 호출하지 않을 경우 부모클래스의 no-parameter 생성자가 자동적으로 호출된다." 라는 생성자의 정의에 의해 발생한 에러이다.

 

 자식클래스의 생성자는 호출 전에 부모클래스의 생성자를 호출해야하는데, 현재 Rum 생성자를 보면 알수있듯이 부모 클래스의 생성자를 호출하는 메서드인 super(...)가 없다. 즉, 명시적으로 호출하지 않았기 때문에 자바가 Rum 생성자 내에서 부모 클래스의 no-parameter 생성자를 자동적으로 호출하려했으나 현재 부모클래스에는 no-parameter 생성자가 없기 때문에 위 에러가 발생한 것이다.

 

그렇다면 해결은 어떻게 할까?


5. 해결

이를 해결하기 위해서는 두가지 방법이 있다.

첫번째, 부모클래스에 no-parameter 생성자를 생성한다.

두번째, 자식클래스의 생성자에 부모클래스의 생성자를 명시적으로 호출한다.

첫번째 방법은 에러 제거만을 목적으로 불필요한 코드를 추가하기 때문에 추천하지 않는 방법이다. 자식 클래스 생성자 내에서 부모 클래스의 생성자를 명시적으로 호출하는 두번째 방법을 사용하자.

 

5.1. Rum.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Rum extends Alcohol{
 
    private String rumType;
 
    public Rum(String rumType, double frequency, double price, String flavor) {
        super(frequency, price, flavor);
        this.rumType = rumType;
    }
    
    public static void main(String[] args) {
        Rum rum = new Rum("DarkRum"40.069000"bitter");
        System.out.println(rum.whatFlavor());
    }
}
cs

Rum 생성자 안에서 super() 메서드를 통해 부모 클래스의 생성자를 명시적으로 호출해주고, 부모클래스에 없는 rumType은 this를 통해 초기화하였다.

 

main 메서드에서 생성자를 통해 Rum 객체를 생성하여 테스트하면 정상적으로 작동됨을 확인할 수 있다.

 

 

오늘 자료구조를 공부하며 알게 된 새로운 내용을 정리할 수 있어 기쁘다.

반응형
반응형

1. 개요

 Window10 및 CentOS7 환경에서 Jenkins를 설치해보자.


2. Jenkins란?

 Jenkins는 CI 툴로 지속적 통합 서비스를 제공해준다. 소스관리, 자동배포, 테스트 및 코드 분석까지도 지원한다.


3. Window10 Jenkins 설치

 3.1) Jenkins 설치 파일 다운로드

  - https://www.jenkins.io/download/ 에 접속 및 LTS 버전 중 Windows 를 클릭하여 다운로드 한다.

Window용 Jenkins 설치

 

 3.2) Jenkins 설치 파일 실행

  - Logon Type 체크 부분이 나오기 전까지 Next를 선택한다.

  - Logon Type 선택 항목이 나오면 Run service as LocalSystem을 선택한다.

Jenkins 설치

   Q. this account either does not have the privilege to logon as a service Error가 발생

   A. 시스템 계정의 로그온 권한이 없어서 발생하는 에러이며, 시작메뉴에서 '로컬 보안정책 - 로컬 정책 - 사용자 권한 할당 - 서비스 로그온 - 사용자 또는 그룹 추가' 에서 사용자를 추가하여 해결

로컬 보안정책

 3.3) Jenkins 관리자 비밀번호 설정

  - Jenkins 설치가 완료되면 8080 포트로 Jenkins 서버가 실행되며, Jenkins Starter 페이지가 자동으로 열림

  - 아래 화면의 빨간색으로 표시된 경로 파일을 열어 입력된 비밀번호를 복사한 후 빈칸에 삽입

  - 파일 경로에 파일이 없을 경우 PC 재부팅

Jenkins Getting Started

 

 3.4) 플러그인 설치

  - Jenkins에서 사용할 플러그인 설치 페이지

  - 추후에도 설치가 가능하므로 Suggested plugins를 클릭하여 설치

plugin 설치

 

 3.5) 계정 및 URL 설정

  - Jenkins 계정 생성

 

 3.6) Jenkins URL 설정

  - Jenkins URL을 설정하는 페이지

  - 8080 포트가 사용중이라면 다른 포트로 변경해도 무관

  - 추후에 포트 변경이 가능하니 참고

URL 설정

 

 3.7) 로그인

  - 로그인 성공 후 메인화면이 출력되면 설치 성공

Jenkins 로그인


4. CentOS7 Jenkins 설치

 - CentOS7에서는 yum을 사용하여 Jenkins를 간편하게 설치할 수 있다.

 - 만약 yum이 설치되어있지 않다면 설치 후 아래 명령어들을 입력하면 된다.

 

 4.1) Jenkins 설치

  # wget –o /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo

  # rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key 

  # yum install jenkins

 

 4.2) Jenkins 포트 변경

  # vi /etc/sysconfig/Jenkins

  - 위 명령어 실행 후 JENKINS_PORT="PORT" 부분을 찾아 수정

 

 4.3) 방화벽 오픈

  # firewall-cmd --permanent --add-port=변경한포트/tcp

  # firewall-cmd --reload

 

 4.4) Jenkins 접속 및 설정

  - localhost:PORT 를 브라우저에 입력하면 Window 설치의 3.3부터 동일하게 따라하면 된다.

반응형
반응형

1. 개요

 Table 생성 후 셀마다 셀 테두리를 설정하였으나, 셀 테두리 사이 간격이 생기는 현상이 발생하고 있음.

셀 테두리가 벌어짐


2. 해결

 table 태그에 대한 css로 border-collapse : collapse 설정을 적용하면 표의 테두리 및 셀간 테두리 간격을 없애고, 겹치는 부분은 한줄로 표시.

1
2
3
4
5
6
7
8
9
10
table{
    border : 1px solid black;
    border-collapse : collapse;
}
 
td,th{
    border : 1px solid black;
    width : 100px;
    height : 30px;
}
cs

3. 결과

border-collapse : collapse 적용

반응형

'프론트엔드 > CSS' 카테고리의 다른 글

[CSS] !important 란?  (1) 2019.07.26
CSS의 등장배경  (0) 2019.06.19
반응형
반응형

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

반응형

+ Recent posts