반응형

Mac에서 Alias 설정하는 방법

 

터미널 열고

$ vi ~/.bash_profile

bash_profile 파일을 열고 

원하는 alias 를 적용한다.

 

아래처럼 alias = "명령어"

 로 설정하면 된다.

# Source bashrc
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

alias cl='clear'

 

파일 저장 후 

$ source ~/.bash_profile

를 ㅈ거용하면 alias 가 적용된다.

반응형
반응형

 MacOs catalina 

에서 플러터 깔고 안드로이드 스튜디오로 안드로이드 폰 usb 연결해서 빌드 시키면 

실행이 안된다.

아이폰 시뮬레이터는 실행되고 안드로이드만 동작하지 않는다.

 

에러문구는 아래와 같다.

 

FAILURE: Build failed with an exception.

 

* What went wrong:
Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'.

> Failed to install the following Android SDK packages as some licences have not been accepted.

~~~~~

 

라이센스가 없다는 의미로 터미널로 입력하면 된다.

$ flutter doctor --android-licenses

여러 질문이 있는데 다 y yes 를 하면 된다.

 

명령어 치고 yes 를 입력한 뒤 바로 빌드하면 헬로우 월드 앱이 잘 실행된다.

 

참고

https://stackoverflow.com/questions/60467477/android-sdk-tools-option-is-missing-from-sdk-manager-in-android-studio-3-6-1
반응형
반응형

ElasticsearchRepository search 메소드 내부에 NativeSearchQuery 를 넘겨 사용 가능하다.

또는 ElasticsearchTemplate 및 elasticsearchOperations 을 사용할떄도 NativeSearchQuery 를 사용할 수 있다.

 

쿼리 클래스의 종류로는 

CriteriaQuery, StringQuery, NativeSearchQuery

 

NativeSearchQuery 란 

복잡한 쿼리를 사용하려 할때 Criteria 로는 표현이 불가할때 사용한다.

CriteriaQuery 와 StringQuery 의 단점을 보완한 클래스이다.

 

ElasticsearchRepository 를 상속받아 사용하는 방식

@Repository
public interface PersonRepository extends ElasticsearchRepository<Person, String> {

}

또는 

ElasticsearchOperations elasticsearchOperations 을 사용하여 사용가능하다.

@Resource
ElasticsearchOperations elasticsearchOperations;

@Resource
PersonRepository personRepository;

 

두 클래스 중 하나를 사용하면 된다. elasticsearchOperations 은 elasticsearchTemplate 을 구현한 것이라 같게 봤다.

쿼리메소드 종류

matchQuery

//    GET /person/_search
//    {
//        "query": {
//        	"age": 30
//    	   }
//    }
// age 가 30 인 데이터
PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("age", 30);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();

String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = sampleRepository.search(nativeSearchQuery).getContent();
// 쿼리스트링으로도 가능

termsQuery

//{
//  "query": {
//    "terms": {
//      "firstname": ["vera", "kari"]
//    }
//  }
//}

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("firstname", "vera", "kari", "blake");
//term 
//TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("firstname", "Vera", "Kari", "Blake");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(termsQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

multiMatchQuery

