레이블이 JAVA인 게시물을 표시합니다. 모든 게시물 표시
레이블이 JAVA인 게시물을 표시합니다. 모든 게시물 표시

2021년 12월 13일 월요일

@Scheduled 어노테이션 파라미터를 환경변수로 사용하기

@Scheduled  어노테이션으로 10초마다 실행하는 스케쥴은 아래와 같이 쉽게 만들 수 있다.

@Scheduled(fixedDelay = 10000)
public void scheduleTask() {


여기서 10000 값을 환경변수로 적용하려고 하면 fixedDelay는 final long 타입이여야 등록 가능한데,  환경변수로 가져오는 값은 final로 처리 할 수가 없다.

그럴때는 아래처럼 fixedDelayString을 사용하면 된다.

  @Scheduled(fixedDelayString = "${myscheduler.period}")
  public void scheduleTask() {

2021년 11월 25일 목요일

자바에서 퍼센트율(나누기)을 구할 때 유의사항.

일부값 / 전체값 * 100   X 

100d * 일부값 / 전체값 O


long part = 1;
long total = 3;

double ok = 100d * part / total;
double no = part / total * 100;

System.out.println(ok);
System.out.println(no);

33.333333333333336
0.0

1 / 3 = 0.333333 이다.

'part / total' 은 항상 정수형을 반환한다. part과 total의 타입이 long 타입이기 때문에 묵시적 형변환을 통해 끝수를 버리고 long타입으로 변환 된다.

만약 part나 total이 double 타입이라면 정상적으로 0.33333을 반환한다.

그래서 처음 100d를 통해 double 타입으로 반환하라고 명시 하면 오류 없이 퍼센트 결과를 얻을 수 있다.

2021년 7월 15일 목요일

ObjectMapper로 형변환이 안되는 문제. (대문자 스네이크 케이스)

 DTO 객체명이 대문자 스네이크 케이스 형태일 경우 ObjectMapper convertValue 메소드로 형변환시 값이 들어가지 않는다.


여기에서 대문자 스네이크 케이스 코드를 받아서 적용했더니 정상적으로 값이 들어간다.


아래와 같이 네이밍 전략을 선언해서 사용한다.

ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(CustomUpperSnakeCaseStrategy.UPPER_SNAKE_CASE);

ObjectMapper로 List Map 타입을 List Dto 타입으로 변환 방법

 

ObjectMapper mapper = new ObjectMapper();
List<Map<String, Object>> listMap = new ArrayList<>();
...
List<MyDto> testDtoList =
mapper.convertValue(listMap, TypeFactory.defaultInstance().constructCollectionType(List.class, MyDto.class));


2021년 3월 29일 월요일

Java로 무한 loop 쉘스크립트 실행

 java에서 쉘스크립트를 실행하는 기능을 추가 했다.

문제는 자바 프로그램이 종료되면 쉘스크립트 역시 1~2초 후에 멈춰 버린다.

쉘스크립트는 while true로 무한루프로 실행이 되는 스크립트였다.


자바에서 실행 명렁어를 nohup, &, sh -c 등 이것 저것 다 해 보았지만 쉘스크립트는 멈췄다.

결국 찾은 방법은 두가지이다. 두가지 방법 다 정석적인 해결책은 아니고 trick으로 볼수 있는 방법이다.


하나는 중간 launch 스크립트를 두는 방식이다.

실제 내가 실행해야 할 스크립트가 target.sh 이면 launcher.sh를 둬서 자바에서는 launcher.sh를 실행하는 방법이다.

launcher.sh에는 아래와 같은 명령을 넣는다.


#/bin/sh

nohup ./target.sh 1> /dev/null 2>&1 &



다른 하나는 trap을 사용한 방법이다. 

trap 명령어는 특정 시그널이 들어올 때 어떤 일을 할 지 적용할 수 있다.


실제 적용한 방법은 두번째 방법으로 trap명령어를 통한 방법이다.

적용 방법은 아래와 같다.


trap "method_name" 0


0은 EXIT 인 경우 이다. 자바 프로세스 종료시 EXIT 시그널이 오는 데, 그 때 loop를 사용하는 method_name를 다시 한번 사용하게 하였다.

종료는 9(SIGKILL) 시그널을 발생시켜서 종료하므로 문제 없이 stop을 할 수 있다.

2021년 3월 20일 토요일

netstat말고 ss명령을 사용합시다.


tcp 파일을 이용해서 현재 접속 현황을 가지고 오는 자바로 만들어진 프로그램이 있다.
이 프로그램에서 cpu 사용량이 15% 이상을 치는 문제가 발생했다.

리눅스에서 tcp와 udp의 소켓 정보는 /proc/net/tcp, tcp5, udp, udp6 파일에서 확인 할 수 있다.
문제의 프로그램은 위 파일을 읽고 파싱하여 데이터를 가져온다.
커넥션이 적을 때는 문제가 없지만, 커넥션이 많아지면서 문제가 발생한 것이다. 

커넥션 수가 20000개가 넘어가면 읽는 시간은 느려지지 않지만, cpu사용율이 15% 가까이 발생한다. 
커넥션 수가 50000개가 넘어가면 전체 데이터를 읽어오는 시간은 20초 가까이 걸리며 cpu사용율을 100% 가까이 사용한다.

tcp, udp 파일에서 하나의 라인은 하나의 커넥션이다.
처음에는 5만 라인이 굉장히 많은 줄 알았다. 그러나 /proc/net/tcp 파일을 복사해서 그 복사한 파일을 읽었더니 굉장히 빨랐다. 
/proc/net/tcp 파일 자체가 문제였던 거다. 구글링으로 문제에 대해 검색을 시작했다. 

구글링 후 알게 된 하나는 netstat명령과 ss 명령의 차이점이었다. 둘 다 네트워크의 상태를 보는 명령이지만 구조 자체가 다르다. 
netstat은 /proc/net/tcp 파일을 읽어들인다. 그렇기 때문에 커넥션이 많을 때는 netstat 명령 역시 느려진다. 
반대로 ss 명령은 커넥션이 많을 때도 굉장히 빠르다. 


AF_NETLINK를 이용하기 위해서는 C 언어를 이용해야 했다. C 언어 자체는 잘 모르지만 예제 샘플을 이용해서 원하는 형태의 프로그램은 만들 수 있을 것 같았다. 
만들어진 프로그램을 JNI을 이용해서 가지고 오면 될것으로 봤다.

여기에는 몇가지 문제가 있었다. kernel 과 직접 통신하기 때문에 커널 버전에 맞는 각각의 실행파일을 따로 준비해야 한다. (문제가 있었던 프로그램은 여러 서버에서 돌아가야 했다.)
커스터마이징을 한다고 해도 c 코드 자체를 수정하기가 쉽지 않다. 관리포인트가 늘어나는 문제도 있다.

고민을 거듭하다 방향을 바꿔서 그냥 ss 명령을 사용하는 방법을 생각했다.
ss 명령은 iproute 패키지의 일부분이다. iproute 패키지는 소스가 공개되어있다.
문제가 되었던 프로그램이 돌아가던 운영체제는 대부분이 centos나 redhat이었다.
centos 4,5,6,7 버전의 minimal 설치 패키지를 조사하여 iproute 패키지가 있는 지 확인하였다. 전부 있는 것으로 확인하였다.

ss 커맨드를 이용한 방법으로 개발을 진행하겠다고 컨펌을 받고 개발을 진행하였고 잘 마무리 되었다.

나중에 알게된 솔라윈즈에서 작성한 글이 딱 내가 겪었던 일을 잘 설명해 주어서 링크한다.

2018년 5월 24일 목요일

스택트레이스(stacktrace) 읽는 팁

자바는 에러 발생시 익셉션을 발생시켜서 에러가 발생한 위치를 정확히 알려준다.

스택트레이스는 꽤나 중요한 사항인데, 나 역시도 깊게 생각해 본 적이 없다.

그러다 okky 글을 읽고 내 나름의 스택트레이스 읽는 팁을 적어 본다.

 
1. lib 는 제외하고, 내 소스 부분만 본다.

2. 스택의 제일 윗 부분을 본다.

3. Caused by 가 있을 경우 제일 하단의 Caused by 부터 본다.

대부분의 경험있는 개발자라면 느낄 것이다. lib에서 에러가 발생하는 경우는 99%이상 없다고 본다.
에러의 대부분은 내가 짠 소스에서 발생한다.
개발할 때는 기본적으로 여러가지 라이브러리를 사용하기 때문에 스택이 복잡해 지는 데, 라이브러리를 제외하고 보면 꽤나 단순해 진다.

스택은 알다시피 First in Last Out (FILO)이다. 그렇기 때문에 제일 상단의 라인이 제일 마지막에 실행된 부분이고 이 부분이 에러인 경우가 가장 많다.

"Caused by: " 부분은 exception chaining 이 발생 할 때 나타나는 메시지이다.
제일 하단의 "Caused by: " 섹션에서 내 패키지가 없다면 패스, 있다면 해당 부분이 원인일 경우가 많다.

검색해 보니, 이런 부분을 이용하여 보기 스택트레이스를 보기 좋게 해주는 사이트도 있다.

참조
https://stackoverflow.com/questions/3988788/what-is-a-stack-trace-and-how-can-i-use-it-to-debug-my-application-errors
https://bukkit.org/threads/how-to-read-stack-traces-and-troubleshoot-your-own-plugins-by-yourself.32457/

2017년 11월 16일 목요일

mybatis hashmap으로 값 가져오기.

한개의 필드의 값을 hashmap으로 가져오려고 한다.

DB 내용
key1:value1,key2:value2,key3:value3

List<String> 으로 가져온 것과 마찬가지로 typeHandler를 사용하여 처리하였다.

 

DTO
HashMap<String, Object> extraProperties;


public HashMap<String, Object> getExtraProperties() {
return extraProperties;
}

public void setExtraProperties(HashMap<String, Object> extraProperties) {
this.extraProperties = extraProperties;
}

 

mybatis resultmap
<result property="extraProperties" column="extraProperties" typeHandler="com.my.handler.StringSplitMapTypeHandler" />

 

handler
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import com.my.util.CommonUtil;

public class StringSplitMapTypeHandler implements TypeHandler<HashMap<String,Object>> {

@Override
public void setParameter(PreparedStatement ps, int i, HashMap<String,Object> parameter, JdbcType jdbcType) throws SQLException {
if (parameter != null) {
ps.setString(i, parameter.toString());
}
}

@Override
public HashMap<String,Object> getResult(ResultSet rs, String columnName) throws SQLException {
String columnValueStr = rs.getString(columnName);
return CommonUtil.getStringSplitHashMap(columnValueStr, ",", ":");

}

@Override
public HashMap<String,Object> getResult(ResultSet rs, int columnIndex) throws SQLException {
String columnValueStr = rs.getString(columnIndex);
return CommonUtil.getStringSplitHashMap(columnValueStr, ",", ":");
}

@Override
public HashMap<String,Object> getResult(CallableStatement cs, int columnIndex) throws SQLException {
String columnValueStr = cs.getString(columnIndex);
return CommonUtil.getStringSplitHashMap(columnValueStr, ",", ":");
}
}

 

getStringSplitHashMap 코드
	public static HashMap<String,Object> getStringSplitHashMap(String targetStr, String fieldSeparator, String mapSeparator){
if (targetStr == null) return null;
String[] fields = targetStr.split(fieldSeparator);
HashMap<String,Object> returnData = new HashMap<String,Object>();
for(String str : fields){
String[] splitStr = str.split(mapSeparator);
if(splitStr.length < 2) continue;
String key = splitStr[0];
String value = splitStr[1];
returnData.put(key, value);
}
return returnData;
}

mybatis List으로 값 가져오기.

DB 테이블 한개의 필드에 저장된 값을 구분자로 나눠서 list로 가져오려고 한다.

typeHandler를 써서 간단히 적용 완료.

DB 내용 searchTags 필드
value1, value2, value3

,를 구분자로 각각의 value들이 하나의 List 값이 된다.

DTO
List<String> searchTags;

public List<String> getSearchTags() {
return searchTags;
}
public void setSearchTags(List<String> searchTags) {
this.searchTags = searchTags;
}

 

mybatis resultmap
<result property="searchTags" column="searchTags" typeHandler="com.my.handler.SplitTypeHandler" />

 

StringSplitTypeHandler
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

public class SplitTypeHandler implements TypeHandler<List<String>> {

@Override
public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
if (parameter != null) {
ps.setString(i, parameter.toString());
}
}

@Override
public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
String columnValueStr = rs.getString(columnName);
if (columnValueStr != null) {
return Arrays.asList(columnValueStr.split(","));
}
return null;
}

@Override
public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
String columnValueStr = rs.getString(columnIndex);
if (columnValueStr != null) {
return Arrays.asList(columnValueStr.split(","));
}
return null;
}

