JAR

jar 란?

  • 일종의 자바 프로젝트 압축 파일
  • ZIP 파일 압축 알고리즘을 기반으로 만들어짐 >> 반디집, 알집과 같은 zip 프로그램과 호환 가능
  • JAR 파일은 웹브라우저에서 빠르게 다운로드할 수 있도록, 자바 애플릿을 위한 클래스, 이미지 및 사운드 파일들을 하나의 파일에 압축하여 담고 있는 파일이다.
  • 사용자의 요청에 의해 웹페이지의 일부로 들어오는 애플릿에는 여러개의 파일들이 담겨 있을 수 있는데, 각각은 웹페이지와 함께 다운로드 되어야 한다. 이 때 그 파일들을 하나의 파일에 압축하면 다운로드에 소요되는 시간이 절약된다.
  • 자바로 개발한 여러 클래스 파일들 또는 패키지 파일이 있을 때, 이를 하나로 묶으면 그 클래스들을 참조하기도 편하고, 다운 받기도 쉽다.
  • 자바 어플리케이션 프로그램을 개발 후 하나의 파일로 묶어서 실행하게 해준다.
  • JAR 로 묶어서 배포하게 되면, 경로나 파일의 위치에 상관없이 프로그램의 실행이 가능하다.

jar 옵션

-client

자바 HotSpot Client VM을 선택한다. (디폴트 값이다)

-server

자바 HotSpot Server VM을 선택한다.

-classpath (-cp)

참조할 클래스 파일 패스를 지정하는데, jar파일, zip파일, 클래스파일의 디렉터리 위치를 기술한다.

각 클래스파일 패스는 콜론(:)을 통해서, 분리시켜 기술한다.

자바VM은  자바프로그램을 로딩시, -classpath로 지정된 클래스 패스나, java플래폼이 설치된, 운영체제에서의 환경변수로 지정된,

클래스패스를 통해서, 클래스 파일들을 참조하게 된다.

-D <property name>=<property value>

시스템의 property 값을 설정한다.

ex) java -Djava.library.path=. HelloWorld

자바의 시스템 property(속성)중 “java.library.path”값을 “.” (현재디렉터리)로 지정해서, HelloWorld 실행시켜라는 의미 이다.

위와같이 자바VM에 지정된 속성을 실행시 -D옵션을 사용해서, 변경, 지정할수 있다.

-jar 파일이름

jar파일로 압축되어져 있는 자바 프로그램을 실행시킨다.

클래스 파일이름 대신 jar파일을 사용해서, 압축되어져 있는 자바 프로그램을 실행시킬수 있는데, 위프로그램이 제대로 실행되어지기

위해서, Jar파일안의 manifest라는 텍스트 파일에 Main-Class:classname 같은 형태의 텍스트 라인이 포함되어 있어야 한다.

그리고, 여기에 기술된 classname은 main함수를 포함하고 있는 클래스 이름이 되어야 한다.

-verbose

자바프로그램 실행되어지는 정보를 화면에 출력해준다.

-verbose:class

로딩되어지는 각클래스들의 정보를 화면에 출력한다.

-verbose:gc

garbage collection 이벤트를 화면에 출력한다.

-verbose:jni

native 함수들과 다른 자바 native 인터페이스 사용에 대한 정보를 출력한다.

-version

현재 JVM의 버젼 정보만 출력한다.

-showversion

현재 JVM의 버젼정보를 출력한다.

java -showversion HelloWolrd 와 같이 자바 프로그램을 실행시키면서, 자바 버젼정보를 출력할수 있다.

-X

비표준 자바옵션 리스트를 화면에 출력해준다.

-Xms, -Xmx

자바를 구동시, JVM이 사용가능한 최대 메모리 사이즈를 변경합니다.

JVM이 자바프로그램을 구동하기 위해, 초기설정된 메모리사이즈는 64M입니다.

사용 방법은 다음과 같습니다

java -Xms <초기힙사이즈> -Xmx <최대힙사이즈>

예를들어, Hello.class 자바 프로그램을 시작시, 256M(메가)의 힙사이즈를 할당하며, 최대 512M의 힙사이즈를 할당받고 싶다면, 

다음과 같이 합니다.

java -Xms256m -Xmx512m Hello

-XX:PermSize,  -XX:MaxPermSize

