구글 플레이스토어 앱 리뷰를 수집하여, 수집한 리뷰데이터 KOMORAN 라이브러리를 통해 명사만 추출하여 해당 앱들의 키워드를 worldCloud로 뿌려주는 기능을 개발하게 되었다. 

 

해당 기능을 개발하면서 겪었던 오류나 개발하면서 알게되었던 내용을 정리해보고자 한다.

 

 

1. 셀레니움 설치 전 Chrome 버전 확인

: Chrome 오른쪽 상단 ...  → 설정 → Chrome 정보에서 버전 확인 

2. Chrome Driver 설치하기

 

ChromeDriver - WebDriver for Chrome - Downloads

Current Releases If you are using Chrome version 115 or newer, please consult the Chrome for Testing availability dashboard. This page provides convenient JSON endpoints for specific ChromeDriver version downloading. For older version of Chrome, please see

chromedriver.chromium.org

ChromeDriver 홈페이지에서 위에서 확인한 자신의 Chrome버전과 맞는 Driver를 설치해줘야 한다.

 

 

3. pom.xml에 라이브러리 추가 (url : https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java)

<dependency>
	<groupId>org.seleniumhq.selenium</groupId>
	<artifactId>selenium-java</artifactId>
	<version>3.141.59</version>
</dependency>

 

4. 구글 플레이스토어 앱 리뷰 크롤링 하기

내가 수집하고자 하는 데이터는 다음과 같았다. ( 사용자 이름, 리뷰내용, 별점, 좋아요 수, 작성일자 )

 

구글플레이 스토어에서 앱에 대한 리뷰가 최대 3개밖에 보이지 않아서,  "모든 리뷰 보기" 버튼 클릭하는 코드를 추가해야했다.

 

 

F12(개발자 도구)를 누르면 해당 페이지의 HTML 코드를 볼 수 있어서 웹 페이지가 어떻게 구성되어 있는지 쉽게 파악할 수 있다.

 

4-1) 구글플레이스토어 웹 페이지 띄우기

public void getReivew() throws InterruptedException {
		
		System.setProperty(chromeDriverName, chromeDrvierPath);
		//크롬 드라이버 셋팅 (드라이버 설치한 경로 입력)
		
		ChromeOptions options = new ChromeOptions();
		options.addArguments("--no-sandbox")
			.addArguments("--disable-dev-shm-usage")
			.addArguments("--disable-blink-features=AutomationControlled");
		
		
		driver = new ChromeDriver(options);
		driver.get(url);    
 }

 

4-2) 리뷰 모두 보기 버튼을 클릭 했을 때, 동적 페이지 형식으로 되어있어서 모든 리뷰를 가져오기 위해서는 스크롤을 통해 맨 아래로 내리는 작업을 해줘야했다.

 

 scrollTo 함수를 사용했을 때, 리뷰 모달창이 아닌 뒤에 있는 페이지가 아래로 내려가 포커싱을 모달창에 잡아주는 과정에서 많은 시간을 소요했다.😂

 

WebElement prev_element = null ;
			
while(true) {
				
		WebElement element = driver.findElement(By.xpath("//*[@id=\"yDmH0d\"]/div[4]/div[2]/div/div/div/div/div[2]/div/div[2]/*[last()]"));
					
		if(element.equals(prev_element)) break;
					
		JavascriptExecutor.executeScript("arguments[0].scrollIntoView(true);", element);
		Thread.sleep(4000);
					
		prev_element = element;
}

i) 로딩된 리뷰 모달창에 사용자의 마지막 리뷰에 해당하는 xpath를 웹페이지에서 가져온 뒤,  해당 element 값이 prev_element 값과 동일하다면 웹 페이지에 모든 리뷰를 로딩했음을 의미하므로 종료

ii) JacascriptExecutor 를 이용해 해당 element 위치까지 스크롤를 내려주는 과정을 반복

 

[위 소스코드 결과]

 

 

4-3) 앱 리뷰 데이터 파싱해서, 내가 원하는 데이터로 가공해 적재하기