@Override
public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
String columnValueStr = cs.getString(columnIndex);
if (columnValueStr != null) {
return Arrays.asList(columnValueStr.split(","));
}
return null;
}
}

 

 

 

2017년 11월 14일 화요일

자바 json 데이터 정렬(?) 예쁘게(?) 출력하기.

GSON 사용시.
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(uglyJSONString);
String prettyJsonString = gson.toJson(je);

 

jettison 사용시
JSONObject json = new JSONObject(jsonString); // Convert text to object
System.out.println(json.toString(4)); // Print it with specified indentation

 

jackson 사용시
String test = "{\"age\":29,\"messages\":[\"msg 1\",\"msg 2\",\"msg 3\"],\"name\":\"mkyong\"}";
Object json = mapper.readValue(test, Object.class);
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json));

2017년 11월 3일 금요일

spring standalone application 에서 @service 실행

spring standalone application 에서 @service 실행

스프링 프로젝트에서 Main 함수안에서 기존에 구축된 @service 를 사용하려면

다음과 같이 하면 된다.
package xxx.xxx.www.test.console;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import xxx.xxx.www.test.service.TestService;

@Component
public class Main {

private static final String CONFIG_PATH = "classpath*:spring/all-config.xml";

public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_PATH);

Main p = context.getBean(Main.class);
p.start(args);
}

