일반 프로그램은 하드웨어 위에서 하드웨어를 제어하기 위한 OS 동작,

OS 위에서 프로그램이 실행.

그래서 프로그램을 OS 마다 다르게 제작.

윈도우에서 동작하는 프로그램, 리눅스에서 동작하는 프로그램.

반면 자바는 OS 위에 JVM(java virtual machine) 존재하여 jvm 위에서 프로그램이 실행되기 떄문에 운영체제에 의존적이지 않음.

운영체제 상관없이 자바 프로그램 실행이 가능

 

일반 프로그램 hardware > OS(operating system) > program

자바 프로그램 hardware > OS(operating system) > jvm > java program

 

컴파일 과정 :

개발자가 작성한 자바 코드를 컴파일러가 기계어인 자바 바이트 코드(.class)로 변환,

변환된 코드를 인터프리터가 한줄 씩 실행시키며 애플리케이션을 실행.

 

 

자바 코드 --컴파일러--> 자바 바이트 코드(class) --인터프리터--> 애플리케이션 실행

https://moomini.tistory.com/13

 

반응형

 

OSI (Open Systems Interconnection : 네트워크 통신의 개방 시스템 상호 연결)

여러 정보 통신 업체 장비들은 자신들의 장비끼리만 연결이 되는 등 호환성이 없었다.

이에 ISO 단체에서 1984년에 모든 시스템들의 상호 연결에 있어 표준이 되는 OSI 참조 모델 발표

 

데이터를 전송할 때 7-->1계층으로 내려가면서 각각의 층마다 인식할 수 있는 헤더가 붙고 이를 캡슐화(캡슐레이션)라 함

데이터를 수신할 때 1-->7계층으로 올라가면서 헤더가 벗겨지는데 이를 디캡슐레이션이라 함

 

7. Application
Services that are used with end user applications
사용자 인터페이스 제공
프로토콜 : http, ftp

6. Presentation
Formats the data so that it can be viewed by the user
운영체제의 입출력되는 데이터를 암/복호화, 포맷 변환 등 두 장치가 전송 데이터를 이해할 수 있도록 함
제어코드, 문자 및 확장자
jpeg, mpeg

5. Session
Establishes/ends connections between two hosts
통신 세션 구성 (port연결)
대표적인 프로토콜은 SSH

4. Transport
Responsible for the transport protocol and error handling
발신지 대 목적지간(종단 대 종단(end to end) 전체 메시지를 제어 및 에러 관리
L4(로드밸런싱)
대표적인 프로토콜은 tcp, udp

3. Network
Reads the IP address from the packet
패킷를 목적지까지 안전하고 빠르게 전달하는기능(라우팅)
대표적 프로토콜은 ip

2. Datalink
Reads the MAC address from the data packet
MAC주소를 이용하여 정확한 장치로 정보 전달 (브릿지, 스위치)
노드 대 노드 전달을 감독(1계층의 오류를 찾고 재전송)
3계층에서 정보를 받아 주소와 제어정보를 시작(header)과 끝(tail)에 추가

1. Physical 
Send data to the physical wire
비트 (1, 0 (on off)) 단위의 통신단위사용
데이터는 전달만 할 뿐 메시지 내용은 신경 X
통신 cable, hub(더미허브), 리피터 장비가 이에 해당

Hub, 스위치, 라우터 차이

1. Hub(더미허브)(L1)(bit)

1) 전기적 신호를 증폭시켜 LAN의 전송거리를 연장시키는 역할을 하는 네트워크 장비(리피터 역할)

- UTP케이블의 데이터 최대 전송거리는 100미터 남짓이므로 100미터 지점에 허브를 설치할 경우 더 먼거리까지 데이터 전송이 가능

2) 여러대의 장비를 LAN에 접속할 수 있도록 하나로 연결시켜주는 중심축(허브)역할을 하는 네트워크 장비

- IP를 할당하는 기능은 없으며 단순히 포트만 늘려주는 기능을 함

- 허브에 연결된 컴퓨터 및 네트워크 장비의 MAC 주소를 따로 저장하지 않으므로 한 장비에서 전송된 데이터 프레임을 허브에 연결된 모든 장비에 전송(플러딩)

- 허브는 단순한 분배 중계기에 불과하여 연결되는 컴퓨터 수에 따라 데이터 전송 대역이 분리됨(10Mbps 네트워크 라인에 허브를 물리고 허브에 5대의 컴퓨터를 연결했다면 각 컴퓨터 대역폭은 2Mbps)

3) 데이터 송신이 하나의 포트에서만 가능

- CSMA/CD 프로토콜(Carrier Sense Multiple Access with Collision Detection)사용하여 충돌을 방지

* 더미허브 장비는 요즘은 거의 사용되지 않는 장비

 