// id 와 age 가 44 인 것 찾기

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("44", "id", "age");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(multiMatchQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

BoolQueryBuilder

 PageRequest pageRequest = PageRequest.of(0, 50);
 FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
 .must(QueryBuilders.matchQuery("firstname", "Effie"))
 .mustNot(QueryBuilders.matchQuery("gender", "M"))
 .should(QueryBuilders.matchQuery("balance", "3607"));

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

IdsQueryBuilder

//    GET /person/_search
//    {
//        "query": {
//        "ids": {
//            "values": [
//            "25",
//                    "44",
//                    "126"
//      ]
//        }
//    }
//    }
PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
IdsQueryBuilder idsQueryBuilder = QueryBuilders.idsQuery().addIds("25", "44", "126");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(idsQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

DisMaxQueryBuilder

//    GET /person/_search
//    {
//        "query": {
//        "dis_max": {
//            "tie_breaker": 0.7,
//                    "boost": 1.2,
//                    "queries": [
//            {
//                "term": {
//                "firstname": {
//                    "value": "Effie"
//                }
//            }
//            },
//            {
//                "match": {
//                "gender": "m"
//            }
//            }
//      ]
//        }
//    }
//    }

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

DisMaxQueryBuilder disMaxQueryBuilder = QueryBuilders.disMaxQuery()
.add(QueryBuilders.termQuery("firstname", "Effie"))
.add(QueryBuilders.matchQuery("gender", "m"))
.boost(1.2f)
.tieBreaker(0.7f);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(disMaxQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();
    

FuzzyQueryBuilder

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("firstname", "effie");

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(fuzzyQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

prefixQuery

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

PrefixQueryBuilder prefixQueryBuilder = QueryBuilders.prefixQuery("firstname", "b");
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(prefixQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

rangeQuery

PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age").from(20).to(30).includeLower(true).includeUpper(false);
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(rangeQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();
PageRequest pageRequest = PageRequest.of(0, 50);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);
//address: "398 Dearborn Court",  有3个term  查询court 结束的位置为 3
SpanFirstQueryBuilder spanFirstQueryBuilder = QueryBuilders.spanFirstQuery(QueryBuilders.spanTermQuery("address", "court"), 3);
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(spanFirstQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();

boolQuery

PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("gender", "f"))
.should(QueryBuilders.termQuery("address", "court"))
.should(QueryBuilders.termQuery("state","md"))
.filter(QueryBuilders.rangeQuery("age").gte(30))
.filter(QueryBuilders.rangeQuery("balance").gte(2726));

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
List<Person> content = personRepository.search(nativeSearchQuery).getContent();
/**
     *
     *
    GET /person/_search
    {
        "query": {
            " function_score": {
                "query": {
                    "match": {
                        "gender": "F"
                    }
                },
                "field_value_factor": {
                    "field": "balance",
                            "modifier": "log1p",
                            "factor": 0.5
                }
          , "boost_mode": "sum"
            }
        }
    }*/
    
PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

//bool query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("gender", "F"));

//feild_value_factor
FieldValueFactorFunctionBuilder functionBuilder = ScoreFunctionBuilders.fieldValueFactorFunction("balance")
.modifier(FieldValueFactorFunction.Modifier.LOG1P)
.factor(0.5f);

FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, functionBuilder).boostMode(CombineFunction.SUM);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(functionScoreQueryBuilder)
.withPageable(pageRequest)
.withSort(idSortBuilder)
.build();
String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
Page<Person> page = personRepository.search(nativeSearchQuery);
logger.info("page: "+page.toString());
List<Person> content = page.getContent();

functionScoreQuery

/**

     GET /person/_search
     {
         "query": {
             "function_score": {
                 "query": {
                     "match": {
                         "gender": "F"
                    }
             },
             "gauss": {
                 "balance": {
                     "origin": "43951",
                     "scale": "100",
                     "offset": "10"
                 }
             },
             "boost_mode": "sum"
             }
         }
     }
     */
PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

//bool query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("gender", "F"));
GaussDecayFunctionBuilder gaussDecayFunctionBuilder = ScoreFunctionBuilders.gaussDecayFunction("balance", "43951", "100", "10");

//builder
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, gaussDecayFunctionBuilder).boostMode(CombineFunction.SUM);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(functionScoreQueryBuilder)
.withPageable(pageRequest)
//.withSort(idSortBuilder)
.build();

String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
Page<Person> page = personRepository.search(nativeSearchQuery);
logger.info("page: "+page.toString());
List<Person> content = page.getContent();
/**   function_score weight query
    GET /person/_search
    {
        "query": {
            "function_score": {
                    "query": {
                        "bool": {
                            "must": [
                            {
                                "match": {
                                "gender": "F"
                            }
                            },{
                                "range": {
                                    "age": {
                                        "gte": 25,
                                                "lte": 30
                                    }
                                }
                            }
                  ]
                        }
                    },
                    "functions": [
                    {
                        "weight": 2
                    }
                ]
            }
        }
    }
     **/
PageRequest pageRequest = PageRequest.of(0, 10);
FieldSortBuilder idSortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

//bool query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("gender", "F"))
.must(QueryBuilders.rangeQuery("age").gte(25).lte(30));

//weight buil
WeightBuilder weightBuilder = ScoreFunctionBuilders.weightFactorFunction(2.0f);

//builder
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, weightBuilder).boostMode(CombineFunction.SUM);

NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(functionScoreQueryBuilder)
.withPageable(pageRequest)
//.withSort(idSortBuilder)
.build();

String query = nativeSearchQuery.getQuery().toString();
logger.info(query);
Page<Person> page = personRepository.search(nativeSearchQuery);
logger.info("page: "+page.toString());
List<Person> content = page.getContent();
반응형
반응형

엘라스틱 을 조회할떄 네이밍에 언더바(언더스코어)가 들어간 경우 일반적인 방법으로 ElasticsearchRepository 라이브러리로 조회가 가능하지 않다.

EX) name_field : '데이터'

 

 

이거 오류 해결 방법 좀 알고 싶었으나 지지고 볶아도 오류해결 방법이 없다. 

애초에 네이밍을 카멜로 했거나 그게 안되면 ElasticsearchOperations 을 사용해서 엘라스틱 조회를 해야한다.

 

간편하게 사용하기에는 아무래도 ElasticsearchRepository 가 jpa 랑 같아 사용하기 편한데, 복잡한 쿼리에는 불편한 점이 따르니 어쩔 수 없다.

굳이 언더바를 검색하려면 방법은 있다.

@Repository
public interface SampleRepository extends ElasticsearchRepository<SampleData, String> {

    @Query("{\"bool\": {\"must\": {\"match\": {\"name_field\": \"?0\"}}}}")
    List<SampleData> findByName(String name);
}

이런 식으로 네이티브 쿼리를 보내는 방법 밖에 알 수가 없다.

하지만 이렇게 할거면 굳이 ElasticsearchRepository 를 쓰는 이유가 있을까 싶다. 그냥 ElasticsearchOperations 로만 사용해도 된다.

또는 ElasticsearchRepository 에서 search 메소드에 queryBuilder 를 넣어서 ElasticsearchOperations처럼 사용해도 된다.

 

 

새로 업데이트 된 엘라스틱에서는 가능해졌는지는 모르겠지만,

내가 사용중인 버젼 spring boot 2.1.2, 

spring-data-elasticsearch:3.1.4 에서는 가능하지 않다.

 

아마 상위버젼에서도 불가 할 것이다.

jpa 에서는 @Colunm(name="name_field") 이런식으로 해결 가능한데, elastic 에는 이러한 어노테이션이 존재하지가 않아 되지 않는다.

 

구글링을 하면 @@JsonProperty("name_field") 이렇게 해결하라는 글이 하나 있는데, 동작하지 않는다. 찾아보면 다 동작이 안된다는 말이 더 많다.

 

 

처음 엘라스틱 데이터를 만들때 언더바, 언더스코어 등을 네이밍에 사용하지 말고 카멜케이스로 만들자..

 

 

참고

stackoverflow.com/questions/50528299/query-elasticsearch-using-spring-data-elasticsearch-with-fields-containing-under
반응형
반응형

자바 selenium 사용시 새탭을 열고 싶은 경우가 있다.

 

 

탭을 여는 방법은 여러가지 있는데 키보드 이벤트를 이용한 경우와 자바스크립트를 이용하는 경우가 있다.

 

자바스크립트를 이용하는 경우

((JavascriptExecutor) driver).executeScript("window.open()");

위의 방식으로 새탭을 열고, 새탭의 브라우저도 컨트롤 하고 싶은 경우 tab 을 이동해야 한다.

탭을 이동하는 방법은

ArrayList<String> tabs = new ArrayList<String>(driver.getWindowHandles()); // 탭리스트 가져오기
driver.switchTo().window(tabs.get(1)).navigate().to("https://www.naver.com");

getWindowHandles 로 탭 리스트를 가져와서 원하는 탭 index 로 이동하면 된다. 탭을 새로 만들었으면 

driver.switchTo().window(tabs.get(1)).navigate().to("https://www.naver.com"); // 방금만든 1번 탭으로 이동

로 탭 이동 후 새로운 탭에서 이동하고자 하는 url 로 이동하자.

 

샘플코드

예시로 탭을 새로 만들고 이미 요청했던 화면을 닫는 코드.

System.setProperty(WEB_DRIVER_ID, WEB_DRIVER_PATH);
//Driver SetUp
ChromeOptions options = new ChromeOptions();
options.setHeadless(true);
options.setCapability("ignoreProtectedModeSettings", true);

driver = new ChromeDriver(options);
// driver 는 알아서 생성하자.

RemoteWebDriver driver = webdriverExecutor.getWebDriver();

for(int i = 1; i < 100; i ++) {
  driver.get("https://juntcom.tistory.com/" + i);
  ((JavascriptExecutor) driver).executeScript("window.open()"); // 새로운 탭 열기
  driver.close(); // 포커스가 첫번째 탭이므로 첫번째 탭이 닫힌다.
  ArrayList<String> tabs = new ArrayList<String>(driver.getWindowHandles()); // 탭리스트
  driver.switchTo().window(tabs.get(0)).navigate().to("https://www.naver.com"); 
  // 탭이 2개였지만 close로 한개가 없으므로 현재 탭은 1개 그러므로 0번 탭으로 포커스 후 url 이동하자
}

여기서 driver.close 를 하지 않으면 계속해서 새탭이 만들어질 것이다.

get() 메소드는 현재탭에서 url 변경이다.

 

키 이벤트로 열기

// Open a new tab
driver.findElement(By.cssSelector("body")).sendKeys(Keys.CONTROL + "t");

컨트롤 T 로 탭을 열 수 있어, 다음과 같이 사용할 수 있다.

 

반대로 탭을 키 이벤트로 닫을 수도 있다.

Actions action = new Actions(driver);
// Close the newly opened tab
action.keyDown(Keys.CONTROL).sendKeys("w").perform();
// Switch back to original
action.keyDown(Keys.CONTROL).sendKeys("1").perform();

컨트롤 W 는 현재 포커스의 브라우저를 닫는 단축키

컨프롤 숫자는 해당 번호의 순서 탭으로 이동하는 단축키이다.

 

또한 탭이 많을 경우 제어가 힘들수도 있는데, 원래 탭으로 돌아갈 수 도 있다.

// I had to grab the original handle
String originalHandle = driver.getWindowHandle();

// And switch back to the original handle. I am not sure why, but
// it just did not work without this, like it has lost the focus
driver.switchTo().window(originalHandle);

 

 

stackoverflow.com/questions/17547473/how-to-open-a-new-tab-using-selenium-webdriver[새탭열기 및 이동]

반응형
반응형

자바로 크롤링을 하려면 SSR - 서버사이드 렌더링 인 경우 url 에 로 http 리퀘스트 해서 받아온 응답을

element 를 찾아 파싱만 하면 된다.

 

하지만 문제가 CSR - 클라이언트사이드 렌더링 의 경우 크롤링을 하기 어렵다.

뿐만 아니라 로그인 및 기타 인증이 필요한 사이트일 수록 크롤링을 하기 어렵다.

 

그래서 브라우져를 프로그래밍으로 조작해서 필요한 데이터만 추출하는 라이브러리를 사용해야 한다.

라이브러리는 Selenium 라이브러리다.

 

Selenium?

: Selenium은 주로 웹앱을 테스트하는데 이용하는 프레임워크이다. ebdriver라는 API를 통해 운영체제에 설치된 Chrome등의 브라우저를 제어하게 된다.

spring boot 를 활용해서 maven 으로 pom.xml 에 다음과 같이 추가한다.

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

 

브라우져의 드라이버도 각자 받아야한다.

유의해야 할 것은 크롬의 경우 사용자가 사용하는 브라우져의 크롬 버젼을 확인해서 같은 버젼의 드라이버를 받아야 한다.

설정 > chrome 정보 에서 버젼을 확인한다.

 

드라이버 세팅

public class Webdriver {

    //WebDriver 설정
    private WebDriver driver;

    //Properties 설정
    public static String WEB_DRIVER_ID = "webdriver.chrome.driver";
    public static String WEB_DRIVER_PATH = "/Users/chromedriver"; // 다운받은 크롬드라이버 경로


    public Webdriver() {
        //System Property SetUp

        chrome();
        //firefox();
    }
    
    private void chrome(){
        System.setProperty("webdriver.chrome.driver", DRIVER_PATH);
		ChromeOptions options = new ChromeOptions();
		options.setHeadless(true);
		options.addArguments("--lang=ko");
	    options.addArguments("--no-sandbox");
	    options.addArguments("--disable-dev-shm-usage");
		options.addArguments("--disable-gpu");
        options.setCapability("ignoreProtectedModeSettings", true);


	    ChromeDriver driver = new ChromeDriver(options);
	    driver.manage().timeouts().pageLoadTimeout(30, TimeUnit.SECONDS);
    }
    
    private void firefox() {
        System.setProperty("webdriver.gecko.driver", "/Users/eomjuntae/develop/spring/crawling/src/main/resources/geckodriver");
        driver = new FirefoxDriver();
    }
    ...
    
    public void useDriver(String url) {
    	driver.get(url);	
    }
}    

위와 같이 크롬 드라이버에 관한 세팅을 하자. 

파이어폭스 드라이버 메소드도 만들었다. 위의 경로 설정으로 사용하면 된다.

 

드라이버를 생성후 get 메소드를 사용하면 된다.

Webdriver webdriver = new Webdriver();
webdriver.useDriver("https://juntcom.tistory.com");

 

드라이버 close 및 quit

브라우저를 띄우게 되면 항상 close 를 시켜줘야 한다. 하지 않으면 메모리만 잡아먹게 된다.

close() 메소드와 quit() 메소드의 차이는

quit 는 브라우져 종료

close 는 탭 종료 이다.

더이상 사용하지 않을 경우 quit() 메소드로 종료 시켜야 한다.

반응형
반응형
  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

 

상속이란

기반이 되는 상위 클래스의 특성을 하위 클래스에게 적용하고, 거기에 더해 필요한 특성을 추가 확장하는 방식을 말한다.

상속의 목적은 기존 기능의 확장과 코드의 재사용이다.

 

상속의 장점

하위클래스에 상위 클래스를 extend 만 하여도 코드가 자동적으로 재사용된다는 것이며, 확장성을 위해 변경하여야 할 부분의 로직만 overiding을 사용하여 재정의도 가능하다.

 

상속 시 단점

상속을 사용하여 코드를 재사용하느 경우에는 기존 상위 클래스가 어떻게 작성되었는지 고려해야 한다.

상속의 특성상 상위 클래스와 하위 클래스가 결합상태로 묶여, 추후 코드의 수정으로 상위클래스의 변경 여파가 하위클래스에 전파될 수 있는 상황이 발생한다.

 

상속을 사용하는 경우

프로젝트 구조 자체가 상속을 위해 설계된 경우

is-a 관계가 명확한 경우 (동물 -> 포유류 -> 고래 등 상위 분류와 하위분류가 명확한 경우)

이미 존재하는 클래스의 메서드와 필드를 재사용할 수 있기때문에 사용한다. 하지만 단순히 재사용만을 위해 상속을 사용하게 되면 oop 의 목적과는 맞지 않는 클래스가 될 수 있다.

 

자바 상속의 특징

다중상속을 지원하지 않는다.

모든 클래스는 Object 클래스의 자식 클래스이다.

 

자바의 상속 구조

단일 상속 구조

public class 상위클래스 {

}

public class 하위클래스 extends 상위클래스 {

}

계층적 상속 구조

public class 상위클래스 {

}

public class 하위클래스A extends 상위클래스 {

}

public class 하위클래스B extends 상위클래스 {

}

자바는 다중 상속을 허용하지 않는다.

다중 상속을 허용하지 않는 이유는 하위 클래스의 코드를 복잡하게 만들기 떄문이다.

다중상속을 하려면 인터페이스를 이용한 다중 상속이 있다.

public class 상위클래스 {

}

public interface 인터페이스{

}

public class 하위클래스 extends 상위클래스 implements 인터페이스 {

}

 

Super 키워드

super 키워드는 자식클래스에서 부모 클래스를 가리키는 키워드이다.

super 키워드를 통해 자식클래스는 부모 클래스의 필드나 메서드를 호출할 수 있다.

super() 메서드는 부모클래스의 생성자를 호출하는 메서드이다.

자식클래스의 생성자에는 기본적으로 super() 를 호출해야 하는데 기입하지 않더라도 컴파일러가 기본으로 추가한다.

class Parent {
	
    public Parent() {
    	System.out.println("부모 클래스 생성자");
    }
    
}

class Child extends Parent {

    public Child() {
    //  super(); 내부적으로 동작한다.
    	System.out.println("자식 클래스 생성자");
    }
}

class Main {
    public static void main(String[] args) {
        Parent p = new Parent();
        
        System.out.println(">>");
        
        Child c = new Child();
    }
}

// 출력내용

부모 클래스 생성자  // 얘도 알고보면 앞에 Object의 생성자가 호출 되겠지.
>>
부모 클래스 생성자다.  // 부모클래스의 생성자가 호출되고 자식 클래스의 생성자가 호출된다.
자식 클래스 생성자다.  

자식클래스의 기본생성자를 만들때 부모 클래스의 기본 생성자가 없으면 컴파일 에러가 생긴다.

class Parent {  //  기본 생성자가 없는 부모 클래스

    String name;
    
    public Parent(String name) {
        this.name = name;
    }
    
}

class Child extends Parent {
    
    public Child() {    // 이 경우 부모 클래스에 기본 생성자가 없기 때문에
    // super();         // 컴파일 에러가 난다.
    }
    
    public Child() {   
        super("super class"); // 부모 클래스의 생성자가 정확히 호출 되므로 에러가 없다.
    }
    
}

 

메서드 오버 라이딩

오버라이딩은 부모클래스로부터 상속받은 메서드를 자식 클래스에서 재정의 하는 것이다.

class Parent {
    public void say() {
        System.out.println("I`m parent.");
    }
}

class Child extends Parent {
    
    @Override
    public void say() {
        System.out.println("I`m child.");
    }
}

class Main {
	public static void main(String[] args) {
        Parent parent = new Parent();
        Child child = new Child();
        
        parent.say();
        child.say();
    }
}

@Override 어노테이션은 없어도 되지만 개발자에게 부모클래스에 있는 메서드라는것을 명시하기 위해 쓰는 것이다.

부모클래스에 없는 메서드에 어노테이션을 붙일 경우 컴파일 오류가 난다.

 

다이내믹 메서드 디스패치 (Dynamic Method Dispatch)

메서드 디스패치란 메소드를 어떻게 호출할 지를 정해서 호출하는 것이다.

정적 메소드 디스패치 (static method dispatch) 는 메서드가 어떻게 실행될지가 컴파일 타임에 결정된다.

스태틱 메소드 디스패치 (Static Method Dispatch)

스태틱 메소드 디스패치는 구현 클래스를 통해 컴파일 시점에서 컴파일러가 어떤 메소드를 호출할지 명확하게 알고 있는 경우
컴파일 시 생성된 바이트코드에도 정보가 남아있으며 애플리케이션 실행 전에 호출할 메소드 결정
대표적으로, 메소드를 오버로딩(Overloading)하면 매개변수 타입과 개수에 따라 어떤 메소드를 호출할지 알 수 있는 경우(메소드 시그니처 변경)
상위 클래스가 있더라도 하위 클래스(구현 클래스)로 선언을 하고 하위 클래스의 인스턴스를 생성

다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

다이나믹 메소드 디스패치는 인터페이스나 추상 클래스에 정의된 추상 메소드를 호출하는 경우로, 호출되는 메소드가 런타임 시 동적으로 결정되는 것
인터페이스 또는 추상 클래스로 선언하고 구현/상속 받은 하위 클래스의 인스턴스를 생성
컴파일러가 알고 있는 타입에 대한 정보를 토대로 런타임 시 해당 타입의 객체를 생성하고 메소드를 호출

 

public interface Animal {
  String method();
}

public class Dog implements Animal {

  @Override
  public String method() { . . . }

  public void bark() { . . . }
}

public class Cat implements Animal {

  @Override
  public String method() { . . . }

  public void meow() { . . . }
}
// 예시 1
public static void main(String[] args) {
  // 다이나믹 메소드 디스패치
  Animal animal = new Dog();
  System.out.println(animal.method());
}

// 예시 2
public class Example {
  private Animal animal;

  public Example(Animal animal) {
    this.animal = animal;
  }

  public void print() {
    // 다이나믹 메소드 디스패치
    System.out.println(animal.method());
  }
}
  • 런타임 전에는 객체 생성이 되지 않기 때문에 Animal animal = new Dog();를 해도, 컴파일러는 Dog가 생성됨을 알 수 없으므로 Animal이 정의한 method() 메소드만 접근 가능
  • Example 클래스의 생성자 인자로 넘어오는 매개값의 타입이 Dog인지 Cat인지(혹은 그 외)는 실행 시 확인 가능.  실행 시 동적으로 확인해서 다이나믹 디스패치

 

더블 디스패치 (Double Dispatch)

다이나믹 메소드 디스패치가 2번 발생하는 것
디자인 패턴 중 방문자 패턴 (Visitor Pattern) 과 밀접한 관계

방문자 패턴 (Visitor Pattern)

일반적으로 OOP는 객체가 스스로 행위를 수행하게 하지만, 경우에 따라(ex. 확장성 고려, OCP 위배) 객체의 행위 수행을 외부 클래스에 위임

이 때 사용하는 디자인 패턴 종류는 전략 패턴, 커맨드 패턴, 방문자 패턴

  • 전략 패턴 (Strategy Pattern)
    • 하나의 객체가 여러 동작을 하게 하는 패턴 (1:N)
  • 커맨드 패턴 (Command Pattern)
    • 하나의 객체가 하나의 동작(+보조 동작)을 하게 하는 패턴 (1:1)
  • 방문자 패턴 (Visitor Pattern)
    • 여러 객체에 대해 각 객체의 동작들을 지정하는 패턴 (N:N)

 

추상클래스

추상(abstract)이란 실체 간에 공통되는 특성을 추출한 것
추상 클래스는 객체를 직접 생성할 수 있는 실체 클래스의 공통적인 특성(필드, 메소드)을 추출해서 선언한 클래스

추상 클래스는 인스턴스 생성 불가능

Animal animal = new Animal(); // (X)
Animal animal = new Dog();    // (O)

클래스 선언 시 abstract 키워드 추가

public abstract class 클래스 {
  // 필드
  // 생성자
  // 메소드
}
  • 추상 클래스를 상속받은 자식 객체가 생성될 때, 내부에서 super 메소드가 호출되어 추상 클래스 객체를 생성하기 때문에 생성자 필요

추상 메소드 오버라이딩

추상 클래스는 자식 클래스의 공통 기능을 추출해 구현한 클래스이지만, 경우에 따라 자식 클래스마다 구현 내용이 달라질 수 있기 때문에 필요 시 추상 메소드를 작성
추상 메소드는 추상 클래스에서만 선언 가능하며, 메소드 선언부만 있는 형태
자식 클래스가 반드시 실행 내용을 구현하게 하기 위해 추상 메소드 선언

public abstract class Animal {
  public abstract void sound();
}

구현

public class Dog {
  @Override
  public void sound() {
    System.out.println("bark");
  }
}
public class Cat {
  @Override
  public void sound() {
    System.out.println("meow");
  }
}

 

final 키워드

메소드에 붙는 경우 - 오버라이딩 불가

class Parent {

    final void run() { // 메소드에 final이 붙는 경우.
        System.out.println("run");
    }
    
}

class Child extends Parent {

    @Override  //  오버라이딩을 못함, 컴파일 에러.
    void run() {
    }
    
}

클래스에 붙는 경우 - 상속 불가

final class Parent { // 클래스에 final이 붙는 경우.
}


class Child extends Parent { // 상속받을 수 없음, 컴파일 에러.
}

 

Object 클래스

 자바에서 모든 클래스들의 상위 클래스이다. 내가 임의로 만든 클래스도 Object 클래스를 상속받고 있다. extends Object를 써넣지 않았는데도 어떻게 이게 되는 걸까? 이것은 컴파일러가 컴파일 타임에 쓱 끼워 넣어 준다.

 

 내가 만든 클래스는 다른 클래스를 상속받고 있는데, 다중 상속이 되지 않는 자바인데 어떻게 Object 상속 부분을 끼워 넣어줄까? 이것은 컴파일러가 똑똑하게 가상 상위 클래스를 찾아 거기에 Object 상속을 슥 끼워넣어 준다.

 

 이렇게 해서 직접적으로나 간접적으로나 모든 클래스는 Object 클래스를 상속받는다.

반응형
반응형

RestTemplate이란

스프링에서 제공하는 http 통신에 유용하게 쓸 수 있는 템플릿

Spring 3부터 지원 되었고 REST API 호출이후 응답을 받을 때까지 기다리는 동기방식이다

 

AsyncRestTemplate

Spring 4에 추가된 비동기 RestTemplate이다

Spring 5.0에서는 deprecated 되었다

 

메소드

메서드

HTTP

설명

getForObject

GET

주어진 URL 주소로 HTTP GET 메서드로 객체로 결과를 반환받는다

getForEntity

GET

주어진 URL 주소로 HTTP GET 메서드로 결과는 ResponseEntity로 반환받는다

postForLocation

POST

POST 요청을 보내고 결과로 헤더에 저장된 URI를 결과로 반환받는다

postForObject

POST

POST 요청을 보내고 객체로 결과를 반환받는다

postForEntity

POST

POST 요청을 보내고 결과로 ResponseEntity로 반환받는다

delete

DELETE

주어진 URL 주소로 HTTP DELETE 메서드를 실행한다

headForHeaders

HEADER

헤더의 모든 정보를 얻을 수 있으면 HTTP HEAD 메서드를 사용한다

put

PUT

주어진 URL 주소로 HTTP PUT 메서드를 실행한다

patchForObject

PATCH

주어진 URL 주소로 HTTP PATCH 메서드를 실행한다

optionsForAllow

OPTIONS

주어진 URL 주소에서 지원하는 HTTP 메서드를 조회한다

exchange

any

HTTP 헤더를 새로 만들 수 있고 어떤 HTTP 메서드도 사용가능하다

execute

any

Request/Response 콜백을 수정할 수 있다

 

GET 메소드

getForObject()

Employee employee = restTemplate.getForObject(BASE_URL + "/{id}", Employee.class);

Employee 로의 매핑은 jackson-databind 가 기본적으로 담당하고 있다.

 

getForEntity()

응답을 ResponseEntity 객체로 받는다. getForObject()와 달리 HTTP 응답에 대한 추가 정보를 담고 있어서 GET 요청에 대한 응답 코드, 실제 데이터를 확인할 수 있다. 또한 ResponseEntity<T> 제네릭 타입에 따라서 응답을 String이나 Object 객체로 받을 수 있다.

 

ResponseEntity<String> responseEntity = restTemplate.getForEntity(BASE_URL + "/{id}", String.class, 25);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());