클래스 메타 정보 메모리 (Xms, Xmx 메모리와 별도로 관리된다.

-XX:PermSize=64m -XX:MaxPermSize=256m

intelli j 에서 jar 파일 생성

terminal 에서 jar 파일 생성

  1. 리눅스 환경에서 java(jar)를 데몬처럼 실행
    • java -jar jar파일명.jar
    • 사용자가 로그아웃시 프로그램 종료 됨
  2. 사용자가 로그아웃해도 백그라운드로 실행되게 하는 명령어
    • $ nohup java -jar jar파일명.jar
  3. 프로세스 종료
    • 찾기 : ps -ef | grep ‘jar파일명’
    • pid
      • sudo netstat -ltnp
    • 종료 : kill -9 (pid)

STREAM은 사용시 주의!

다량의 데이터 처리 작업을 지원하고자 자바8에 스트림 API가 추가되었다.
이 API가 제공하는 추상 개념 중 핵심은 두 가지다.

  • 스트림(stream)은 데이터 원소의 유한 혹은 무한 시퀀스(sequence)를 뜻한다.
  • 스트림 파이프라인(stream pipleline)은 이 원소들로 수행하는 연산 단계를 표현하는 개념이다.

스트림 파이프라인은 소스 스트림에서 시작해 종단 연산(terminal operation)으로 끝나며, 그 사이에 하나 이상의 중간 연산(intermediate operation)이 있을 수 있다.

각 중간 연산들은 스트림을 어떠한 방식으로 변환(transform)하여 다른 스트림으로 변환한다.

또한 스트림 파이프라인은 지연 평가(lazy evaluation)이 되며, 평가는 종단 연산이 호출될 때 이뤄진다. 종단 연산에 쓰이지 않는 데이터 원소는 계산이 쓰이지 않는다.

스트림을 제대로 사용하면 프로그램이 짧고 깔끔해지지만, 잘못 사용하면 읽기 어렵고 유지보수도 힘들어 진다.

예를 살펴보자 (이펙티브 자바 참고).

아래는 아나그램관련 프로그램이다.
⇒ 사전 파일에서 단어를 읽어 사용자가 지정한 문턱값(minGroupSize)보다 원소 수가 많은 아나그램그룹을 출력한다.

아나그램 : 단어나 문장을 구성하고 있는 문자의 순서를 바꾸어 다른 단어나 문장을 만드는 놀이

public class Anagrams {
    public static void main(String[] args) throws FileNotFoundException {
        File dictionary = new File(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);

        Map<String, Set<String>> groups = new HashMap<>();
        try(Scanner s = new Scanner(dictionary)){
            while(s.hasNext()){
                String word = s.next();
                groups.computeIfAbsent(alphabetize(word),
                        (unused) -> new TreeSet<>()).add(word);
            }
        }

        for(Set<String> group : groups.values()){
            if(group.size() >= minGroupSize)
                System.out.println(group.size() + " : " + group);
        }
    }

    private static String alphabetize(String s){
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

computeIfAbsent (참고)

이 메소드는 map 안에 키가 있는지 찾은 다음, 있으면 단순히 그 키에 매핑된 값을 반환한다.
키가 없으면 건네진 함수 객체를 계산하여 그 키에 해당하는 값으로 매핑한 후 계산된 값을 반환한다.

그러면 이제 위의 코드를 같은 일을 하는 스트림을 사용한 코드로 변경해보자. (과한 스트림을 사용)

public class AnagramsStream {
    public static void main(String[] args)throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        try(Stream < String > words = Files.lines(dictionary)) {
            words
                .collect(groupingBy(word - > word
                    .chars()
                    .sorted()
                    .collect(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append)
                    .toString()))
                .values()
                .stream()
                .filter(group - > group.size() >= minGroupSize)
                .map(group - > group.size() + " : " + group)
                .forEach(System.out::println);
        }
    }
}

위의 코드는 확실히 짧지만 읽기는 어렵다. 이처럼 스트림을 과용하면 프로그램을 읽거나 유지보수하기 어려워진다.

다행히 절충 지점이 있는데 아래와 같이 스트림을 적당히 사용하면 코드가 짧아질 뿐만아니라 명확해지기까지 한다.

public class AnagramsEasyStream {
    public static void main(String[] args)throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        try(Stream < String > words = Files.lines(dictionary)) {
            words
                .collect(groupingBy(word - > alphabetize(word)))
                .values()
                .stream()
                .filter(group - > group.size() >= minGroupSize)
                .forEach(group - > System.out.println(group.size() + ": " + group));
        }
    }
    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

결론

스트림을 처음 사용하게 되면 모든 반복문을 스트림으로 바꾸고 싶은 유혹이 일게 된다고 한다. (나 또한 그랬다.)

하지만 스트림으로 바꾸는 게 가능할지라도 코드 가독성과 유지보수 측면에서 손해를 볼 수 있기 때문에 기존 코드는 스트림을 사용하도록 리팩토링하되, 새 코드가 더 나아 보일때만 반영 해야 한다.

꼭 for문, stream을 써야하는 경우가 정해져 있는 것은 아니지만 특정상황에서 for문 또는 stream을 써야만하는 경우가 존재한다.

for문을 써야하는 경우(stream에서 할 수 없는 것들)

  • 코드블록안에서 지역변수를 수정해야할 때
    (람다에서는 final이거나 사실상 final인 변수만 읽을 수 있다. 지역변수를 수정하는 건 불가능하다.)
  • 중간에 return 문으로 메서드를 빠져나가는 경우나, break continue 문으로 블록 바깥의 반복문을 종료하거나 건너띄는 경우.

stream을 써야하는 경우(stream과 궁합이 맞는 경우)

  • 원소들의 시퀀스를 일관되게 변환한다.
  • 원소들의 시퀀스를 필터링한다.
  • 원소들의 시퀀스를 하나의 연산을 사용해 결합한다. (더하기, 연결하기, 최솟값 구하기 등)
  • 원소들의 시퀀스를 컬렉션에 모은다.
  • 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는다.
람다에서 매개변수 이름 지을때 주의!!

람다에서는 타입 이름을 자주 생략하므로 매개변수 이름을 잘 지어야 스트림 파이프라인의 가독성이 유지된다.

RestFul API

REST 구성

쉽게 말해 REST API는 다음의 구성으로 이루어져있습니다. 자세한 내용은 밑에서 설명하도록 하겠습니다.

  • 자원(RESOURCE) – URI
  • 행위(Verb) – HTTP METHOD
  • 표현(Representations)

REST 의 특징

1) Uniform (유니폼 인터페이스)

Uniform Interface는 URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍처 스타일을 말합니다.

2) Stateless (무상태성)