public void dataParsing(WebDriver driver, String appName) {
		List<WebElement> list = driver.findElements(By.className("RHo1pe"));

		DataMap map;
		
		try {
        
			for(int i=0;i<list.size();i++) {
				String[] str = list.get(i).getText().split("\\n");
				
				map = new DataMap();
				map.put("appName", appName);
				map.put("userName", str[0]);
				map.put("contents", str[3]);
		
				String score = list.get(i).findElement(By.xpath("//*[@id=\"yDmH0d\"]/div[4]/div[2]/div/div/div/div/div[2]/div/div[2]/div[38]/header/div[2]/div")).getAttribute("aria-label")
						.replaceAll("[^0-9]", "");
				map.put("score", Integer.parseInt(score.substring(score.length()-1)));
			
				String thumbsUp = str[4].replaceAll("[^0-9]", "");
				thumbsUp = thumbsUp.length()==0 ? "0" : thumbsUp;
				map.put("thumbsUp", Integer.parseInt(thumbsUp));

				map.put("cretDt", compareFormat.parse(str[2]));
				
				googlePlayStoreReviewMapper.insertReviewData(map);
			}
			
		} catch (ParseException e) {
			// catch
		}
	}

 

[앱 리뷰 데이터 파싱 결과]

 

 

## 2023-06-15 추가 

1) 매번 모든 리뷰를 파싱하면 비효율 적이므로, 최신 순으로 정렬 후 앱에 해당하는 마지막 리뷰일자를 찾아 그 이후 날짜에 해당하는 리뷰만 파싱해 올 수 있도록 코드를 수정했다.😊

'정리 > Java' 카테고리의 다른 글

ListIterator  (0) 2021.10.20

리눅스 환경에서 새로운 패키지를 설치할 때에 "yum을 통해 손쉽게 다운로드할 수 있지만,

현재 구축하려는 서버는 사내망에 있어서 인터넷이 불가능한 환경이라 yum명령어를 사용할 수 없었습니다..

 

yum을 사용하지 못할경우에는 설치하려는 rpm 파일을 하나씩 설치해야 하는데,

해당 rpm 파일을 설치하기 위해서는 이에 해당하는 의존성 패키지 또한 같이 설치해줘야 하는 번거로움이 존재합니다.

 

인터넷이 안 되는 서버에 패키지를 쉽게 설치하는 방법은

인터넷이 되는 환경의 서버에서 yumdownloader를 사용해서 필요한 패키지. rpm파일을 다운로드하는 것입니다.

 

yumdownloader 명령어에 --resolve 옵션을 추가하면 의존성 있는 패키지도 같이 다운로드할 수 있습니다.

# yumdownloader --resolve [패키지 명]

 

해당. rpm 파일이 다운됐으면 인터넷이 안 되는 서버에 옮긴 후,

# rpm -ivh [패키지 파일명].rpm

명령어를 통해, 패키지를 설치합니다.

 

하지만, 이상하게도 패키지를 설치하는 도중 다른 패키지가 없어 설치가 안된다는 에러 문구를 발견할 수 있는데.. 🤣

 

 

구글링 해본 결과,

yumdownloader 명령어를 통해 패키지를 다운로드할 때 이미 서버에 설치된 dependency의 경우 다운로드를 하지 않는다고 하더라구요..ㅠㅠㅠ

그래서 다운받을 rpm을 폐쇄망 서버에 옮겨서 설치했을 때 위와 같은 오류가 발생하는 것이었습니다..

 

그래서 저는 repotrack을 사용해 rpm패키지를 다운로드하였습니다.

repotrack은 yumdownloader --resolve과 매우 유사하지만, yumdownloader는 종속성을 해결할 때 이미 설치되어 있으면 해당 종속성을 설치하지 않고 건너뛰고 repotrack의 경우 모든 종속성을 다운로드할 수 있는 차이점이 있습니다.

 

repotrack -p [다운로드 파일 경로] [다운받을 패키지 명]

해당 명령어를 통해 모든 종속성 패키지와 함께 rpm을 다운받을 수 있었습니다!

 

repotrack으로도 위와 동일하게 패키지가 없어 설치가 안된다는 에러 문구가 보일 경우에는 

yum install [패키지 명] --downloadonly --downloaddir=[다운받을 경로]

로 설치해보세요!

Centos7에서 docker 최신버전 설치 후, docker run 명령어를 치면 아래와 같은 오류가 발생했다.

- 서버 정보 : Centos7

- 설치한 docker version : 18.09.3

docker: Error response from daemon: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "/var/lib/docker/containers/241adecbff77a64f06883d9fe12c346d8928e8061f13ac5681e0b9990f71b3af/resolv.conf" to rootfs at "/etc/resolv.conf": possibly malicious path detected -- refusing to operate on /etc/resolv.conf: unknown.

 

 

구글링한 결과, Centos7버전대에서 해당 에러가 빈번히 발생하고 있는 걸 확인할 수 있었고, issue solved된 docker 버전으로 재설치했다.

 