2. 스위치(스위치허브)(L2)(frame)

- 데이터 송신이 여러 포트에서 가능

연결된 컴퓨터 및 네트워크 장비의 IP와 MAC주소 모두 테이블로 가지고 있어 프레임 전송시 목적지를 파악하여 해당 목적지에 프레임을 전송. 단, 테이블에 없는 목적지를 가진 패킷이 오면 허브의 동작과 마찬가지로 모든 기계에 포워딩.

 

3. 라우터(L3)(패킷)

- 둘 혹은 그 이상의 네트워크와 네트워크 간 데이터 전송을 위해 최적 경로를 설정하며 데이터를 해당 경로를 따라 한 통신망에서 다른 통신망으로 통신할 수 있도록 도와주는 인터넷 접속 장비

- 데이터에 담긴 수신처의 주소를 읽고 적절한 통신통로를 이용해 다른 통신망으로 데이터를 전송하는 장치

- LAN(Local Area Network)을 연결해주는 장치 외부로부터 내부  보호

 

* 공유기

ISP(Internet Service Provider) 업체에서 제공하는 한 개의 인터넷 IP address(공인IP)로 여러 대의 컴퓨터가 인터넷을 공유할 수 있는 기능을 제공

가정용 소용량 라우터

라우터의 NAT(Network Address Translation) 기능을 가지고 있음

: 공유기 사용시 ISP에서 할당받은 공인IP를 내부 네트워크에서 여러개의 사설 IP 주소로 변환하여 사용

 

* 홈 네트워크 구성의 예

모뎀

  ㄴ 공유기(라우터) 

            ㄴ 컴퓨터1                           

            ㄴ TV 셋탑박스

            ㄴ 허브(스위치허브)

                             ㄴ 컴퓨터2

                             ㄴ 컴퓨터3

                             ㄴ NAS

 

 

TCP/IP 4계층 (인터넷모델)

4. Application : OSI 5,6,7

3. Transport : OSI 4

2. Internet : OSI 3

1. Network Interface : OSI 1,2

 

 

참고:

http://blog.naver.com/PostView.nhn?blogId=demonicws&logNo=40117378644

https://shlee0882.tistory.com/110

https://www.quora.com/What-is-the-difference-between-HTTP-protocol-and-TCP-protocol

https://instrumentationtools.com/7-osi-layers-of-communications/

https://m.blog.naver.com/PostView.nhn?blogId=wlsdml1103&logNo=220936267002&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

https://minwan1.github.io/2018/10/01/2018-09-03-network-network-divice/

https://m.blog.naver.com/PostView.nhn?blogId=vanshaw&logNo=220709918927&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

 

반응형

이미지 출처: https://stackoverflow.com/questions/11541338/should-i-use-arraylist-or-list/11541387#11541387

Set, List Interface 는 Collection 을 extends 하고 있다 (구조 동일)

Map Interface 는 별도로 정의돼있다.

 

[List]

순서가 있는 데이터의 집합, 데이터 중복을 허용

ArrayList, Vector, LinkedList 차이에 대한 설명

 

[Set]

순서가 없는 데이터의 집합, 데이터 중복을 허용하지 않음

HashSet, LinkedHashSet, TreeSet 차이에 대한 설명

 

[Map]

키와 값, 한 쌍으로 이루어지는 데이터의 집합으로 순서가 없음

값은 중복을 허용하나 키는 중복될 수 없음(키는 중볼될 경우 덮어 씀)

HashTable, HashMap, LinkedHashMap, TreeMap 차이에 대한 설명

 

[Queue]

First In First Out 구조를 따름

LinkedList, PriorityQueue 차이에 대한 설명

 

 

반응형

'back > java' 카테고리의 다른 글

[Java] Generic 제네릭  (0) 2019.12.28
[Java] Compile  (0) 2019.12.22
Comparator, Comparable + Arrays.sort() 그리고 인터페이스..  (0) 2019.09.02
깊은복사(Deep Copy)와 얕은복사(Shallow Copy)  (3) 2019.08.31
LocalHost IP 가져오기  (0) 2019.05.28

[List]

순서가 있는 데이터의 집합, 데이터 중복을 허용

List Interface 를 구현하는 ArrayList, LinkedList, Vector 는 모두 위 성질을 따름

 

[사용법]

ArrayList, LinkedList, Vector 모두 사용법이 동일하다.

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
List<Integer> al = new ArrayList<>();
al.add(1);
al.add(2);
al.add(2);    //중복허용, 순서보장
//al.remove(0);
 
System.out.print("ArrayList: ");
for(int i:al) {
    System.out.print(i+" ");
}
System.out.println();
 