getForEntity()에 여러 값을 담은 params을 같이 넘겨주기

LinkedMultiValueMap 객체에 담아서 params로 넘겨줄 수 있다.

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("name", "Frank Oh");
params.add("country", "US");

ResponseEntity<Employee> responseEntity = restTemplate.getForEntity(BASE_URL + "/{name}/{country}", Employee.class, params);
log.info("statusCode: {}", responseEntity.getStatusCode());
log.info("getBody: {}", responseEntity.getBody());

 

get 요청에 header 값이 필요한 경우 

get 메소드에서는 header 를 추가 할 수 가 없다.

exchange 메소드를 사용해야 한다.

HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);

ResponseEntity<String> response = restTemplate.exchange(
  URL_PATH,
  HttpMethod.GET,
  request,
  String.class
);

 

get 요청에 header 값 과 쿼리스트링(query String, param)이 필요한 경우

post 처럼 HttpEntity 에 넣어서 요청할 수가 없다.

HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);
//adding the query params to the URL
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(URL_PATH)
	.queryParam("keywords", "11");
    .queryParam("name", "22");

ResponseEntity<String> response = restTemplate.exchange(
  uriBuilder.toUriString(),
  HttpMethod.GET,
  request,
  String.class
);

이렇게 UrlBuilder 를 사용해서 넣는 수 밖에 없다. post 방식과 달리 httpEntity 에 같이 넣거나 exchange 의 parameter 로 넘길 수가 없다.