해결방법

1) docker version : 19.03.1으로 재설치

# yum install docker-ce-19.03.1 \
                  docker-ce-cli-19.03.1 \
                  containerd.io
                  
                  
# systemctl start docker
# systemctl enable docker

--> 해당 버전으로 재설치했으나, 위와 동일한 오류 발생

 

 

2) docker version : 19.03.4으로 재설치

# yum install docker-ce-19.03.4 \
                  docker-ce-cli-19.03.4 \
                  containerd.io-1.3.7-3.1.el7
                  
                  
# systemctl start docker
# systemctl enable docker

--> 오류 해결!!

 

1. CMAK를 설치하게 된 이유

Kafka Manager는 GUI 기반 카프카 관리도구이다.

배치를 돌리면서 consumer하는 속도보다 producer 하는 속도가 더 빨라 설정된 kafka 메모리 초과로 배치가 제대로 실행되지 않았던 경험이있어, 카프카 모니터링 툴을 찾던 중 오픈소스인 CMAK를 설치하기로 했다.

 

카프카 모니터링 툴은 CMAK외에도 Kafdrop, Burrow 등 여러 오픈소스가 존재하지만,

설치가 간단하고, GUI로 토픽을 생성 및 변경할 수 있으면 좋을 것 같아 CMAK를 선택했다.

 

CMAK의 주요 기능

CMAK에서 제공하는 기능은 다음과 같다.

1. Kafka Cluster 관리

2. Consumer Lag 관리

3. GUI로 토픽 생성 및 변경

4. 파티션 추가

 

 

2. CMAK 설치

설치할 서버환경

  • CentOS 7

CMAK 설치 전, 기본 환경

  • JDK 11 이상
  • kafka 0.8 이상

 

1) tar.gz 파일 다운로드

# wget https://github.com/yahoo/CMAK/archive/refs/tags/3.0.0.6.tar.gz
# tar -zxvf 3.0.0.6.tar..gz

 

2) 현재 서버는 jdk1.8이지만 CMAK는 최소 JDK11 이상이기 때문에 sbt 파일을 수정했다.

- 1)에서 압축을 푼 파일로 이동하면 sbt 파일이 있다.

# vi sbt


[sbt]
-- 35번째 java_cmd 경로를 설치한 jdk11 위치로 변경

declare sbt_jar sbt_dir sbt_create sbt_version sbt_script sbt_new
declare sbt_explicit_version
declare verbose noshare batch trace_level

# declare java_cmd="java"
declare java_cmd="/usr/lib/jvm/jdk-11/bin/java" --이렇게!
declare sbt_launch_dir="$HOME/.sbt/launchers"
declare sbt_launch_repo

 

 

3) 빌드

# ./sbt clean dist

- 설치경로/CMAK-3.0.0.6/target/universal/cmak-3.0.0.6 생성된걸 확인 할 수 있다.

 

4) cofing 파일 수정

# cd /빌드한 파일 경로/confi/

-- application.conf 파일 수정

# Settings prefixed with 'kafka-manager.' will be deprecated, use 'cmak.' instead.
# https://github.com/yahoo/CMAK/issues/713
# kafka-manager.zkhosts="kafka-manager-zookeeper:2181"
kafka-manager.zkhosts="localhost:2181"
kafka-manager.zkhosts=${?ZK_HOSTS}
# cmak.zkhosts="kafka-manager-zookeeper:2181"
cmak.zkhosts="localhost:2181"
cmak.zkhosts=${?ZK_HOSTS}

 

5) 실행

# cd /app/kafkaManager/CMAK-3.0.0.6/target/univeral/cmak-3.0.0.6
# bin/cmak -Dhttp.port=9003 -java-home /usr/lib/jvm/jdk-11

- 자바버전을 11으로 잡아주기 위해 JAVA_HOME을 따로 잡아주었다.

- 기본 포트는 9000번이지만, 옵션을 통해 9003으로 변경해주었다. 

 

 

3. 실행결과 확인

 

이펙티브 자바를 공부하면서 플라이웨이트 패턴이 나와 개념 정리 및 실습을 해보려고 한다.

 

1. 플라이웨이트 패턴(Flyweight Pattern)이란?


동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하여 사용하도록 하여 메모리 사용량을 최소화하는 디자인 패턴이다.

즉, 자주 변하는 속성과 변하지 않는 속성을 분리하고, 변하지 않는 속성은 재사용하여 메모리 사용을 줄이는 방식이다.

 

 