@Autowired
private TestService serv;
private void start(String[] args) throws Exception {
Map map = new HashMap();
map.put("docnum", "108");
List list = serv.selectList(map);
System.out.println("결과 logger:::::::::::::::: " + list);
}
}

 

한참 해맸었던 이유가 있는데, 기존에 스프링 환경은 아래와 같이 각 파트별로 구분지어 만들어진 환경이다.

spring-common.xml

spring-datasource.xml

....

이걸 부분만 적용하려니 잘 되지 않았다. 아래와 같이 한꺼번에 가져오도록 xml 파일을 하나 가져와서 해결.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

<import resource="classpath*:spring/spring-*.xml"/>
</beans>

2017년 8월 4일 금요일

PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification 에러 해결법

mvn clean install 명령어를 실행하려고 했더니, 에러 메시지가 난다.
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification

 

문제의 원인은 JAVA에서 인증서를 신뢰하지 않아서 발생 하는 문제.

나의 경우는 회사에서만 발생하는 에러이다.

왜냐면...

회사에서 나의 활동을 감시하기 위하여 'SSL PRISM Cerificate Installer (ARA Networks Co.,Ltd)' 프로그램을 강제로 설치하도록 했기 때문이다.

설치안하면 인터넷이 안된다.  ㅡㅡ;
쩝... 어째든 해결 방법은 다음과 같다.

