1. 세가지 공통점

 - 문자열을 처리하는 Class


2. String 과 (StringBuffer, StringBuilder)의 차이점

 - 메모리 상에서 처리방식에서 차이점이 있다.

 - String은 immutable(변경불가) 이고, StringBuffer는 mutable이다.


3. StringBuffer, StringBuilder 두개의 차이점

 1) StringBuffer

  - append 메소드

@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}


 2) StringBuilder

  - append 메소드

@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}


4. 결론

 - 단순한 처리일 경우 String 을 사용해도 크게 문제 안됨.

 - multi thread 환경에서는 StringBuffer를 사용하는게 안전하다. 그렇지 않을 경우라면 StringBuilder가 더 빠르다. 아래 소스를 참고하자.

public class StrTest {

public static void main(String[] args) {

strBufferTest();
strBuilderTest();
}

private static void strBufferTest() {
Date start = new Date();
StringBuffer stringBuffer = new StringBuffer();

new Thread(() -> {
IntStream.range(1, 10000).forEach(num -> {
stringBuffer.append(num);
});

}).start();

new Thread(() -> {
IntStream.range(1, 10000).forEach(num -> {
stringBuffer.append(num);
});

}).start();

new Thread(() -> {
Date end = new Date();
long millis = end.getTime() - start.getTime();

try {
Thread.sleep(5000);

System.out.println("StringBuffer length : " + stringBuffer.length());
System.out.println("StringBuffer time(ms) :" + millis);

} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}

private static void strBuilderTest() {
Date start = new Date();
StringBuilder stringBuilder = new StringBuilder();
new Thread(() -> {
IntStream.range(1, 10000).forEach(num -> {
stringBuilder.append(num);
});

}).start();

new Thread(() -> {
IntStream.range(1, 10000).forEach(num -> {
stringBuilder.append(num);
});

}).start();

new Thread(() -> {
Date end = new Date();
long millis = end.getTime() - start.getTime();

try {
Thread.sleep(5000);

System.out.println("StringBuilder length : " + stringBuilder.length());
System.out.println("StringBuilder time(ms) :" + millis);

} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

}
}

  - 위 소스 결과


 - JDK 1.5버전 부터인지 정확하지는 않지만 String 선언시 문자열을 '+'하는 형태로 작업 할 경우 컴파일시에 StringBuilder로 변경됨.



*참고 페이지

 - https://www.slipp.net/questions/271

 - https://lalwr.blogspot.com/2016/02/string-stringbuffer-stringbuilder.html

(작성일 기준으로 1.2.8 버전이 최신버전)


자바(with Spring)를 개발하다보면 Class를 추가/변경 하는 작업을 많이 한다. 그럴때마다 서버를 리스타트하는 일은 엄청나게 번거로운 일이다. 그래서 자동으로 reload 시켜주는 걸 찾아봤다.


1. 설치하기

 - pom.xml

<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>


 - jar 다운로드(이 경우에는 다운로드한 경로를 따로 복사해 놓으세요.)

http://mvnrepository.com/artifact/org.springframework/springloaded/1.2.8.RELEASE


4. Spring Boot

 1) maven 명령어로 실행

spring-boot:run 을 실행시킨다.


 2) vm option 추가

-javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify


  (1) Intelli J 일 경우

 - File -> Setting -> Build, Execution, Deployment -> Compiler

Build project automatically 체크!!




  (2) Eclipse 일 경우

    - 추후 작성


5. Spring Framework + Tomcat

 - 추후 작성


6. Daemon 실행

java -javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify SomeJavaClass


7. 테스트 결과

 1) 전역변수 추가 -> 안됨.

  - 테스트 코드(변경 전)

@RequestMapping(value = "/")
public ResponseEntity<String> test() {
return new ResponseEntity<String>("INDEX", HttpStatus.OK);
}

  - 테스트코드(변경 후)

private String ma = "test222";

@RequestMapping(value = "/")
public ResponseEntity<String> test() {
LOG.info("test : {} ", ma);
return new ResponseEntity<String>("INDEX", HttpStatus.OK);
}


  - 결과