2. Flyweight Pattern의 구성


  • Flyweight : 공유에 사용할 클래스
  • FlyweightFactory : Flyweight 인스턴스를 생성 또는 공유
  • Client : Flyweight : 해당 패턴의 사용자

 

3. 실습


  • Shape (공유에 사용할 클래스들의 인터페이스)
public interface Shape {
	public void draw();
}

 

  • Circle (인터페이스 내용 및 필요한 속성 정의)
public class Circle implements Shape {
	
	private String color;
	private int x;
	private int y;
	private int radius;
	
	public Circle(String color) {
		this.color = color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public void setX(int x) {
		this.x = x;
	}


	public void setY(int y) {
		this.y = y;
	}

	public void setRadius(int radius) {
		this.radius = radius;
	}

	@Override
	public void draw() {
		System.out.println("Circle [color= " + color +" , x= "+ x + " , y= "+ y +" , radius= "+ radius + " ]" );
	}
}

 

  • ShapeFactory (객체의 생성 또는 공유의 역할)
import java.util.HashMap;

public class ShapeFactory {
	public static final HashMap<String, Circle> circleMap = new HashMap<>();
	
	public static Shape getCircle(String color) {
		Circle circle = circleMap.get(color);
		
		if(circle == null) {
			circle = new Circle(color);
			circleMap.put(color, circle);
			System.out.println("---- 새로운 객체 생성 " + color +"색 원 ----" );
		}
		return circle;
	}
}

 

  • Main 클래스
public class Main {

	public static void main(String[] args) {
		String[] colors = {"Red", "Yellow", "Pink", "Blue"};
		
		for(int i=0;i<10;i++) {
			Circle circle = (Circle) ShapeFactory.getCircle(colors[(int) (Math.random()*4)]);
			circle.setX((int) (Math.random()*10));
			circle.setY((int) (Math.random()*20));
			circle.setRadius((int) (Math.random()*10));
			circle.draw();
		}
	}
}

 

  • 실행결과
---- 새로운 객체 생성 Blue색  ----
Circle [color= Blue , x= 8 , y= 5 , radius= 0 ]
---- 새로운 객체 생성 Red색  ----
Circle [color= Red , x= 5 , y= 5 , radius= 5 ]
---- 새로운 객체 생성 Pink색  ----
Circle [color= Pink , x= 6 , y= 17 , radius= 4 ]
Circle [color= Blue , x= 0 , y= 1 , radius= 6 ]
---- 새로운 객체 생성 Yellow색  ----
Circle [color= Yellow , x= 7 , y= 1 , radius= 4 ]
Circle [color= Yellow , x= 1 , y= 2 , radius= 0 ]
Circle [color= Red , x= 0 , y= 13 , radius= 3 ]
Circle [color= Yellow , x= 9 , y= 5 , radius= 1 ]
Circle [color= Pink , x= 1 , y= 16 , radius= 0 ]
Circle [color= Red , x= 7 , y= 11 , radius= 2 ]

같은 색상의 원은 1개만 생성되며, 생성된 객체를 공유하는 것을 확인할 수 있다.

 

 

4. 결론


4.1 언제 플라이웨이트 패턴을 사용하면 좋을까?

  • 공통적인 인스턴스를 많이 생성하는 로직이 포함된 경우
  • 자주 변하지 않는 속성을 재사용하는 경우

 

4.2 싱클톤 패턴과의 차이는 뭘까?

  • 싱클톤 패턴은 클래스 자체가 오직 1개의 인스턴스만 허용
  • 플라이웨이트 패턴은 싱글톤이 아닌 클래스 팩토리에서 제어

--> 인스턴스 생성의 제한을 어디서 제어하느냐의 차이

 

4.3 어디에서 플라이웨이트 패턴을 사용할까?

  • 임베디드와 같이 메모리를 최소한으로 사용해야 하는 경우에 활용
  • 클래스의 객체를 많이 만들어야할 때 사용

 

 

 

## 참고한 블로그 ##

 

[디자인패턴/Design Pattern] Flyweight Pattern / 플라이웨이트 패턴

관련 내용은 [자바 언어로 배우는 디자인 패턴 입문] , [Head First Design Pattern] 의 내용을 참고해서 정리한 글입니다. 잘못된 부분은 댓글로 피드백 부탁드립니다. 1. Flyweight 패턴이란? 어떤 클래스

lee1535.tistory.com

 

[구조 패턴] 플라이웨이트 패턴

1. 플라이웨이트 패턴(Flyweight Pattern)이란? 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴 공통으로 사용하는 클래스(Flyweight)를 생성하는 팩토리 클래스(FlyweightFactory)를 만들어, 인스턴스를 최

dev-youngjun.tistory.com

 

'정리 > Design Pattern' 카테고리의 다른 글

Visitor Pattern - 방문자 패턴  (0) 2022.01.04
 
 

1. Visitor Pattern 이란?
2. Visitor Pattern의 적용 예시

 

1️⃣ Visitor Pattern