List<Integer> ll = new LinkedList<>();
ll.add(1);
ll.add(2);
ll.add(2);    //중복허용, 순서보장
//ll.remove(0);
 
System.out.print("LinkedList: ");
for(int i:ll) {
    System.out.print(i+" ");
}
System.out.println();
 
List<Integer> v = new Vector<>();
v.add(1);
v.add(2);
v.add(2);    //중복허용, 순서보장
//v.remove(0);
 
System.out.print("Vector: ");
for(int i:v) {
    System.out.print(i+" ");
}
cs

[실행 결과]

ArrayList: 1 2 2
LinkedList: 1 2 2
Vector: 1 2 2

 

[차이점]

ArrayList

배열과 동일하나 저장되는 자료크기에 따라 내부적으로 배열의 크기가 늘었다 줄었다 하는 resizable array.

조회 수행속도가 빠르다.

데이터 삽입이 발생하면 삽입된 데이터 뒤에 위치하는 모든 데이터의 인덱스를 하나씩 증가시킨다(한 칸씩 민다)

데이터 삭제가 발생하면 삭제된 데이터 뒤에 위치하는 모든 데이터의 인덱스를 하나씩 감소시킨다(한 칸씩 당긴다).

 

LinkedList

자료의 주소값을 기준으로 데이터들이 연결된 형태.

삽입과 삭제의 수행속도가 빠르다.

데이터 삽입이 발생하면 삽입될 데이터 앞의 데이터는 삽입될 데이터의 주소값을,

삽입될 데이터는 삽입될 데이터 뒤에 위치한 데이터의 주소값을 참조하게 한다.

 

Vector

Thread Safe 한 ArrayList (Synchronized)

 

[ArrayList vs LinkedList 성능 비교]

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
private static long START_TIME = 0;
    private static long END_TIME = 0;
    
    public static void main(String[] args) {
        
        List<Integer> ArrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();
        
        setStartTime();
        for(int i=0; i<10000; i++) {
            ArrayList.add(0);
        }
        setEndTime();
        System.out.print("ArrayList add: ");
        printDuration();
 
        
        setStartTime();
        for(int i=0; i<10000; i++) {
            linkedList.add(0);
        }
        setEndTime();
        System.out.print("LinkedList add: ");
        printDuration();
        
        System.out.println("-------------------------------------------------");
        
        setStartTime();
        for(int i=0; i<10000; i++) {
            ArrayList.get(i);
        }
        setEndTime();
        System.out.print("ArrayList get: ");
        printDuration();
        
        
        setStartTime();
        for(int i=0; i<10000; i++) {
            linkedList.get(i);
        }
        setEndTime();
        System.out.print("LinkedList get: ");
        printDuration();
        
        System.out.println("-------------------------------------------------");
        
        setStartTime();
        Iterator<Integer> ai = ArrayList.iterator();
        while(ai.hasNext()) {
            ArrayList.remove(0);
        }
        setEndTime();
        System.out.print("ArrayList remove: ");
        printDuration();
        
        
        setStartTime();
        Iterator<Integer> li = linkedList.iterator();
        while(li.hasNext()) {
            linkedList.remove(0);
        }
        setEndTime();
        System.out.print("LinkedList remove: ");
        printDuration();
        
    }
    
    private static void setStartTime() {
        START_TIME = System.nanoTime();
    }
    
    private static void setEndTime() {
        END_TIME = System.nanoTime();
    }
    
    private static void printDuration() {
        System.out.println(END_TIME - START_TIME);
    }
cs

[실행 결과]

ArrayList add: 1049700
LinkedList add: 1224800
-------------------------------------------------
ArrayList get: 861200
LinkedList get: 42666800
-------------------------------------------------
ArrayList remove: 6322100
LinkedList remove: 1229300

 

조회(get)는 ArrayList가 상대적으로 뛰어난 성능을 보여준다.

삽입(add)/삭제(remove)는 LinkedList가 상대적으로 뛰어난 성능을 보여준다.

[ArrayList vs LinkedList 시간복잡도]

   ArrayList LinkedList
Indexing Θ(1) Θ(n)
Insert/delete at beginning Θ(n) Θ(1)
Insert/delete at end Θ(1) Θ(n)-last element is unknown
Θ(1)-last element is known
Insert/delete in middle Θ(n) search time + Θ(1)
Wasted space (average) Θ(n) Θ(n)

 

 

참고 :

https://dzone.com/articles/arraylist-vs-linkedlist-vs

http://www.nextree.co.kr/p6506/

반응형

Map은 키와 값이 한 쌍으로 이루어지는 데이터의 집합

값은 중복을 허용하나 키는 중복될 수 없음(키는 중복될 경우 덮어 씀)

Map Interface 를 구현한 HashMap, HashTable, LinkedHashMap, TreeMap 은 모두 위 규칙을 따름.

 

 