 2) @Controller 추가 -> 안됨.

@Controller
public class Test {
@RequestMapping(value = "/test")
public ResponseEntity<String> test2() {
return new ResponseEntity<String>("INDEX 2", HttpStatus.OK);
}
}

 

 3) @RequestMapping 추가 -> 안됨.

@RequestMapping(value = "/test")
public ResponseEntity<String> test2() {
return new ResponseEntity<String>("INDEX 2", HttpStatus.OK);
}

   - 404가 뜬다.


 4) Class 추가 - 됨!!

  - 테스트 코드

public enum TestCd {
BOOK, NOTE;
}

  - 잘 추가/수정 된다.


 5) 지역변수 -> 됨!

@RequestMapping(value = "/")
public ResponseEntity<String> test() {

String testValue = "test test test";
LOG.info("test : {} ", testValue);

return new ResponseEntity<String>("INDEX", HttpStatus.OK);
}

 - 잘 추가/수정 된다.



관련문서

 - https://github.com/spring-projects/spring-loaded

 - https://andromedarabbit.net/spring-loaded%EC%99%80-gradle%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%ED%95%AB%EC%8A%A4%EC%99%91-%EC%A7%80%EC%9B%90%ED%95%98%EA%B8%B0/

 - http://www.holaxprogramming.com/2015/05/29/spring-boot-and-loaded/





업무 중에 엑셀 다운로드 기능에 "암호 걸기"가 필요해짐.


1. pom.xml에 필요한 dependencyt 추가

<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.16</version>
</dependency>


2. 샘플

 - 아래와 같이 설정해주면 잘 나온다.

EncryptionInfo encryptionInfo = new EncryptionInfo(EncryptionMode.agile);
Encryptor encryptor = encryptionInfo.getEncryptor();
encryptor.confirmPassword(password);


 - 결과


3. 전체소스

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionMode;
import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;

import java.io.*;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class ExcelTest {

@Test
public void 엑셀파일에_암호걸기() {
final String password = "1234";
final String savePath = "c:\\temp\\excel2.xlsx";

List<String> tmpData = Arrays.asList("Java", "JavaScript", "Python", "GoLang");

try (Workbook wb2 = new XSSFWorkbook();
ByteArrayOutputStream fileOut = new ByteArrayOutputStream();
FileOutputStream fos = new FileOutputStream(savePath);) {

Sheet sheet1 = wb2.createSheet("Sheet1");

//Write Excel File
for (int i = 0; i < tmpData.size(); i++) {
String data = tmpData.get(i);

Row row = sheet1.createRow(i);
Cell cell = row.createCell(0);

cell.setCellValue(data);

}

wb2.write(fileOut);

InputStream filein = new ByteArrayInputStream(fileOut.toByteArray());
OPCPackage opc = OPCPackage.open(filein);

POIFSFileSystem fileSystem = new POIFSFileSystem();

EncryptionInfo encryptionInfo = new EncryptionInfo(EncryptionMode.agile);
Encryptor encryptor = encryptionInfo.getEncryptor();
encryptor.confirmPassword(password);

opc.save(encryptor.getDataStream(fileSystem));
fileSystem.writeFilesystem(fos);

log.info("Create Excel File!!");

} catch (IOException e) {
log.error(e.getMessage(), e);

} catch (InvalidFormatException e) {
log.error(e.getMessage(), e);

} catch (GeneralSecurityException e) {
log.error(e.getMessage(), e);
}

}

}


1. 현상

 - 아래와 같은 소스를 사용할 경우

@PropertySource(value = {"classpath:common.properties"})

 - 아래와 같이 호출할 경우,한글이 깨진다.

@Autowired
private Environment env;


private void test(){
env.getRequiredProperty("test");
}


2. 해결법

@PropertySource(value = {"classpath:common.properties"}, encoding = "UTF-8")


간단한 해결 법이지만 기억하기 위해 기록해 놓는다.

1. 현상

 - Spring boot 1.5.14 로 프로젝트를 세팅하는 중 에러 발생.

 - Lombok Annotation 적용해놓은 class에서 컴파일 에러

 - Github 이슈함 검색... 버그...