  • 방문자와 방문 공간을 분리하여, 방문 공간이 방문자를 맞이할 때, 이후에 대한 행동을 방문자에게 위임하는 패턴
  • 알고리즘을 객체 구조에서 분리시키는 디자인 패턴
    ⇒ 개방-폐쇄 원칙을 적용하는 방법 중 하나
    ⇒ 구조를 수정하지 않고도 실질적으로 새로운 동작을 기존의 객체 구조에 추가할 수 있게 된다.

 

예시

  • 쇼핑몰 고객을 등급별로 나누고 등급에 따라 혜택을 주기로 한다.
  • 등급은 Gold, Vip가 있고 혜택은 포인트, 할인이 있다.
  • 고객 등급별 혜택을 줄 수 있는 확장 가능한 해결책을 찾아보자.

 

public interface Member { }

public class GoldMember implements Member { }

public class VipMember implements Member { }

  • Member 인터페이스를 사용해서 등급별로 구체 클래스 생성

 

public class GoldMember implements Member {
	public void point() { System.out.println("Point for Gold Member"); }
	public void discount() { System.out.println("Discount for Gold Member"); }
}

public class VipMember implements Member {
	public void point() { System.out.println("Point for Vip Member"); }
	public void discount() { System.out.println("Discount for Vip Member"); }
}
  • 등급별 혜택 구현

 

public class Main {
	public static void main(String[] args) {
		Member gold = new GoldMember();
		Member vip = new VipMember();

		gold.point();
		vip.point();
		gold.discount();
		vip.discount();
	}
}
//실행 결과
Point for Gold Member
Point for Vip Member
Discount for Gold Member
Discount for Vip Member

 

 

⭐ 문제점 ⭐

  • 고객들에게 혜택을 주고자 할 때 명시적으로 혜택을 주기 위한 메소드를 호출해야한다.
  • 혜택이 늘어났을 경우 모든 멤버들에 대해 그 혜택을 구현했다는 보장이 없다.

 

 

⭐ 해결방안 ⭐

public interface Benefit {
    void getBenefit(GoldMember member);
    void getBenefit(VipMember member);
}
  • Benefit 인터페이스에 혜택을 받을 Member 별로 실행 가능한 메소드 정의

 

public class DiscountBenefit implements Benefit {
    @Override
    public void getBenefit(GoldMember member) {
        System.out.println("Discount for Gold Member");
    }

    @Override
    public void getBenefit(VipMember member) {
        System.out.println("Discount for Vip Member");
    }
}

public class PointBenefit implements Benefit {
    @Override
    public void getBenefit(GoldMember member) {
        System.out.println("Point for Gold Member");
    }

    @Override
    public void getBenefit(VipMember member) {
        System.out.println("Point for Vip Member");
    }
}
  • 멤버 등급별 혜택에 대해 기능 구현

 

public interface Member {
    void getBenefit(Benefit benefit);
}
  • 등급별 멤버가 혜택을 받을 수 있는 메소드를 Member 인터페이스에 선언

 

public class GoldMember implements Member {
    @Override
    public void getBenefit(Benefit benefit) {
        benefit.getBenefit(this);
    }
}

public class VipMember implements Member {
    @Override
    public void getBenefit(Benefit benefit) {
        benefit.getBenefit(this);
    }
}
  • 혜택받는 메소드 구현
  • 다른 Member가 추가되더라도 구현 부분은 benefit.getBenefit(Benefit benefit); 만 넣으면 된다.

 

public class Main {
    public static void main(String[] args) {
        Member goldMember = new GoldMember();
        Member vipMember = new VipMember();
        Benefit pointBenefit = new PointBenefit();
        Benefit discountBenefit = new DiscountBenefit();

        goldMember.getBenefit(pointBenefit);
        vipMember.getBenefit(pointBenefit);
        goldMember.getBenefit(discountBenefit);
        vipMember.getBenefit(discountBenefit);
    }
}

//실행 결과
Point for Gold Member
Point for Vip Member
Discount for Gold Member
Discount for Vip Member

 

⭐ 결과 ⭐