REST는 무상태성 성격을 갖습니다. 다시 말해 작업을 위한 상태정보를 따로 저장하고 관리하지 않습니다. 세션 정보나 쿠키정보를 별도로 저장하고 관리하지 않기 때문에 API 서버는 들어오는 요청만을 단순히 처리하면 됩니다. 때문에 서비스의 자유도가 높아지고 서버에서 불필요한 정보를 관리하지 않음으로써 구현이 단순해집니다.

3) Cacheable (캐시 가능)

REST의 가장 큰 특징 중 하나는 HTTP라는 기존 웹표준을 그대로 사용하기 때문에, 웹에서 사용하는 기존 인프라를 그대로 활용이 가능합니다. 따라서 HTTP가 가진 캐싱 기능이 적용 가능합니다. HTTP 프로토콜 표준에서 사용하는 Last-Modified태그나 E-Tag를 이용하면 캐싱 구현이 가능합니다.

4) Self-descriptiveness (자체 표현 구조)

REST의 또 다른 큰 특징 중 하나는 REST API 메시지만 보고도 이를 쉽게 이해 할 수 있는 자체 표현 구조로 되어 있다는 것입니다.

5) Client – Server 구조

REST 서버는 API 제공, 클라이언트는 사용자 인증이나 컨텍스트(세션, 로그인 정보)등을 직접 관리하는 구조로 각각의 역할이 확실히 구분되기 때문에 클라이언트와 서버에서 개발해야 할 내용이 명확해지고 서로간 의존성이 줄어들게 됩니다.

6) 계층형 구조

REST 서버는 다중 계층으로 구성될 수 있으며 보안, 로드 밸런싱, 암호화 계층을 추가해 구조상의 유연성을 둘 수 있고 PROXY, 게이트웨이 같은 네트워크 기반의 중간매체를 사용할 수 있게 합니다.

REST API 디자인 가이드

REST API 설계 시 가장 중요한 항목은 다음의 2가지로 요약할 수 있습니다.

첫 번째, URI는 정보의 자원을 표현해야 한다.
두 번째, 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.

REST API 중심 규칙


1) URI는 정보의 자원을 표현해야 한다. (리소스명은 동사보다는 명사를 사용)

    GET /members/delete/1

위와 같은 방식은 REST를 제대로 적용하지 않은 URI입니다. URI는 자원을 표현하는데 중점을 두어야 합니다. delete와 같은 행위에 대한 표현이 들어가서는 안됩니다.

2) 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)로 표현

위의 잘못 된 URI를 HTTP Method를 통해 수정해 보면

    DELETE /members/1

으로 수정할 수 있겠습니다.
회원정보를 가져올 때는 GET, 회원 추가 시의 행위를 표현하고자 할 때는 POST METHOD를 사용하여 표현합니다.

회원정보를 가져오는 URI

    GET /members/show/1     (x)
    GET /members/1          (o)

회원을 추가할 때

    GET /members/insert/2 (x)  - GET 메서드는 리소스 생성에 맞지 않습니다.
    POST /members/2       (o)

[참고]HTTP METHOD의 알맞은 역할
POST, GET, PUT, DELETE 이 4가지의 Method를 가지고 CRUD를 할 수 있습니다.