아래의 경로로 가면
'C:\Program Files (x86)\ARA Networks Co.,Ltd\SSL PRISM Certificate Installer'

SSLPrism.crt 파일이 있다. 이것을 자바의 ca가 저장 되어 있는 'C:\Program Files\Java\jdk1.8.0_131\jre\lib\security'로 복사 해준다.

그래고 아래 명령어로 인증서를 추가 해준다.
C:\Program Files\Java\jdk1.8.0_131\jre\lib\security>keytool -keystore cacerts -importcert -alias sslprism -file SSLPrism.crt

 

패스워드를 물어보는데, 기본패스워드는 'changeit' 이다.

그리고 y.
mvn clean install 명령을 다시 실행하면 정상적으로 동작 한다.

 

ps.

아래 명령어로 ssl 무시하고 하려고 했지만, 안 됐다.
mvn clean install -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true

https://stackoverflow.com/questions/21252800/how-to-tell-maven-to-disregard-ssl-errors-and-trusting-all-certs

 

참고.
https://gs.saro.me/#!m=elec&p=1&jn=772
https://docs.microsoft.com/ko-kr/azure/java-add-certificate-ca-store

2017년 7월 6일 목요일

StringUtils의 isEmpty, isBlank 분석.

자바에서 Null 포함 빈값을 체크 할 때, StringUtils의 isEmpty 나 isBlank를 사용한다.
isEmpty는 "", null 일 경우 true를 리턴한다.
isBlank는 "", null, " " 일 경우 true를 리턴한다.

해당 메소드가 어떻게 동작하는 지 알아 봤다.

소스
isEmpty가 동작하는 방식은 간단하다.
        public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}

null 이거나, length 가 0 일경우를 체크 한다.

 

 

isBlank는 조금 더 복잡하다.
        public static boolean isBlank(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if ((Character.isWhitespace(str.charAt(i)) == false)) {
return false;
}
}
return true;
}

첫번째는 isEmpty와 같지만 스페이스를 체크하기 위해 for문을 돈다.

 

 

간혹 StringUtils 라이브러리가 없고, 추가 할 수 없는 경우 참고 하면 좋을 것 같다.