[HashMap]

순서가 보장되지 않는다.

Null을 허용한다.

Thread Safe 하지 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//순서를 보장하지 않음
//key, value 에 Null 허용
//Thread safe X
 
Map<Integer, Integer> map = new HashMap<>();
 
map.put(1180);
map.put(3175);
map.put(2160);
map.put(4190);
map.put(3170);
 
try {
    map.put(0null);
catch(NullPointerException ne) {
    System.out.println(ne.getMessage());
}
 
for(int s : map.keySet()) {
    System.out.println("person : " + s + ", height : " + map.get(s));
}
cs

[실행결과]

person : 0, height : null
person : 4, height : 190
person : 3, height : 170
person : 2, height : 160
person : 1, height : 180

1, 3, 2, 4, 3, 0 의 Key 를 순서대로 입력하였지만 순서는 지켜지지 않았다.

(혹여 정렬된 것 처럼 값이 출력된다 해도 정렬이 되어 나온게 아니다)

14번 라인에 null 값을 넣어봤지만 exception 이 찍히지 않았다 : null 허용

 

 

[HashTable]

순서가 보장되지 않는다

Null을 허용하지 않는다

Thread Safe 하다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//순서를 보장하지 않음
//key, value 에 Null 허용
//Thread safe
 
Map<Integer, Integer> map = new Hashtable<>();
 
map.put(1180);
map.put(3175);
map.put(2160);
map.put(4190);
map.put(3170);
 
try {
    map.put(0null);
catch(NullPointerException ne) {
    System.out.println(ne.getMessage());
}
 
for(int s : map.keySet()) {
    System.out.println("person : " + s + ", height : " + map.get(s));
}
cs

[실행결과]

null
person : 4, height : 190
person : 3, height : 170
person : 2, height : 160
person : 1, height : 180

HashMap 과 마찬가지로 순서가 보장되지 않았다.

14번라인에서 NullPointerException 이 발생하여 null 이라는 에러메시지를 찍고 있다.

 

 

[LinkedHashMap]

입력한 순서가 보장된다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//입력한 순서가 보장됨
//Thread safe X
 
Map<Integer, Integer> map = new LinkedHashMap<>();
 
map.put(1180);
map.put(3175);
map.put(2160);
map.put(4190);
map.put(3170);
 
for(int s : map.keySet()) {
    System.out.println("person : " + s + ", height : " + map.get(s));
}
cs

[실행결과]

person : 1, height : 180
person : 3, height : 170
person : 2, height : 160
person : 4, height : 190

put 을 한 순서(1, 3, 2, 4)대로 결과가 출력되었다.

 

 

[TreeMap]

데이터가 내부적으로 정렬되어 존재한다. (RedBlackTree 구조)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//KEY를 기준으로 정렬함(내부적으로 RedBlackTree 구조로 정렬)
//Thread safe X
 
Map<Integer, Integer> map = new TreeMap<>();
 
map.put(1180);
map.put(3175);
map.put(2160);
map.put(4190);
map.put(3170);
 
for(int s : map.keySet()) {
    System.out.println("person : " + s + ", height : " + map.get(s));
}
 
cs

[실행 결과]

person : 1, height : 180
person : 2, height : 160
person : 3, height : 170
person : 4, height : 190

오름차순 기준으로 key값을 정렬하여 출력되었다.

 

 

※ 정렬 순서를 바꾸고 싶다면?

Comparator 를 인자값으로 넘겨준다. Comparator 에 대한 설명은 이곳을 참고

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Map<Integer, Integer> map2 = new TreeMap<>(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
}); 
 
map2.put(1180);
map2.put(3175);
map2.put(2160);
map2.put(4190);
map2.put(3170);
 
for(int s : map2.keySet()) {
    System.out.println("person : " + s + ", height : " + map2.get(s));
}
cs

[실행결과]

person : 4, height : 190
person : 3, height : 170
person : 2, height : 160
person : 1, height : 180

내림차순 기준으로 key값을 정렬하여 출력되었다.

 

반응형

※ 들어가기에 앞서, Spring Framework, maven, jboss(wildfly) 환경에서 log4j설정하는 법은 이곳을 참고.

 

log4j.xml 파일을 아래와 같이 설정한다.

pom.xml 및 기타 설정은 되어있다 가정

 

[log4j.xml]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
 
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%-5p: %c - %m%n" />
        </Console>
    </Appenders>
    
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="console" />
        </Root>
    </Loggers>
 
</Configuration>
 
cs

 

위에 설정한 콘솔 로그를 출력하기 위해 아래와 같이 Sample.class 를 만들어준다.

[Sample.class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.log;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class Sample {
    
    protected static final Logger logger = LoggerFactory.getLogger(Sample.class);
    protected static final Logger fileLogger = LoggerFactory.getLogger("fileLogger");
    
    public static void main(String[] args) {
        
        logger.info("Sample.class logger print with info level");
        
        fileLogger.info("fileLogger logger print with info level");
    }
 
}
 
cs

"fileLogger 를 name 으로 하는 logger 가 없는데?" 라는 생각이 들겠지만 일단 main 메소드를 실행시켜보자.

 

[Sample.class 실행결과]

INFO : com.log.Sample - Sample.class logger print with info level
INFO : fileLogger - fileLogger logger print with info level

Exception을 찍거나, 혹은 아무것도 찍지 않을 줄 알았던 fileLogger.info(~); 가 로그를 정상적으로 찍고 있다.

이를 이해하려면 log4j 의 위계구조 (hierarchy) 에 대한 이해가 필요하다.

 

[log4j 위계구조 (hierarchy)]

1. 모든 로거의 최상위 부모는 Root 로거이다.

2. class 에서 로그를 출력하는데 사용된 logger가 log4j.xml 에 존재하지 않는다면, 부모 로거를 찾는다.

: Sample.class 9번 라인의 fileLogger 는 "fileLogger" 라는 name을 갖는 로거를 log4j.xml 에서 찾았으나, 해당 이름의 로거가 존재하지 않으므로 부모인 Root 로거를 타게된 것. (마찬가지 이유로, Sample.class 에서 8번라인의 ~.getLogger(Sample.class) 부분을 ~.getLogger("") 과 같이 바꿔도 로거는 정상출력된다)

 

 

이번엔 log4j 를 다음과 같이 수정 후, 위의 Sample.class 메인메소드를 다시 실행해보자.

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
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%-5p: %c - %m%n" />
        </Console>
    </Appenders>
    
    <Loggers>
        <Logger name="com" level="INFO" >
            <AppenderRef ref="console" />
        </Logger>
        
        <Logger name="com.log" level="INFO" >
            <AppenderRef ref="console" />
        </Logger>
        
        <Logger name="com.log.Sample" level="INFO" >
            <AppenderRef ref="console" />
        </Logger>
        
        <Root level="INFO">
            <AppenderRef ref="console" />
        </Root>
    </Loggers>
    
</Configuration>
 
cs

 

[실행 결과]

INFO : com.log.Sample - Sample.class logger print with info level
INFO : com.log.Sample - Sample.class logger print with info level
INFO : com.log.Sample - Sample.class logger print with info level
INFO : com.log.Sample - Sample.class logger print with info level
INFO : fileLogger - fileLogger logger print with info level

com.log 패키지 경로에 있는 Sample.class (Sample.class 의 1번라인 참고)는 자신의 클래스명과 일치하는 name을 가진 Logger를 찾는다.

com.log.Sample 로거의 appender 로 로그가 찍히고, 

com.log 로거의 appender 로 로그가 찍히고,

com 로거의 appender 로 로그가 찍히고,

Root 로거의 appender 로 로그가 찍혀 총 4번의 로그가 찍혔다. (fileLogger 제외)

(부모 > Root > com > com.log > com.log.Sample > 자식)

 

이번엔 아래와 같이 com.log 로거에 additivity="false" 속성을 추가해보자

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
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%-5p: %c - %m%n" />
        </Console>
    </Appenders>
    
    <Loggers>
        <Logger name="com" level="INFO" >
            <AppenderRef ref="console" />
        </Logger>
        
        <Logger name="com.log" level="INFO" additivity="false">
            <AppenderRef ref="console" />
        </Logger>
        
        <Logger name="com.log.Sample" level="INFO" >
            <AppenderRef ref="console" />
        </Logger>
        
        <Root level="INFO">
            <AppenderRef ref="console" />
        </Root>
    </Loggers>
    
</Configuration>
cs

 

[실행결과]

INFO : com.log.Sample - Sample.class logger print with info level 
INFO : com.log.Sample - Sample.class logger print with info level 
INFO : fileLogger - fileLogger logger print with info level

com.log.Sample 로거의 appender 로 로그가 찍히고,

com.log 로거의 appender 로 로그가 찍히고 끝이난다.

additivity 값을 false 로 주면 부모 로거로의 전달을 막게된다. (default 값은 true)

 

 

[실제 응용]

1. 특정 레벨의 로그는 별도의 파일로 관리

콘솔로그는 INFO 레벨 이상(INFO < WARN < ERROR...)인 경우 출력,

ERROR(ERROR < FATAL) 레벨 이상의 로그는 별도의 파일에 작성하여 관리하고자 하는 경우

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
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%-5p: %c - %m%n" />
            <ThresholdFilter level="INFO"/>
        </Console>
        
        <RollingFile name="logFile" fileName="/logs/api.log" filePattern="/logs/$${date:yyyyMM}/app_%d{yyyyMMdd}-%i.log.gz">
            <PatternLayout pattern="%-5p: %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" />
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <ThresholdFilter level="ERROR"/>    
        </RollingFile>    
    </Appenders>
    
    
    <Loggers>
        <Logger name="com" additivity="false">
            <AppenderRef ref="console" />
            <AppenderRef ref="logFile" />
        </Logger>
          
        <Root level="INFO">
            <AppenderRef ref="console" />
        </Root>
    </Loggers>
</Configuration>
 
cs

FileAppender 는 로그를 파일에 작성하기 위해,

RollingFileAppender 는 로그를 파일에 저장 + 롤아웃(백업) 하기 위해 사용한다.

이외에도 다양한 Appender 들이 지원되니 다른 appender 가 필요하다면 공식 document 를 참고하면된다.

 

TimeBasedTriggeringPolicy 는 시간에 의한 백업

SizeBasedTriggeringPolicy 는 로거 파일 용량에 의한 백업

filePattern 은 백업 파일형식

ThresholdFilter 는 appender 의 로그 레벨을 지정

 

위 설정은 아래와 같다.

com 밑의 모든 class 에서 출력하는 INFO 레벨 이상의 콘솔 로그를 %-5p: %c - %m%n 포맷으로 찍는다.

com 밑의 모든 class 에서 출력하는 ERROR 레벨 이상의 콘솔 로그를 %-5p: %m%n 포맷으로 /logs/api.log 파일에 기록한다.

용량이 100MB 초과할 경우, 혹은 하루가 지난 경우(매일) 로그 파일을 /logs/년월/app_년월일-백업시퀀스.log.gz 이름으로 압축한다.

 

 

※ logback에서의 설정은 아래와 같다

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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- doc : http://logback.qos.ch/documentation.html -->
<!-- xml example : https://mkyong.com/logging/logback-xml-example/ -->
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      </encoder>
   </appender>
 
   <appender name="FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <file>/boot_jpa_log/log_stats.log</file>
      <encoder>
         <pattern>%m%n</pattern>
      </encoder>
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>/boot_jpa_log/old/log_stats_%d{yyyyMMdd}.log</fileNamePattern>
      </rollingPolicy>   
   </appender>
   
   <logger name="com" level="DEBUG" additivity="false">
      <appender-ref ref="STDOUT" />
   </logger>
   
   <logger name="org.springframework.boot.autoconfigure" level="WARN">
      <appender-ref ref="STDOUT"/>
   </logger>
   
   <logger name="fileLogger" level="INFO" additivity="false">
      <appender-ref ref="FILE_APPENDER" />
   </logger>
   
   <root level="INFO">
      <appender-ref ref="STDOUT" />
   </root>
 
</configuration>
cs

 

 

[위와 같이 설정한 경우 Sample.class]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.sample.controller;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class Sample {
    
    protected static final Logger logger = LoggerFactory.getLogger(Sample.class);
    
    public static void main(String[] args) {
        
        logger.info("Sample.class logger print with info level");
        
        logger.error("fileLogger logger print with error level");
    }
 
}
 
cs

main 메소드를 실행시 logger.error 로 찍은 로그는 /logs/api.log 파일에 write 되어 있으며, 콘솔에선 찍히지 않는다.

 

[실행 결과 : 콘솔]

INFO : com.sample.controller.Sample - Sample.class logger print with info level

[실행 결과 : /logs/api.log 파일]

ERROR : fileLogger - fileLogger logger print with error level

 

2. 특정 로거에서 출력한 로그는 파일로 저장하기

기존의 console 로그 이외에 로거를 하나 추가로 두고,

추가한 로거를 사용하여 로그를 찍는 경우, 별도의 파일로 저장하고자 하는 경우

fileLogger 를 로거로 추가하고, 파일 어팬더를 주입.

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
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout pattern="%-5p: %c - %m%n" />
        </Console>
        
        <RollingFile name="logFile" fileName="/logs/api.log" filePattern="/logs/$${date:yyyyMM}/app_%d{yyyyMMdd}-%i.log.gz">
            <PatternLayout pattern="%-5p: %c - %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
        </RollingFile>
    </Appenders>
    
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="console" />
        </Root>
        
        <Logger name="fileLogger" level="INFO" additivity="false">
             <AppenderRef ref="logFile" />
        </Logger>
    </Loggers>
    
</Configuration>
 
cs

 

Sample.class는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.sample.controller;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class Sample {
    
    protected static final Logger logger = LoggerFactory.getLogger(Sample.class);
    protected static final Logger fileLogger = LoggerFactory.getLogger("fileLogger");
 
    public static void main(String[] args) {
        
        logger.info("Sample.class logger print with info level");
        
        fileLogger.info("fileLogger logger print with info level");
    }
 
}
 
cs

 

[실행결과 : 콘솔]

INFO : com.sample.controller.Sample - Sample.class logger print with info level

[실행결과 : /logs/api.log]

INFO : fileLogger - fileLogger logger print with info level

 

참고:

https://logging.apache.org/log4j/2.x/manual/architecture.html

https://logging.apache.org/log4j/2.x/manual/appenders.html

 

반응형

 

https://m.blog.naver.com/CommentList.nhn?blogId=sim4858&logNo=220924984480

 

반응형

스프링 빈은 Thread safe 하나,

스프링 빈 내에서의 멤버변수는 Thread safe 하지 않다.

--> race condition 을 유의해야 한다.

--> stateless 하게 개발해야 한다.

 

※ race condition : 

두 개 이상의 프로세스가 공통 자원을 병행적으로(concurrently) 읽거나 쓸 때,

공용 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 달라지는 상황을 말한다.

※ stateful :

상태 변화에 의존적인 상태를 의미.

ex)서버 session 은 stateful, 클라이언트가 토큰을 들고있는 방식은 stateless (공부중)

 

[Example]

1
2
3
4
5
6
7
8
9
10
11
@Service
public class TestServiceImpl implements TestService {
  
    private int idx = 0;
 
    @Override
    public void test(){
        System.out.println("idx : " + idx);
        idx++;
    }
}
cs

ControllerA  에서 TestService를 주입받아 test() 메소드 실행시 idx 값이 호출될 때마다 증가하며,

ControllerB 에서도 TestService를 주입받아 test() 메소드 실행시 idx 값이 호출될 때마다 증가함을 확인 할 수 있다.

 

만약 멤버변수를 단순 읽기 전용으로 사용한다면 문제되지 않지만, 위와 같이 읽기 뿐만 아닌 쓰는 용도로 사용한다면 문제가 된다.

 

이처럼 스프링 Bean 은 Default로 싱글톤 디자인패턴을 따르므로, 

멤버변수에 값을 저장(쓰는 행위)하는 식의 stateful한 설계는 피해야 한다.

(가급적 스프링 빈으로 관리되는 객체만 멤버변수로 사용하는게 좋다)

 

 

[코드 개선]

위와 같은 코드는 멤버변수를 제거하고, 메소드 내에서 지역변수로 선언 및 파라미터로 값을 들고 다니는 방식으로 수정을 하거나(권장), 빈의 scope 를 prototype 으로 주어 호출시마다 새로운 인스턴스를 생성하도록 해야 한다.

@Scope("prototype") annotation 을 class 선언 부에 달아주면 해당 객체는 싱글톤이 아닌 프로토타입 객체가 된다.

※ 싱글톤 빈 내에서 프로토타입 빈 생성을 할 경우 정상적으로 프로토타입 빈 동작이 되지 않는다(호출마다 빈이 생성되야 하나 그렇지 못함)

 

[prototype Scope를 적용한 코드]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
@Scope("prototype")
public class TestServiceImpl implements TestService, InitializingBean, DisposableBean {
  
    private int idx = 0;
 
    @Override
    public void test(){
        System.out.println("idx : " + idx);
        idx++;
    }
 
    @Override 
    public void afterPropertiesSet() throws Exception { 
        logger.info("created service! "); 
    }
 
    @Override 
    public void destroy() throws Exception { 
        logger.info("destroyed service! "); 
    }
 
}
cs

※ InitializingBean, DisposableBean 인터페이스의 afterPropertiesSet(), destroy() 메소드는 빈이 생성, 제거 될 때 각각 호출된다.

 

코드를 위와 같이 수정한 후, 이전과 동일하게

1) ControllerA  에서 TestService를 주입받아 test() 메소드 실행,