사실 굳이 uriBuilder 를 써야 되나 싶기도 하다. 그냥 map 에 파라미터를 추가하고 map 을 parameter 로 변환해주는 메소드만 만들어서 사용하면 편할거 같다.


...
HttpHeaders headers = new HttpHeaders();
headers.set("header", header);
headers.set("header2", header2);

HttpEntity request = new HttpEntity(headers);

Map<String, String> params = new HashMap<String, String>();
params.put("query1", "test");
params.put("query2", "test2");

ResponseEntity<String> response = restTemplate.exchange(
  URL_PATH + "?" + this.mapToUrlParam(params),
  HttpMethod.GET,
  request,
  String.class
);
...

// 위에서 사용하려고 만든 util 메소드
private static String mapToUrlParam(Map<String, Object> params) {
  StringBuffer paramData = new StringBuffer();
  for (Map.Entry<String, Object> param : params.entrySet()) {
  	if (paramData.length() != 0) {
  		paramData.append('&');
  	}
    paramData.append(param.getKey());
    paramData.append('=');
    paramData.append(String.valueOf(param.getValue()));
  }
  return paramData.toString();
}

사실 굳이 uriBuilder 를 써야 되나 싶기도 하다.

단순하게 map 에 파라미터를 추가하고 map 을 parameter 문자열로 변환해주는 메소드만 만들어서 사용하면 편할거 같다.

 

 