@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sample {
private String test1;
private String test2;

}


2. 해결법

 - @NoArgsConstructor 를 @Data 보다 위에 선언해야한다.


@NoArgsConstructor
@AllArgsConstructor
@Data
public class Sample {

private String test1;
private String test2;

}


* 관련 문서

https://github.com/rzwitserloot/lombok/issues/1703



1. Filter

public interface Filter {
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain);
}

 1) Filter란?

  - J2EE 표준 스팩

 2) init()

  - 필터 인스턴스 초기화

 3) doFilter()

  - 전/후 처리

 4) destroy()

  - 필터 인스턴스 종료

 

2. Interceptor

 - 소스

public interface HandlerInterceptor {


@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;

@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;

@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;

}

 1) Interceptor란?

  - Spring Framework에서 지원하는 스팩


 2) preHandle()  

  - Controller 전에 호출된다.

  - return 값이 false일 경우 controller 및 다음 postHandle 역시 호출되지 않는다.

  - 인증처리 등을 추가하면 좋다.

  - 요청 로그를 남기기 좋다.


 3) postHandle()

  - Controller가 호출되고 난 뒤 호출된다.

  - Request 값을 변경하는데 사용하면 좋다.

  - View에 공통적으로 값을 추가할 때 사용하면 좋다.


 4) afterCompletion()

  - 뷰 렌더링까지 완료된 후에 호출된다.


3. Filter와 Interceptor 차이점

 1) Filter는 Dispatcher servlet 앞단에서 처리한다. Interceptor는 Controller 시작부터해서 메소드별로 라이프 사이클에 맞게 호출된다.

 2) Interceptor와 다르게 Filter는 web.xml에 설정을 추가한다.



1. Content Type

 - 파일명에 따라 ContentType을 설정한다.


ObjectMetadata objMeta = new ObjectMetadata();
objMeta.setContentType(Mimetypes.getInstance().getMimetype(saveFileNm));


2. Content Length

 - byte length를 추가한다.

ObjectMetadata objMeta = new ObjectMetadata();

byte[] bytes = IOUtils.toByteArray(targetIS);
objMeta.setContentLength(bytes.length);

ByteArrayInputStream byteArrayIs = new ByteArrayInputStream(bytes);

PutObjectRequest putObjReq = new PutObjectRequest(bucketName, key, byteArrayIs, objMeta);
s3client.putObject(putObjReq);

 - 해당 소스처리를 안할 경우 아래와 같은 warning 메시지가 뜬다.

[WARN ] c.a.services.s3.AmazonS3Client:1714 - No content length specified for stream data.  Stream contents will be buffered in memory and could result in out of memory errors.


3. 임시파일로 업로드

 - 서버 내 파일을 저장하지않고, 바로 S3에 업로드할 때 사용하면 좋다.

File file = File.createTempFile("test", ".txt");
file.deleteOnExit();


해당 내용은 https://github.com/java-json-tools/json-schema-validator 를 사용하면서 적은 내용입니다.


1. NodeType

{.... "type": "string or array...."}


 - NodeType 목록

 ARRAY("array"),

 BOOLEAN("boolean"),

 INTEGER("integer"),

 NULL("null"),

 NUMBER("number"),

 OBJECT("object"),

 STRING("string")


 - 해당 Library에서는 "com.github.fge.jackson.NodeType"를 참고하시면 편하게 코딩할 수 있다.


2. minItems, maxItems

{.... "minItems":0, "maxItems":2....}


 - 아이템 수롤 지정할 수 있다.

 - type이 array일 경우 해당 옵션으로 아이템 수를 체크할 수 있다.


AWS, java, S3

1. 기존 메소드


AmazonS3 s3Client = new AmazonS3Client(new BasicAWSCredentials(accessKey, secretKey));

 - 해당 메소드 수석을 참고하면 아래와 같다.