METHOD역할
POSTPOST를 통해 해당 URI를 요청하면 리소스를 생성합니다.
GETGET를 통해 해당 리소스를 조회합니다. 리소스를 조회하고 해당 도큐먼트에 대한 자세한 정보를 가져온다.
PUTPUT를 통해 해당 리소스를 수정합니다.
DELETEDELETE를 통해 리소스를 삭제합니다.

다음과 같은 식으로 URI는 자원을 표현하는 데에 집중하고 행위에 대한 정의는 HTTP METHOD를 통해 하는 것이 REST한 API를 설계하는 중심 규칙입니다.

URI 설계 시 주의할 점


1) 슬래시 구분자(/)는 계층 관계를 나타내는 데 사용

    http://restapi.example.com/houses/apartments
    http://restapi.example.com/animals/mammals/whales

2) URI 마지막 문자로 슬래시(/)를 포함하지 않는다.

URI에 포함되는 모든 글자는 리소스의 유일한 식별자로 사용되어야 하며 URI가 다르다는 것은 리소스가 다르다는 것이고, 역으로 리소스가 다르면 URI도 달라져야 합니다. REST API는 분명한 URI를 만들어 통신을 해야 하기 때문에 혼동을 주지 않도록 URI 경로의 마지막에는 슬래시(/)를 사용하지 않습니다.

    http://restapi.example.com/houses/apartments/ (X)
    http://restapi.example.com/houses/apartments  (0)

3) 하이픈(-)은 URI 가독성을 높이는데 사용

URI를 쉽게 읽고 해석하기 위해, 불가피하게 긴 URI경로를 사용하게 된다면 하이픈을 사용해 가독성을 높일 수 있습니다.

4) 밑줄(_)은 URI에 사용하지 않는다.

글꼴에 따라 다르긴 하지만 밑줄은 보기 어렵거나 밑줄 때문에 문자가 가려지기도 합니다. 이런 문제를 피하기 위해 밑줄 대신 하이픈(-)을 사용하는 것이 좋습니다.(가독성)

5) URI 경로에는 소문자가 적합하다.

URI 경로에 대문자 사용은 피하도록 해야 합니다. 대소문자에 따라 다른 리소스로 인식하게 되기 때문입니다. RFC 3986(URI 문법 형식)은 URI 스키마와 호스트를 제외하고는 대소문자를 구별하도록 규정하기 때문이지요.

    RFC 3986 is the URI (Unified Resource Identifier) Syntax document

6) 파일 확장자는 URI에 포함시키지 않는다.

    http://restapi.example.com/members/soccer/345/photo.jpg (X)

REST API에서는 메시지 바디 내용의 포맷을 나타내기 위한 파일 확장자를 URI 안에 포함시키지 않습니다. Accept header를 사용하도록 합시다.

    GET / members/soccer/345/photo HTTP/1.1 Host: restapi.example.com Accept: image/jpg

— 리소스 간의 관계를 표현하는 방법


REST 리소스 간에는 연관 관계가 있을 수 있고, 이런 경우 다음과 같은 표현방법으로 사용합니다.

    /리소스명/리소스 ID/관계가 있는 다른 리소스명

    ex)    GET : /users/{userid}/devices (일반적으로 소유 ‘has’의 관계를 표현할 때)

만약에 관계명이 복잡하다면 이를 서브 리소스에 명시적으로 표현하는 방법이 있습니다. 예를 들어 사용자가 ‘좋아하는’ 디바이스 목록을 표현해야 할 경우 다음과 같은 형태로 사용될 수 있습니다.

    GET : /users/{userid}/likes/devices (관계명이 애매하거나 구체적 표현이 필요할 때)

— 자원을 표현하는 Colllection과 Document


Collection과 Document에 대해 알면 URI 설계가 한 층 더 쉬워집니다. DOCUMENT는 단순히 문서로 이해해도 되고, 한 객체라고 이해하셔도 될 것 같습니다. 컬렉션은 문서들의 집합, 객체들의 집합이라고 생각하시면 이해하시는데 좀더 편하실 것 같습니다. 컬렉션과 도큐먼트는 모두 리소스라고 표현할 수 있으며 URI에 표현됩니다. 예를 살펴보도록 하겠습니다.

    http:// restapi.example.com/sports/soccer

위 URI를 보시면 sports라는 컬렉션과 soccer라는 도큐먼트로 표현되고 있다고 생각하면 됩니다. 좀 더 예를 들어보자면

    http:// restapi.example.com/sports/soccer/players/13

sports, players 컬렉션과 soccer, 13(13번인 선수)를 의미하는 도큐먼트로 URI가 이루어지게 됩니다. 여기서 중요한 점은 컬렉션은 복수로 사용하고 있다는 점입니다. 좀 더 직관적인 REST API를 위해서는 컬렉션과 도큐먼트를 사용할 때 단수 복수도 지켜준다면 좀 더 이해하기 쉬운 URI를 설계할 수 있습니다.