(작성일 기준으로 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.


해당 내용은 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. 읽기 좋은 소스를 만들자.

 - 혼자 개발하는 프로젝트가 아닌 이상, 다른 사람이 내 코드를 보고 빠르게 이해할 수 있어야 한다.

 - var str_1 = 'TEST_STRING_1'; var str_2 = 'TEST_STRING_2'; 정말... 최악이다.


2. 불필요한 전역변수 사용

 - 특정 메소드에서만 사용하는 변수를 전역변수로 선언해 놓을 경우 소스 상단이 굉장히 지저분해 질 수 있다. 


3. 적당한 주석

 - 주석 없이 작성된 코드를 한번에 이해할 수 있으면, 그 코드는 굉장히 잘 작성된 코드일 것이다. 

 - 허나 실제로는 그렇게 하기 힘들다. 그러니 코드를 읽는데 최대한 방해되지 않게 해야 한다고 본다. 

 - 주석보다는 명확한 변수명 또는 메소드명을 작성하여 개발 하는 걸 추천한다. 

 - 추가적으로 업무 중에는 여러 이슈들에 의해서 예외처리, 하드코딩 등이 들어갈 수 있다. 그럼 어떤 이슈 때문에 작성된 코드고, 이슈 관리를 한다면 이슈 번호를 추가하여 다른 담당자가 해당 이슈를 찾기 쉽게 작성해 놓으면 업무하는데 있어서 굉장히 편하다.


4. 코드 컨벤션

 - 여럿이 여러 컨벤션을 사용하여 하나의 프로젝트를 진행한다면 최악의 사태가 발생할 수 있다. 

 - A라는 개발자는 helloWorld 라고 변수를 만들고, B라는 개발자는 hello_world 로 만들어 사용한다면, 같은 목적을 가진 변수가 두개 존재하게 되고, 재활용을 못하게 된다.

 - 프로젝트를 진행하기전 camelCase( 각 단어의 첫 글자는 대문자를 사용(첫 단어는 소문자 사용)), underscores(각 단어를 밑줄로 구분한다) 등의 표기법을 잡고 시작하는걸 추천한다.

 - 또 formatter를 하나 만들어 공통된 코드 포멧을 사용할 수 있도록 하면 더더욱 좋다.


5. 리팩토링

 - 아 다음에 리팩토링해야지... 하는 생각은 버려라!

 - 조금씩이라도 꾸준히 리팩토링 해야한다.

 - 미사용 소스는 과감하게 삭제하고, 중복코드 또한 정리하는 걸 추천한다.

 - 추가 스팩이 발생하더라도 소스가 깔끔하게 정리되어 있다면 부담없이 추가 할 수 있을 것이다.


6. 코드리뷰

 - 혼자 작성한 코드보다는 여럿의 의견을 듣고 수정하는 코드가 더 좋을 수 밖에 없다.

 - 단, 코드리뷰시에 "비난"보다는 의견을 나누는 형태여야, 코드리뷰에 대한 스트레스가 없을 것이다.


7. 잘된 소스 보기

 - Spring 프로젝트 소스, Apache 프로젝트 소스 등을 틈틈히 읽어보다.

 - 아! 이렇게도 소스를 만들 수 있구나 싶은 부분들이 굉장히 많다.

 - 그러다보면 디자인 패턴은 기본적으로 배울 수 있게 되고, 여러 부분에서 도움이 많이 될 것이다.


 

좋은 코드 작성하는것에 대한 정답은 없는 것 같다. 단지 개발을 하면서 느낀 부분을 짧게 정리해 봤다.

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

좋은 코드 작성하기.  (0) 2018.05.01
HSTS(HTTP Strict Transport Security) 개념과 설정  (0) 2018.03.08



1. json-schema-validator란?

 - xml의 DTD 와 유사함.

 - Json 스키마(http://json-schema.org/) 포멧이 유효한지 확인해줌.

 - 웹사이트에서 해당 내용을 테스트해볼 수 있다.(링크)


2. library

 - 기존에 jackson을 많이 사용하므로, fasterxml에서 제공하는 jsonSchema 라이브러리를 사용하려고함.

 1) pom.xml

<dependency>
<groupId>com.github.java-json-tools</groupId>
<artifactId>json-schema-validator</artifactId>
<version>2.2.8</version>
</dependency>


3. 간단한 Json 설명

{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "/etc/fstab", /* JSON에 대한 제목 */
"description": "JSON representation of /etc/fstab", /* JSON에 대한 설명 */
"type": "object", /* 해당 Json의 최상위 객체 타입, http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1 참고 */
"properties": {
"swap": {
"$ref": "#/definitions/mntent" /* 하단 definitions내용을 참조 합니다. */
}
},
"patternProperties": {
"^/([^/]+(/[^/]+)*)?$": {
"$ref": "#/definitions/mntent"
}
},
"required": /* 필수 값에 대한 속성 정의 */ [
"/",
"swap"
],
"additionalProperties": false,
"definitions": {
"mntent": {
"title": "mntent",
"description": "An fstab entry",
"type": "object", /* 객체 타입 */
"properties": {
"device": {
"type": "string"
},
"fstype": {
"type": "string"
},
"options": {
"type": "array",
"minItems": 1, /* 아이템의 최소 단위 */
"items": {
"type": "string"
}
},
"dump": {
"type": "integer",
"minimum": 0,
"maximum": 0 /* 아이템의 최대 단위 */
},
"fsck": {
"type": "integer",
"minimum": 0
}
},
"required": [
"device",
"fstype"
],
"additionalItems": false
}
}
}



4. 샘플 소스

 1) Basic ( https://github.com/java-json-tools/json-schema-validator/blob/master/src/main/java/com/github/fge/jsonschema/examples/Example1.java)

 - 소스

final String urlPrefix = "https://raw.githubusercontent.com/java-json-tools/json-schema-validator/"
+ "master/src/main/resources/com/github/fge/jsonschema/examples/";

final JsonNode fstabSchema = JsonLoader.fromURL(new URL(urlPrefix + "fstab.json"));
final JsonNode good = JsonLoader.fromURL(new URL(urlPrefix + "fstab-good.json"));
final JsonNode bad = JsonLoader.fromURL(new URL(urlPrefix + "fstab-bad.json"));
final JsonNode bad2 = JsonLoader.fromURL(new URL(urlPrefix + "fstab-bad2.json"));

final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();

final JsonSchema schema = factory.getJsonSchema(fstabSchema);

ProcessingReport report;

report = schema.validate(good); //SUCCESS
print(report);

report = schema.validate(bad); //FAILURE
print(report);

report = schema.validate(bad2); //FAILURE
print(report);

 - 결과


 

 2) message custom(https://github.com/java-json-tools/json-schema-validator/blob/master/src/main/java/com/github/fge/jsonschema/examples/Example8.java)

 - 소스(valid)

final JsonNode customSchema = JsonLoader.fromURL(new URL(URL_PREFIX + "custom-fmt.json"));
final JsonNode good = JsonLoader.fromURL(new URL(URL_PREFIX + "custom-fmt-good.json"));
final JsonNode bad = JsonLoader.fromURL(new URL(URL_PREFIX + "custom-fmt-bad.json"));

//@formatter:off
final Library library = DraftV4Library
.get()
.thaw()
.addFormatAttribute("uuid", UUIDFormatAttribute.getInstance())
.freeze();
//@formatter:on

final String key = "invalidUUID";
final String value = "Not a valid UUID";
final MessageSource source = MapMessageSource.newBuilder().put(key, value).build();
final MessageBundle bundle = MessageBundles.getBundle(JsonSchemaValidationBundle.class).thaw().appendSource(source).freeze();

final ValidationConfiguration cfg = ValidationConfiguration.newBuilder().setDefaultLibrary("http://my.site/myschema#",
library).setValidationMessages(bundle).freeze();

final JsonSchemaFactory factory = JsonSchemaFactory.newBuilder().setValidationConfiguration(cfg).freeze();

final JsonSchema schema = factory.getJsonSchema(customSchema);

ProcessingReport report;

report = schema.validate(good);
print(report);

report = schema.validate(bad);
print(report);


  - 소스(UUIDFormatAttribute)

private static class UUIDFormatAttribute extends AbstractFormatAttribute {
private static FormatAttribute INSTANCE = new UUIDFormatAttribute();

private UUIDFormatAttribute() {
super("uuid", NodeType.STRING);
}

public static FormatAttribute getInstance() {
return INSTANCE;
}

@Override
public void validate(final ProcessingReport report, final MessageBundle bundle, final FullData data) throws ProcessingException {
final String value = data.getInstance().getNode().textValue();
try {
UUID.fromString(value);

} catch (IllegalArgumentException ignored) {
report.error(super.newMsg(data, bundle, "invalidUUID").put("input", value));
}
}
}

  - 결과


5. 전체소스



 * 참고링크

  - https://github.com/java-json-tools/json-schema-validator

  - http://json-schema.org/latest/json-schema-validation.html

  - http://json-schema-validator.herokuapp.com/

  - http://json-schema.org/example2.html

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

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
try-catch-resources  (0) 2018.04.11

+ Recent posts