/**
* Constructs a new Amazon S3 client using the specified AWS credentials to
* access Amazon S3.
*
* @param awsCredentials
* The AWS credentials to use when making requests to Amazon S3
* with this client.
*
* @see AmazonS3Client#AmazonS3Client()
* @see AmazonS3Client#AmazonS3Client(AWSCredentials, ClientConfiguration)
* @deprecated use {@link AmazonS3ClientBuilder#withCredentials(AWSCredentialsProvider)}
*/
@Deprecated
public AmazonS3Client(AWSCredentials awsCredentials) {
this(awsCredentials, configFactory.getConfig());
}


2. 변경된 메소드

BasicAWSCredentials creds = new BasicAWSCredentials(accessKey, secretKey);
AmazonS3 s3client = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(creds)).build();


 - 리전을 추가할 경우

.withRegion(Regions.valueOf("AP_NORTHEAST_2"))


'프로그래밍 > JAVA' 카테고리의 다른 글

AWS s3 upload source Tip  (0) 2018.06.05
json schema validator 사용하기 - 02  (0) 2018.05.23
AmazonS3Client, deprecated!!!  (0) 2018.05.21
json schema validator 사용하기 - 01  (0) 2018.04.18
시스템 종료 후 File 삭제  (0) 2018.04.12
Java에서 Tuple 사용하기.  (0) 2018.04.11

해당 내용은 공부하면서 간단하게 만들어본 블록체인 입니다.


1. Block 만들기

 - BlockChain을 구성하는 Block 을 생성한다,

 - Block은 BlockChain 내에서 고유한 Hash(Digital Signature)값을 가지고 있다.

 - Block 내에는 이전 Block의 Hash 값과 데이터들이 포함되어 있다.


 1) 소스코드

package kr.geun.o.bc.basic;

import java.util.Date;

/**
* Block Class
*
* @author akageun
*/
public class Block {

private String hash;
private String previousHash;

private String data;
private long timeStamp;

private Block() {

}

/**
* Block Constructor
*
* @param data
* @param previousHash
*/
public Block(String data, String previousHash) {
this.data = data;
this.previousHash = previousHash;

this.timeStamp = new Date().getTime();
}

/**
* get previousHash
*
* @return
*/
public String getPreviousHash() {
return previousHash;
}

/**
* get Hash
* - digital signature
*
* @return
*/
public String getHash() {
return hash;
}

/**
* get TimeStamp
*
* @return
*/
public long getTimeStamp() {
return timeStamp;
}

/**
* get Data
*
* @return
*/
public String getData() {
return data;
}
}

2. Hash(Digital Signature) 생성 메소드 만들기

 - Sha256을 사용.

package kr.geun.o.bc.basic;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* BlockChain Utils
*
* @author akageun
*/
public class BcUtils {

/**
* Hash 생성
*
* @param inputValues
* @return
*/
public static String generateHash(String... inputValues) {
try {
StringBuffer sb = new StringBuffer();
for (String inputValue : inputValues) {
sb.append(inputValue);
}

String input = sb.toString();

MessageDigest digest = MessageDigest.getInstance("SHA-256");

byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer();

for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if (hex.length() == 1) {
hexString.append('0');
}

hexString.append(hex);
}
return hexString.toString();

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);

} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}


3. Hash 계산로직 추가.

 - 위 BcUtils 를 이용하여 Block 안 Hash값을 만드는 로직을 추가해보자.

 - Block 내 메소드 추가(소스코드)

/**
* Hash값 계산하기!
*
* @return
*/
public String calculateHash() {
return BcUtils.generateHash(previousHash, Long.toString(timeStamp), data);
}

 - 생성자 내에 해당 메소드 호출 추가


/**
* Block Constructor
*
* @param data
* @param previousHash
*/
public Block(String data, String previousHash) {
this.data = data;
this.previousHash = previousHash;

this.timeStamp = new Date().getTime();
this.hash = calculateHash(); //추가!!
}


4. Genesis Block 호출하기!

 - 첫번째 블럭 생성

Block genesisBlock = new Block("이건 Genesis Block 입니다.", "0");
System.out.println("Genesis Block Hash : " + genesisBlock.getHash());

 - 메인 메소드를 만들어 호출하면 아래와 같은 결과를 얻을 수 있다.(호출할 때마다 Hash값이 달라진다.)