2) ControllerB 에서도 TestService를 주입받아 test() 메소드 실행한 경우 결과는 아래와 같다.

[실행 결과]

created service!
idx : 0
created service!
idx : 1

 

참고:

토비의 스프링 3.1(도서)

https://gmlwjd9405.github.io/2018/11/10/spring-beans.html

https://beyondj2ee.wordpress.com/2013/02/28/%EB%A9%80%ED%8B%B0-%EC%93%B0%EB%A0%88%EB%93%9C-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B9%88-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD/

https://gmlwjd9405.github.io/2018/11/10/spring-beans.html

 

 

반응형

원래 객체의 기능을 다른 객체가 대신 처리하도록 설계하는 패턴

AOP 등에 사용되는 패턴

 

 

Proxy 기본 패턴

[Subject interface]

1
2
3
public interface Subject {
    public String action();
}
cs

 

[RealSubject class]

실제 사용하는 객체

1
2
3
4
5
6
7
8
public class RealSubject implements Subject{
 
    @Override
    public String action() {
        return "Real Subject action()";
    }
}
 
cs

 

[Proxy class]

프록시 객체.

내부적으로 RealSubject 객체를 생성하여 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Proxy implements Subject {
    
    RealSubject realSubject;
    
    @Override
    public String action() {
        if(realSubject == null) {
            realSubject = new RealSubject();
        }
        return realSubject.action();
    }
}
 