  • Visitor 패턴에서는 혜택이 늘어나더라도 Benefit 인터페이스에 명시적으로 등급별 메소드를 정의하고 있어 구현 누락이 발생하지 않는다.
  • 혜택을 추가하기 위해서 해당 혜택을 구현하는 클래스를 추가하면 되므로, 코드 중복이 발생하지 않는다.

 

⭐ Visitor 패턴을 언제 쓰면 좋을까? ⭐

  • 대상 객체가 잘 바뀌지 않고, 적용할 알고리즘이 추가될 가능성이 많은 상황일 때

 

 

참고

https://thecodinglog.github.io/design/2019/10/29/visitor-pattern.html

https://velog.io/@cham/Design-Pattern-방문자-패턴-Visitor-pattern

'정리 > Design Pattern' 카테고리의 다른 글

Flyweight Pattern - 플라이웨이트 패턴  (0) 2022.03.31

1. WebSocket connection to failed

개발 서버에서는 WebSocket을 통한 채팅이 정상적으로 됐는데, 배포한 서버에서는 Connection 오류가 발생하면서 채팅 기능이 제대로 동작하지 않았다..

 

 

서버 환경

  • Ubuntu 20.04
  • Nginx 1.18.0

 

💡 해결방법

1. /nginx.conf > proxy_http_version, proxy_set_header 부분 추가

location /endpoint {
		proxy_pass <http://localhost>:포트번호/endpoint;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

		proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
}

에러 해결 ❌

 

 

2. 위의 방법이 안될경우

location /endpoint {
		proxy_pass <http://localhost>:포트번호/endpoint;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
		proxy_set_header Origin ""; // 이 부분 추가

		proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
}

proxy_set_header Origin ""; 을 추가해보자!

contextLoads() FALIED error

  • build 중contextLoads() FALIED 에러가 발생했다.

💡 해결방법 💡

  • main에는 application.properties 파일이 존재하지만 test에는 application.properties 파일이 없어서 발생하는 문제!
  • Spring Boot 에서 ApplicationTest.java 파일에 @SpringBootTest 주석처리 해주면 해결!

npm not found error

  • build 중 npm install 에서 npm not found 에러가 발생했다.

 

💡 해결방법 💡

1️⃣ NodeJS 플러그인 설치

  • Jenkins 관리 -> 플러그인 관리 -> 설치 가능 탭 -> NodeJs 검색 후 설치

 

 

2️⃣ NodeJs 플러그인 설정

  • Jenkins 관리 -> Global Tool Configuration
  • NodeJS -> Add NodeJS에서 위에와 같이 설정 후 저장

 

3️⃣ Job 설정

  • 구성 -> 빌드 환경 설정

Iterator<E> 인터페이스

  • 컬렉션에 저장된 요소를 읽어오는 방법을 iterator인터페이스로 표준화
    LinkedList<Integer> list = new LinkedList<Integer>(); 
    
    list.add(4); 
    list.add(3); 
    list.add(2); 
    list.add(1); 
    
    Iterator<Integer> iter = list.iterator(); 
    
    while(iter.hasNext()){ //hasNext : 다음 요소를 가지고 있으면 true / 아니면 false 반환 
    	System.out.print(iter.next()+" "); //next : 다음 요소 반환 
    } 
    
    // 4 3 2 1​

 

ListIterator<E> 인터페이스

  • Iterator 인터페이스를 상속받아 여러 기능을 추가한 인터페이스
  • Iterator 인터페이스는 컬렉션 요소에 접근할 때 한 방향으로만 이동할 수 있지만 ListIterator 인터페이스는 양방향으로 이동할 수 있다.
LinkedList<Integer> list = new LinkedList<Integer>(); 

list.add(4); 
list.add(2); 
list.add(3); 
list.add(1); 

ListIterator<Integer> iter = list.listIterator(); 

while (iter.hasNext()) { //hasNext : 순방향으로 순회할 때 다음 요소가 있으면 true 아니면 false 
	System.out.print(iter.next() + " "); //next : 다음 요소를 반환하고 커서 위치 순방향으로 이동 
} 

while (iter.hasPrevious()) { //hasPrevious : 역방향으로 순회할 때 다음 요소가 있으면 true 아니면 false 
	System.out.print(iter.previous() + " "); //previous : 이전 요소를 반환하고 커서 위치 역방향으로 이동 
} 

// 4 2 3 1 
// 1 3 2 4

+ Recent posts