Genesis Block Hash : f57e2c72feba62234118d1356799c367a802c342156a43ac2774698bb316941e


 - 몇개를 더 추가해서 출력해보면.... 아래와 같다.

Block genesisBlock = new Block("이건 Genesis Block 입니다.", "0");
System.out.println("Genesis Block Hash : " + genesisBlock.getHash());

Block secBlock = new Block("이건 두번째 블럭 입니다.", genesisBlock.getHash());
System.out.println("Second Block Hash : " + secBlock.getHash());

Block thirdBlock = new Block("이건 세번째 블럭 입니다.", secBlock.getHash());
System.out.println("Hash for block 3 : " + thirdBlock.getHash());

Genesis Block Hash : f57e2c72feba62234118d1356799c367a802c342156a43ac2774698bb316941e

Second Block Hash : 082902f4d78eab658b964d9743532178073da2b9d529b00752fa5588034fb2a2

Hash for block 3 : 97a821109da7ed12fc3263452670d42b392d71772dd70b81720a23e1d898bf15


5. 블록체인 만들기.

 - 간단하게 static List를 만들어서 블럭들을 생성해 보자.

/**
* BlockChain 메인 클래스
*
* @author akageun
*/
public class BlockChain {

public static List<Block> BLOCK_CHAIN = new LinkedList<>();

public static void main(String[] args) {

BLOCK_CHAIN.add(new Block("이건 Genesis Block 입니다.", "0"));
BLOCK_CHAIN.add(new Block("이건 두번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.add(new Block("이건 세번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));

for (Block block : BLOCK_CHAIN) {
System.out.println("=========");

System.out.println("hash : " + block.getHash());
System.out.println("previousHash : " + block.getPreviousHash());
System.out.println("timeStamp : " + block.getTimeStamp());
System.out.println("Data : " + block.getData());
}

System.out.println("=========");
}

}

 - 결과

=========

hash : 0f7e40c1f99be348a2a600f678ef6e64136d4ea6dc3d7457561e572129c628c9

previousHash : 0

timeStamp : 1532238023070

Data : 이건 Genesis Block 입니다.

=========

hash : b3a2226d4dafb77bfca41c2d792e7590df544e34450bfd5ecd69e71124f6baea

previousHash : 0f7e40c1f99be348a2a600f678ef6e64136d4ea6dc3d7457561e572129c628c9

timeStamp : 1532238023086

Data : 이건 두번째 블럭 입니다.

=========

hash : 57caa0507cbed37fbde89a4d80b2ea68450cff3698fba56856858093d8ac6982

previousHash : b3a2226d4dafb77bfca41c2d792e7590df544e34450bfd5ecd69e71124f6baea

timeStamp : 1532238023086

Data : 이건 세번째 블럭 입니다.



6. 무결성 검사

 - 계산한 Hash값이 같은지, Block 내 PreviousBlock 과 실제 이전 블럭 Hash값이 같은지를 검증하는 로직

 - 구현 소스

/**
* 블록 검증
*
*
* @return
*/
private static boolean isValidBlockChain() {
Block currentBlock;
Block previousBlock;

for (int i = 1; i < BLOCK_CHAIN.size(); i++) { //GenesisBlock 은 제외한다.
currentBlock = BLOCK_CHAIN.get(i);
previousBlock = BLOCK_CHAIN.get(i - 1);

if (currentBlock.getHash().equals(currentBlock.calculateHash()) == false) { //만들어 놓은 Hash값과 계산한 Hash값이 같은지 검증!
System.out.println("Not Equals Current Block Hash!!");
return false;
}

if (currentBlock.getPreviousHash().equals(previousBlock.getHash()) == false) {
System.out.println("Not Equals Previous Block Hash!!");
return false;
}

}

return true;
}


7. 채굴!(마이닝)

 - 작업증명'(Proof-of-Work) : BlockChain의 Block Hash는 `Difficulty`에 따라 선택된  Target 데이터 규격을 만족해야 한다.

  - Block Class 수정( nonce 값 추가)

private int nonce;

/**
* get nonce
*
* @return
*/
public int getNonce() {
return nonce;
}

  - Hash 값 연산하는 부분에 nonce 값 추가