postForObject() 메소드 header 값 없는 경우

Employee newEmployee = Employee.builder()
         .name("Frank")
         .address(Address.builder()
               .country("US")
               .build())
         .build();
 
   Employee employee = restTemplate.postForObject(BASE_URL + "/employee", newEmployee, Employee.class);

postForObject() 메소드 header 포함해서 보내기

Employee newEmployee = Employee.builder()
         .name("Frank")
         .address(Address.builder()
               .country("US")
               .build())
         .build();
 
   HttpHeaders headers = new HttpHeaders();
   headers.set("headerTest", "headerValue");
 
   HttpEntity<Employee> request = new HttpEntity<>(newEmployee, headers);
 
   Employee employee = restTemplate.postForObject(BASE_URL + "/employee", request, Employee.class);

postForEntity

getForEntity 와 동일해서 생략

 

post form data 사용

// 헤더 설정
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

// 파라미터 세팅
MultiValueMap<String, String> map= new LinkedMultiValueMap<>();
map.add("id", "1");

// 요청 세팅 완료
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

// 실제 요청부
ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl+"/form", request , String.class);

 

Timeout 설정하기

timeout 을 설정하려면 ClientHttpRequestFactory 와 같은 팩토리 메소드를 만들고 RestTemplate 의 생성자에 추가해야 한다.

RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());

private ClientHttpRequestFactory getClientHttpRequestFactory() {
    int timeout = 5000;
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
      = new HttpComponentsClientHttpRequestFactory();
    clientHttpRequestFactory.setConnectTimeout(timeout);
    return clientHttpRequestFactory;
}

 

참고사항으로 timeout 이 0 이면 무제한설정이다. (infinite timeout)

 

Execute()

 Execute()는 콜백을 통해 요청 준비와 응답 추출을 완벽하게 제어하여 요청을 수행하는 가장 일반적인 메서드를 RestTemplate에서 제공한다.

getForObject(), postForeObject() 등은 excute() 를 내부적으로 호출한다.

 

connection pool 적용

RestTemplate 은 기본적으로 connection pool 을 사용하지 않는다. 따라서 연결할 때 마다, 로컬 포트를 열고 tcp connection 을 맺는다. 이때 문제는 close() 이후에 사용된 소켓은 TIME_WAIT 상태가 되는데, 요청량이 많다면 이런 소켓들을 재사용하지 못하고 소켓이 오링나서 응답이 지연될 것이다.

 

 

참고문헌

www.baeldung.com/rest-template [샘플 문서]
stackoverflow.com/questions/31869193/using-spring-rest-template-either-creating-too-many-connections-or-slow/ [커넥션 많을 경우 pool 관리
반응형

+ Recent posts