cs

 

[Client class]

1
2
3
4
5
6
7
8
9
public class Client {
 
    public static void main(String[] args) {
        Subject subject = new Proxy();
        System.out.println(subject.action());
    }
 
}
 
cs

 

 

Proxy의 실제 사용

[AOP 흉내내기]

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
public class Proxy implements Subject {
    
    RealSubject realSubject;
    
    @Override
    public Object action() {
        if(realSubject == null) {
            realSubject = new RealSubject();
        }
        
        preProcess();
        Object result = realSubject.action();
        postProcess();
        
        return result;
    }
    
    private void preProcess() {
        System.out.println("선행작업");
    }
    
    private void postProcess() {
        System.out.println("사후작업");
    }
}
 
cs

 

[실제 AOP 소스]

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
49
50
51
52
53
54
55
56
57
58
@Component
@Aspect
public class ControllerAOP {
 
    protected static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);
    private final String CANNOT_PRINT_INPARAM = "{IN_PARAMS LENGTH IS TOO LONG TO PRINT}";
    private final String CANNOT_PRINT_OUTPARAM = "{OUT_PARAMS LENGTH IS TOO LONG TO PRINT}";
    private final String CANNOT_PRINT_VALUE = "VALUE LENGTH IS TOO LONG TO PRINT";
    private final String NOTHING_TO_PRINT = "RETURN TYPE IS VOID";
    
    @Around("execution(public void com.sample..*Controller.*(..))")
    public void voidAround(ProceedingJoinPoint pjp) {
        
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
 
        String inputParam = null;
        
        for (Object obj : pjp.getArgs()) {
            if (obj instanceof Map) {
                inputParam = obj.toString();
            } else if (obj instanceof String){
                inputParam = (String)obj;
            }
        }
 
        long start = System.currentTimeMillis();
 
        String controller = (String) pjp.getTarget().getClass().getSimpleName();
 
        String path = request.getRequestURI();
        String addr = request.getRemoteAddr();
        int port = request.getRemotePort();
 
        logger.info("##########################################################################");
        logger.info("# REQUEST | CONTROLLER = {} | METHOD = {} | REMOTEADDR = {} | PORT = {} | IN_PARAMS = {}",
                controller, path, addr, port, inputParam==null?"":(inputParam.length()>=1500?this.CANNOT_PRINT_INPARAM:inputParam));
        logger.info("##########################################################################");
        
        String result_msg = "success";
        
        try {
            pjp.proceed();
            result_msg = "success";
        } catch (APIException e) {
            result_msg = "fail";
        } catch (Throwable e) {
            result_msg = "fail";
        } finally {
            long end = System.currentTimeMillis();
            logger.info("##########################################################################");
            logger.info("# RESPONSE | CONTROLLER = {} | METHOD = {} | RESULT = {} | REMOTEADDR = {} | PORT = {} | TIME = {} ms | IN_PARAMS = {} | OUT_PARAMS = {}"
                    controller, path, result_msg, addr, port, end-start, inputParam==null?"":(inputParam.length()>=1500?this.CANNOT_PRINT_INPARAM:inputParam), 
                    this.NOTHING_TO_PRINT);
            logger.info("##########################################################################");
        }
 
 
    }
cs

위 AOP 코드 요약 : joinpoint(클라이언트에서 호출되는 모든 메소드) 중에서 pointcut으로(execution) 잡은 특정 메소드만 잡아 공통 관심사로 (advice(@Around)) 로 처리.

 

실제 호출되는 메소드를 인자(ProceedingJoinPoint)로 받아 프록시 패턴과 같이 처리한다.

pjp.proceed() 가 실제 메소드 실행부이며,

pjp.proceed() 위 아래로 로그를 처리하는 코드가 있다.

 

 

참고 :

도서 Head first design patterns,

https://effectiveprogramming.tistory.com/entry/Proxy-%ED%8C%A8%ED%84%B4%EA%B3%BC-%EA%B7%B8-%ED%99%9C%EC%9A%A9

 

반응형

 

https://velopert.com/2350

 

https://mangkyu.tistory.com/55?category=761303

 

https://mangkyu.tistory.com/56

 

https://www.sauru.so/blog/basic-of-oauth2-and-jwt/

반응형

+ Recent posts