/**
* Hash값 계산하기!
*
* @return
*/
public String calculateHash() {
return BcUtils.generateHash(previousHash, Long.toString(timeStamp), Integer.toString(nonce) , data);
}

  - 채굴 함수 추가

/**
* Mining
*/
public void mineBlock() {
char[] targetChar = new char[BlockChain.DIFFICULTY];
Arrays.fill(targetChar, '0');
String target = String.valueOf(targetChar);

while (hash.substring(0, BlockChain.DIFFICULTY).equals(target) == false) {
nonce++;
hash = calculateHash();
}

System.out.println("Block Mined!!! : " + hash);
}

  - 난이도 추가

public static int DIFFICULTY = 5;

  - 무결성 검증 코드 내 로직 추가

if (currentBlock.getHash().substring(0, DIFFICULTY).equals(hashTarget) == false) {
System.out.println("This block hasn't been mined");
return false;
}

  - 채굴해보기

BLOCK_CHAIN.add(new Block("이건 Genesis Block 입니다.", "0"));
BLOCK_CHAIN.get(0).mineBlock();

BLOCK_CHAIN.add(new Block("이건 두번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(1).mineBlock();

BLOCK_CHAIN.add(new Block("이건 세번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(2).mineBlock();

BLOCK_CHAIN.add(new Block("이건 네번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(3).mineBlock();


 - 결과

Block Mined!!! : 00000fdd8a35c69f5925051899635e1704af98bdd7da3504ed9bc36b5be30901

Block Mined!!! : 00000d8e9a9e735e8f7cbdae41458379ab8b4ac3de7826671eb10f2b7def35bd

Block Mined!!! : 000005dd4dc19710d82dda43d230ec6eabc8041af0fc0a489279802d172f8d41

Block Mined!!! : 000006ff67b1e5735d8891e3e389620f541aa3467e73471a4e2d7b11f841ed3a

=========

hash : 00000fdd8a35c69f5925051899635e1704af98bdd7da3504ed9bc36b5be30901

previousHash : 0

timeStamp : 1532240373740

Data : 이건 Genesis Block 입니다.

nonce : 465518

=========

hash : 00000d8e9a9e735e8f7cbdae41458379ab8b4ac3de7826671eb10f2b7def35bd

previousHash : 00000fdd8a35c69f5925051899635e1704af98bdd7da3504ed9bc36b5be30901

timeStamp : 1532240374917

Data : 이건 두번째 블럭 입니다.

nonce : 1603790

=========

hash : 000005dd4dc19710d82dda43d230ec6eabc8041af0fc0a489279802d172f8d41

previousHash : 00000d8e9a9e735e8f7cbdae41458379ab8b4ac3de7826671eb10f2b7def35bd

timeStamp : 1532240378568

Data : 이건 세번째 블럭 입니다.

nonce : 2130959

=========

hash : 000006ff67b1e5735d8891e3e389620f541aa3467e73471a4e2d7b11f841ed3a

previousHash : 000005dd4dc19710d82dda43d230ec6eabc8041af0fc0a489279802d172f8d41

timeStamp : 1532240383239

Data : 이건 네번째 블럭 입니다.

nonce : 504539

=========

This Blockchain is Valid : true



전체소스

1. Block 소스

package kr.geun.o.bc.basic;

import java.util.Arrays;
import java.util.Date;

/**
* Block Class
*
* @author akageun
*/
public class Block {

private String hash;
private String previousHash;

private String data;
private long timeStamp;

private int nonce;

private Block() {

}

/**
* Block Constructor
*
* @param data
* @param previousHash
*/
public Block(String data, String previousHash) {
this.data = data;
this.previousHash = previousHash;

this.timeStamp = new Date().getTime();
this.hash = calculateHash();
}

/**
* Hash값 계산하기!
*
* @return
*/
public String calculateHash() {
return BcUtils.generateHash(previousHash, Long.toString(timeStamp), Integer.toString(nonce), data);
}

/**
* Mining
*/
public void mineBlock() {
char[] targetChar = new char[BlockChain.DIFFICULTY];
Arrays.fill(targetChar, '0');
String target = String.valueOf(targetChar);

while (hash.substring(0, BlockChain.DIFFICULTY).equals(target) == false) {
nonce++;
hash = calculateHash();
}

System.out.println("Block Mined!!! : " + hash);
}

/**
* get previousHash
*
* @return
*/
public String getPreviousHash() {
return previousHash;
}

/**
* get Hash
* - digital signature
*
* @return
*/
public String getHash() {
return hash;
}

/**
* get TimeStamp
*
* @return
*/
public long getTimeStamp() {
return timeStamp;
}

/**
* get Data
*
* @return
*/
public String getData() {
return data;
}

/**
* get nonce
*
* @return
*/
public int getNonce() {
return nonce;
}
}


2. BcUtils

package kr.geun.o.bc.basic;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* BlockChain Utils
*
* @author akageun
*/
public class BcUtils {

/**
* Hash 생성
*
* @param inputValues
* @return
*/
public static String generateHash(String... inputValues) {
try {
StringBuffer sb = new StringBuffer();
for (String inputValue : inputValues) {
sb.append(inputValue);
}

String input = sb.toString();

MessageDigest digest = MessageDigest.getInstance("SHA-256");

byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer();

for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if (hex.length() == 1) {
hexString.append('0');
}

hexString.append(hex);
}
return hexString.toString();

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);

} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}


3. BlockChain

package kr.geun.o.bc.basic;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
* BlockChain 메인 클래스
*
* @author akageun
*/
public class BlockChain {

public static List<Block> BLOCK_CHAIN = new LinkedList<>();

public static int DIFFICULTY = 5;

public static void main(String[] args) {

BLOCK_CHAIN.add(new Block("이건 Genesis Block 입니다.", "0"));
BLOCK_CHAIN.get(0).mineBlock();

BLOCK_CHAIN.add(new Block("이건 두번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(1).mineBlock();

BLOCK_CHAIN.add(new Block("이건 세번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(2).mineBlock();

BLOCK_CHAIN.add(new Block("이건 네번째 블럭 입니다.", BLOCK_CHAIN.get(BLOCK_CHAIN.size() - 1).getHash()));
BLOCK_CHAIN.get(3).mineBlock();

for (Block block : BLOCK_CHAIN) {
System.out.println("=========");

System.out.println("hash : " + block.getHash());
System.out.println("previousHash : " + block.getPreviousHash());
System.out.println("timeStamp : " + block.getTimeStamp());
System.out.println("Data : " + block.getData());
System.out.println("nonce : " + block.getNonce());
}

System.out.println("=========");

System.out.println("This Blockchain is Valid : " + isValidBlockChain());

}

/**
* 블록 검증
*
*
* @return
*/
private static boolean isValidBlockChain() {
Block currentBlock;
Block previousBlock;

char[] target = new char[BlockChain.DIFFICULTY];
Arrays.fill(target, '0');

String hashTarget = String.valueOf(target);

for (int i = 1; i < BLOCK_CHAIN.size(); i++) { //GenesisBlock 은 제외한다.
currentBlock = BLOCK_CHAIN.get(i);
previousBlock = BLOCK_CHAIN.get(i - 1);

if (currentBlock.getHash().equals(currentBlock.calculateHash()) == false) { //만들어 놓은 Hash값과 계산한 Hash값이 같은지 검증!
System.out.println("Not Equals Current Block Hash!!");
return false;
}

if (currentBlock.getPreviousHash().equals(previousBlock.getHash()) == false) {
System.out.println("Not Equals Previous Block Hash!!");
return false;
}

if (currentBlock.getHash().substring(0, DIFFICULTY).equals(hashTarget) == false) {
System.out.println("This block hasn't been mined");
return false;
}

}

return true;
}
}



참고 페이지

 - https://medium.com/programmers-blockchain/create-simple-blockchain-java-tutorial-from-scratch-6eeed3cb03fa

 - http://guruble.com/java-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8blockchain/

'BlockChain' 카테고리의 다른 글

Java로 배워보는 BlockChain  (0) 2018.05.02
블록체인 관련 기본 용어정리.  (0) 2018.02.23

+ Recent posts