캠핑과 개발

spring을 사용하다 보면 요청 url 매핑을 정의하게 되는데 여기에 사용되는 url 매핑 방법이 Ant-Style path patterns이라고 한다.

?, *, **과 무슨 뜻인지 알고 쓰자.


? : 1 개의 문자와 매칭 

* : 0개 이상의 문자와 매칭

** : 0개 이상의 디렉터리와 파일 매칭

스프링에서 Exception 처리를 하기 위한 방법은 여러 가지가 있지만 @ControllerAdvice 에 대해서 간단하게 설정 팁을 설명합니다.


@ControllerAdvice은 spring Web에서 발생하는 Exception을 공통된 처리하기 위한 방법입니다.

@ExceptionHandler와 다른 점은 @ExceptionHandler는 컨트롤러 단위에서 처리하고,  @ControllerAdvice는 전역으로 Exception을 처리할 수 있다라는 겁니다.

전자정부프레임워크 기반으로  @ControllerAdvice를 처리하는 과정에서 Exception을 catch하지 못하는 문제가 발생하여 알아보던 중 

<mvc:annotation-driven />과 연관이 있다라는걸 알았습니다.


먼저 @ControllerAdvice를 사용하기 위해서는 <anotation-driven />이 설정되어 있아야 합니다.

하지만  <anotation-driven />을 사용하지 못하는 상황이 발생 할 경우는 별도로 @ControllerAdvice를 사용할 수 있도록 처리해야줘 하는데.

그러한 경우 다음과 같이 bean을 별도로 등록해주면 됩니다.


1
2
<bean id="exceptionHandlerExceptionResolver" 
    class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver" />
cs



maven jetty plugin 옵션 중에 scanIntervalSeconds 라는 옵션을 줘서 파일이 변경됬을 경우 일정시간 마다 서버를 리스타트 하는 기능이 있다.

이것과 비슷하게 지정된 특정 디렉토리에 파일이나 디렉토리가 생성, 삭제, 변경 되는 것을 모니터링 하는 클래스를 맨들어 보았다.

이것을 위해서 Jetty util 패키지에 있는 Scanner 클래스를 사용했다.

maven 사용자는 다음과 같은 dependency 를 추가한다.

  1. <dependency>
  2.     <groupId>org.eclipse.jetty</groupId>
  3.     <artifactId>jetty-util</artifactId>
  4.     <version>8.0.1.v20110908</version>
  5. </dependency>


maven 사용안하는 사람은 jetty-util-8.0.1.v20110908.jar 요파일을 클래스 패스에 추가 시켜 주면 된다. 

jetty-util-8.0.1.v20110908.jar


FileChangeScanner 클래스는 다음과 같다. DiscreteListener 클래스의 각각의 경우에 대해서 원하는 엑션을 취해주면 된다.

  1. package scanner;
  2.  
  3. import java.io.File;
  4. import java.io.FileFilter;
  5. import java.util.ArrayList;
  6. import java.util.List;
  7. import java.util.Timer;
  8. import java.util.TimerTask;
  9.  
  10. import org.eclipse.jetty.util.Scanner;
  11.  
  12. public class FileChangeScanner {
  13.     private Scanner scanner;
  14.  
  15.     public FileChangeScanner(String targetDir){
  16.         List<File> scanFiles = new ArrayList<File>();
  17.          
  18.         searchSubDirs(targetDir, scanFiles);
  19.          
  20.         scanner = new Scanner();
  21.         scanner.setScanInterval(1);          // 1초 간격으로 변경사항 스캔
  22.         scanner.setScanDirs(scanFiles);
  23.         scanner.setReportExistingFilesOnStartup(false);
  24.          
  25.         scanner.addListener(new Scanner.DiscreteListener() {
  26.             public void fileRemoved(String filename) throws Exception {
  27.                 System.out.println(filename + " is deleted");
  28.             }
  29.              
  30.             public void fileChanged(String filename) throws Exception {
  31.                 System.out.println(filename + " is changed");
  32.             }
  33.              
  34.             public void fileAdded(String filename) throws Exception {
  35.                 File f = new File(filename);
  36.                 if(f.isDirectory()) scanner.addScanDir(f);
  37.                  
  38.                 System.out.println(filename + " is added");
  39.             }
  40.         });
  41.     }
  42.      
  43.     public void start(){
  44.         try {
  45.             scanner.start();
  46.         } catch (Exception e) {
  47.             new RuntimeException(e);
  48.         }
  49.     }
  50.      
  51.     /**
  52.      * 하위 디렉토리들 찾기
  53.      * @param targetDir
  54.      * @param dirs
  55.      * @return
  56.      */
  57.     private List<File> searchSubDirs(String targetDir, final List<File> dirs){
  58.         File target = new File(targetDir);
  59.         target.listFiles(new FileFilter() {
  60.             public boolean accept(File file) {
  61.                 if( file.isDirectory() ) {
  62.                     dirs.add(file);
  63.                     searchSubDirs(file.toString(), dirs);
  64.                 }
  65.                 return false;
  66.             }
  67.         });
  68.          
  69.         return dirs;
  70.     }
  71.  
  72.     public static void main(String[] args) throws Exception{
  73.         // c:\test 하위 폴더 및 파일 변경 감시
  74.         new FileChangeScanner("c:\\test").start();
  75.          
  76.         // 실행하자 마자 프로그램이 종료되기 때문에 프로그램 종료방지를 위해 타이머 생성
  77.         // WAS 환경에서 돌릴때는 타이머 생성할 필요없음
  78.         Timer timer = new Timer();
  79.         timer.scheduleAtFixedRate(new TimerTask() {
  80.             public void run() {}
  81.         }60*100060*1000 );
  82.     }
  83. }


출처 : http://stove99.tistory.com/61

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

Ant-style path patterns  (0) 2019.02.07
spring @ControllerAdvice 설정 팁  (0) 2019.02.02
Java ImageIO 이미지 성능을위한 5 가지 팁  (0) 2017.01.18
<mvc:annotation-driven>이 하는 일  (0) 2016.07.28
java 파일 읽고 쓰기  (0) 2016.07.05

JAVA에서 이미지를 그리기 위한 속도는 다른 언어보다 많이 느립니다.

아래는 몇가지 이미지 작업을 하면서 참고가 될만한 팁을 소개합니다.


1. 메모리에서 변환을 수행해도 작업 속도가 향상되지 않습니다.

2. ImageIO.write 이미지에 BufferedOutputStream을 사용하면 일부 시스템에서 약간의 개선이있는 것으로 보입니다.

3. ImageIO.write는 Indexed Colorspaces에 최적화되어 있으므로 이미지를 Index Colorspace로 변환 한 다음 쓸 때가 더 빠릅니다. 

다음은 몇 가지 샘플 코드입니다.


BufferedImage indexedImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
Graphics2D g = indexedImage.createGraphics();
g.drawImage(image, 0,0,null);
image=indexedImage;


4. JAI JPEG 디코딩은 ImageIO보다 훨씬 느립니다.

5. ImageIO.setUseCache (true)가 더 느립니다.

<mvc:annotation-driven>은 @MVC 스타일의 컨트롤러에서 자주 사용되는 validator, conversionService, messageConverter를 자동으로 등록해주는 간편 빈 설정용 태그이다.

이 태그에 의해서 자동으로 등록되는 기능들에 대해서는 3.0.1의 보강된 레퍼런스 문서에 다음과 같이 잘 나와있다.

  1. Support for Spring 3′s Type ConversionService in addition to JavaBeans PropertyEditors during Data Binding. A ConversionService instance produced by the org.springframework.format.support.FormattingConversionServiceFactoryBean is used by default. This can be overriden by setting the conversion-service attribute.
  2. Support for formatting Number fields using the @NumberFormat annotation
  3. Support for formatting Date, Calendar, Long, and Joda Time fields using the @DateTimeFormat annotation, if Joda Time is present on the classpath.
  4. Support for validating @Controller inputs with @Valid, if a JSR-303 Provider is present on the classpath. The validation system can be explicitly configured by setting the validator attribute.
  5. Support for reading and writing XML, if JAXB is present on the classpath.
  6. Support for reading and writing JSON, if Jackson is present on the classpath.

그런데 과연 이게 전부일까?

아니다.

<mvc:annotation-driven>이 등록해주는 빈이 한가지 더 있다. 바로 MappedInterceptor이다. 정확히 말하자면 모든 경로에 대해서 적용되는 ConversionServiceExposingInterceptor를 가진 MappedInterceptor이다.

아직 많은 개발자들에게는 MappedInterceptor도 3.0에서 새로 추가된 것이라 생소할 것이다. 기존 HandlerInterceptor는 핸들러 매핑의 프로퍼티로 등록된다. 이 때문에 두 가지 단점이 있는데, 첫 째는 핸들러 매핑 전략을 두 개 이상을 병행해서 사용하면 인터셉터를 일일히 핸들러 매핑마다 등록해줘야 한다는 점이다. 둘 째는 핸들러 매핑에 등록한 인터셉터는 그 핸들러 매핑이 처리하는 모든 요청에 적용된다는 점이다. URL에 따라 적용 인터셉터를 선별하려면 인터셉터 안에서 URL을 필터링 하는 작업을 해야 한다.

MappedInterceptor는 이 두가지 단점을 보완해주는 새로운 인터셉터 적용방식이다. MappedInterceptor를 빈으로 등록하고 path와 interceptor를 지정해주면 해당 경로패턴에 일치하는 URL에 대해서는 어떤 핸들러 매핑을 이용하든 상관없이 지정한 인터셉터를 적용해준다. 일종의 글로벌 스마트 인터셉터 레지스트리라고나 할까. 이 MappedInterceptor의 등장으로 앞으로는 핸들러 매핑에 인터셉터를 등록해서 사용할 일은 없을 듯 하다.

아무튼, <mvc:annotation-driven>는 뜬금없이 이 MappedInterceptor를 등록해버린다. 그것도 모든 경로에 대해서 적용한다. 추가된 인터셉터는 ConversionServiceExposingInterceptor라는 단순한 것인데, ConversionService 오브젝트를 request의 애트리뷰트에 등록해주는 간단한 작업을 수행한다. ConversionService라면 AnnotationMethodHandlerAdapter에 의해서 @ModelAttribute에 바인딩할 때 사용할 오브젝트이다. <mvc:annotation-driven>에서는 conversion-service 애트리뷰트에 의해서 재구성한 ConversionService를 지정할 수 있다.

<mvc:annotation-driven>는 바로 이 ConversionService를 request 스코프 애트리뷰트에 등록한다. 도데체 어디서 써먹으려는 것일까?

 

용도는 바로 <spring:eval>이다. 새롭게 추가된 <spring:eval>은 JSP에서 JSP EL을 대신해서 초강력 SpEL을 사용할 수 있도록 추가된 태그이다. 바로 이 <spring:eval>이 모델 오브젝트의 프로퍼티 값을 텍스트로 변환할 때 ConversionServiceExposingInterceptor에 의해서 등록된 ConversionService가 적용되는 것이다.

<mvc:annotation-driven>을 사용하지 않아서 ConversionService가 등록되지 않았다면 <spring:eval>은 기본적인 타입변환만 지원하는 StandardTypeConverter을 사용한다. 그보다는 ConversionServiceExposingInterceptor을 통해서 등록되는 FormattingConversionService가 훨씬 강력할 것이다. 모델에 @NumberFormat이나 @DateTimeFormat같은 포맷터가 달려있다면 <spring:eval>만으로도 이 포맷팅이 적용된다는 뜻이다.

기존에는 PropertyEditor가 적용되는 폼에서나 포맷을 가진 프로퍼티 바인딩을 적용할 수 있었다. 단순 출력을 필요로 하는 곳에서는 JSP EL을 사용해야 했기에 모델 오브젝트 프로퍼티의 포맷을 지정하려면 지저분한 <fmt:formatNumber> 따위의 태그를 사용해야만 했다. 그것도 몇가지 종류의 포맷밖에 지원이 안된다.

하지만 이제 3.0.1부터는 <spring:eval>을 사용해서 ConversionService의 막강한 타입변환-포맷팅 기능을 적용할 수 있다.

 

아직 레퍼런스나 API문서 등에도 공개되지 않은 내용인데.. 이런 중요한 정보를 미리미리 알려주지 않다니!

3.0.1의 막강한 @MVC와 ConversionService, Formatter, JSR-303 Bean Validation, Message Converter, AJAX 등등등을 활용한 편리한 웹 개발에 대해서 자세히 알고 싶다면 조만간 출시 예정인 스프링 3.0 책을 참고하도록… 



출처 : http://toby.epril.com/?p=989

java를 이용하여 텍스트 파일을 읽고 쓰는 예제입니다.

사용법은 매우 간단하지만 매번 찾아 보고 쓰게 되어 간단한 샘플을 포스팅합니다.


파일쓰기는 일반 텍스트형식과 바이너리 형식으로 쓰는 방법을 기록하며 순서는 다음과 같습니다.

1. 텍스트 파일 읽고 쓰기

2. 바이너리 파일 읽고 쓰기

3. 텍스트 파일 라인번호 읽기



1. 텍스트 파일 읽고 쓰기


  1. package test;
  2.  
  3. import java.io.BufferedReader;
  4. import java.io.BufferedWriter;
  5. import java.io.File;
  6. import java.io.FileNotFoundException;
  7. import java.io.FileReader;
  8. import java.io.FileWriter;
  9. import java.io.IOException;
  10.  
  11. public class FileTextWriteReadExample {
  12.    
  13.     public static void main(String[] args){
  14.        
  15.         File inFile = new File("C:\\Users\\Public""in.txt");
  16.         File outFile = new File("C:\\Users\\Public""in.txt");
  17.        
  18.         //==========================//
  19.         // 텍스트 파일 쓰기
  20.         //==========================//
  21.         BufferedWriter bw = null;
  22.         try {
  23.             bw = new BufferedWriter(new FileWriter(outFile));
  24.             bw.write("테스트 합니다.");
  25.             bw.newLine();
  26.             bw.write("테스트 합니다.1");
  27.             bw.flush();
  28.         } catch (IOException e) {
  29.             e.printStackTrace();
  30.         }finally {
  31.             if(bw != null) try {bw.close()} catch (IOException e) {}
  32.         }
  33.        
  34.         //==========================//
  35.         // 텍스트 파일 읽기
  36.         //==========================//
  37.         BufferedReader br = null;
  38.         try {
  39.             br = new BufferedReader(new FileReader(inFile));
  40.             String line;
  41.             while ((line = br.readLine()) != null) {
  42.                 System.out.println(line);
  43.             }
  44.         } catch (FileNotFoundException e) {
  45.             e.printStackTrace();
  46.         } catch (IOException e) {
  47.             e.printStackTrace();
  48.         }finally {
  49.             if(br != null) try {br.close()} catch (IOException e) {}
  50.         }
  51.     }
  52. }
  53.  


2. 바이너리 파일 읽기 쓰기

바이너리 파일은 알맞은 데이터형에 맞도록 쓰게되며 쓴 형식을 알지 못하면 읽는것 조차 힘듭니다.

규칙적인 형식으로 파일을 썼다면 DataInputStream객체의 available() 메서드를 이용하여 반복문으로 읽어들여서 처리할 수 있습니다.

소스 코드상에 48라인에 주석처리 되어 있습니다.


  1. package test;
  2.  
  3. import java.io.BufferedInputStream;
  4. import java.io.BufferedOutputStream;
  5. import java.io.DataInputStream;
  6. import java.io.DataOutputStream;
  7. import java.io.File;
  8. import java.io.FileInputStream;
  9. import java.io.FileNotFoundException;
  10. import java.io.FileOutputStream;
  11. import java.io.IOException;
  12.  
  13. public class FileBinaryWriteReadExample {
  14.  
  15.     public static void main(String[] args) {
  16.  
  17.         File inOutFile = new File("C:\\Users\\Public""in.bin");
  18.  
  19.         // ==========================//
  20.         // 바이너리 파일 쓰기
  21.         // ==========================//
  22.         DataOutputStream dos = null;
  23.         try {
  24.            
  25.             dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(inOutFile)));
  26.             dos.writeInt(1);
  27.             dos.writeUTF("안녕하세요");
  28.             dos.flush();
  29.        
  30.         } catch (IOException e) {
  31.             e.printStackTrace();
  32.         } finally {
  33.             if (dos != null) try { dos.close()} catch (IOException e) {}
  34.         }
  35.  
  36.         // ==========================//
  37.         // 바이너리 파일 쓰기
  38.         // ==========================//
  39.         DataInputStream dis = null;
  40.         try {
  41.            
  42.             dis = new DataInputStream(new BufferedInputStream(new FileInputStream(inOutFile)));
  43.            
  44.             System.out.println(dis.readInt());
  45.             System.out.println(dis.readUTF());
  46.            
  47.             /* 파일을 기록할 때 int 형식으로만 입력 한 경우 반복문을 통해서 읽는다.
  48.             while(dis.available() > 0){
  49.                 dis.readInt();
  50.             }*/
  51.            
  52.         } catch (FileNotFoundException e) {
  53.             e.printStackTrace();
  54.         } catch (IOException e) {
  55.             e.printStackTrace();
  56.         } finally {
  57.             if (dis != null) try {dis.close()} catch (IOException e) {}
  58.         } 
  59.     } 
  60. } 



3. 텍스트 파일 라인번호 읽기

이게 왜 필요한가 싶을지도 모르겠다. 그냥 lineCount 라는 변수를 하나 만들고 증가값을 1씩 증가하면 될텐데 말이다..

그래도 필요할지 모르니.. 일단 포스팅..


  1. package test;
  2.  
  3. import java.io.File;
  4. import java.io.FileNotFoundException;
  5. import java.io.FileReader;
  6. import java.io.IOException;
  7. import java.io.LineNumberReader;
  8.  
  9. public class FileLineNumberReaderExam {
  10.  
  11.     public static void main(String[] args) {
  12.         String path = "C:\\Users\\mjhwang\\Desktop";
  13.         String fileName = "test.txt";
  14.         LineNumberReader reader = null;
  15.         try {
  16.             reader = new LineNumberReader(new FileReader(new File(path, fileName)));
  17.             String line = null;
  18.             while ((line = reader.readLine()) != null){
  19.                 System.out.println("Line Number " + reader.getLineNumber() + " : " + line);
  20.             }
  21.         } catch (FileNotFoundException e) {
  22.             e.printStackTrace();
  23.         } catch (IOException e) {
  24.             e.printStackTrace();
  25.         }finally {
  26.             try{ if(reader != null) reader.close()}catch(IOException e){}
  27.         }
  28.     }
  29. } 


프로젝트 완료후 운영과정에서 의도하지 않은 오류가 발생했다.

문제가 된 부분은 메일 발송중에 에러가 발생했는데 흔히 메일 템플릿을 만들어 놓고 사용자명이라던지 비빌번호는 특정한 문구로 넣고 메일 발송 과정에서 해당 특정 문구를 해당 사용자의 이름과 비밀번호로 replace하여 메일을 발송하게 된다.


예를 들어 메일 템플릿에는 다음과 같은 문구가 있다.

이름 : #NAME#

비밀번호 : #PASSWORD#


해당 부분은 사용자가 메일을 받을 시점엔 다음과 같이 변경이 될것이다.

이름 : 홍길동

비밀번호 : testpassword


물론 비밀번호를 이렇게까지 표출 하는 곳은 없겠지만 예를 들어 설명한 부분이다.


일반적으로 템블릿의 특정 부분을 변경하기 위해서는 replaceAll()를 사용하게 되는데 여기에서 문제가 발생했다.

다들 신경쓰는 부분은 replaceAll(String arg1, String arg2) 메서드의 첫번째 인자를 신경쓰게 된다.

첫번째 인자는 정규표현식을 넣어야 하는데 정규표현식에서 특수문자를 문자열로 인식하기 위해서 [], \\를 감싸거나 앞에 붙여준다.


하지만 이번 문제는 2번째 인자에서 발생을 했는데 넘어가는 값은 "$0testword"와 같은 문자열이였다.

해당 문자는 소스상에서 다음과 같은 형태로 구현될것이다.


  1. String template= "비밀번호 : #PASSWORD#";
  2. String password = "$0testword";
  3.  
  4. template = template.replaceAll("#PASSWORD#", password );
  5. System.out.println(template);


정상적으로 문자열이 변경이 될것이라 생각했는데 에러가 발생을 했다.


간단하게 말하면 문제에 원인은 변경될 문자열에 "$"이 포함되어 있기 때문이다. 그리고 \\이 들어갈 경우도 의도하지 않은 결과가 발생할 수 있다.

java replaceAll 소스를 보게되면 "$", "//"일 경우 다른 처리를 하게 되므로 해당 부분을 참고하기 바란다.


이를 해결하기 위해서는 $ --> \\$로 변경하면 해결이 된다.

  1. String template= "비밀번호 : #PASSWORD#";
  2. String password = "\\$0testword";
  3.  
  4. template = template.replaceAll("#PASSWORD#", password );
  5. System.out.println(template);



replaceAll 사용시 특수문자를 치환하기 위해서는 [], \\를 쓰는데 다음과 같이 구별하여 쓴다.

[]를 사용하는 특수문자    ==> *, +, $, |, ? ==> [*]

\\를 사용하는 특수문자 ==> (, ), {, }, [, ] ==> \\(

- 를 사용하기 위해서는 \ 한번 붙여준다.

\를 사용하기 위해서는 \\를 사용한다.

spring boot를 공부하면서 설정에 관련된 사항을 정리 해본다.


1. server 포트 변경

#80 포트로 변경

server.port=80


#랜덤 포트로 변경

server.port=0




관련사이트 :

http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

자바 웹프로젝트를 진행하면서 소스 수정이 일어날 때마다 바로 반영이 되지 않기 때문에 

톰캣을 재기동하거나, 자동으로 소스를 반영하고 톰캣을 재실행되도록 설정을 한다.


이는 너무나 일상적인 개발 방법이라 전혀 이상하지 않았지만 매우 불편하고 시간을 허비하는 일이다.

순수 JSP만 이용하여 개발을 하게 되면 이런 과정을 거치지 않겠지만 요즘은 이런 개발은 하지 않을뿐더러 객체지향적으로 설계를 하다보니

JAVA 파일 갯수는 어마하게 늘어났다. 

파일 하나 수정했을 뿐인데 톰캣을 재기동하고, 때로는 파일 하나 수정했을 뿐인데 설정에 의해서 톰캣이 자동으로 재기동 된다.


이런 과정을 줄여주는 도구가 여러가지가 있겠지만 예전에는 JRebel을 잠깐 사용했었다.

좋은 점이 있지만 무엇보다 유로로 제공되고 잠깐 동안 커뮤니티 버전을 사용했는데 지금은 제공하지 않는지 URL을 찾을수가 없었다.


이번에는 이런  기능을 무료로 제공해주는 도구가 있어서 설정 방법을 요약한다.      

이름은 spring-loaded이며 다운로드는 아래 링크를 통하여 다운로드 받을 수 있다.


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

- maven : http://mvnrepository.com/artifact/org.springframework/springloaded/1.2.6.RELEASE


먼저 사용을 하기 위해서는 해당 사이트에서 파일을 다운로드 한다.

파일명은 현재 기준 가장 최근 파일이 아래와 같다.


springloaded-1.2.6.RELEASE.jar


해당 파일만 다운로드 받으면 준비가 끝난다.

제법 간단하다.


파일을 다운로드 받았다면 이클립스 톰캣 서버 설정을 일부 변경해줘야 한다.

이클립스에서 해당 웹어플리케이션의 톰캣 서버를 더블 클릭하면 톰캣 설정이 화면이 뜨게 되는데

1. Publishing -> Automatically publish when resources change 항목 체크

2. Server Options 탭의 모든 체크 항목 해제

3. General Information 탭 항목의 Open launch configuration 텍스트를 클릭한다.




4. Open launch configuration 텍스트를 선택하게 되면 해당 창에서 Arguments탭을 선택하여 Vm arguments 창에 위 이미지처럼 다음 옵션을 추가해준다.


-javaagent:/Users/hmjkor/Downloads/springloaded-1.2.6.RELEASE.jar -noverify


위에서 /Users/hmjkor/Downloads 부분은 각자의 PC 환경에 따라 다르게 지정이 된다. 

springloaded-1.2.6.RELEASE.jar 파일이 저장되어 있는 경로로 지정한다.  


모든 설정이 끝나면 OK 버튼을 눌러 설정을 종료하고 서버 설정 화면에서 하단 modules 탭으로 들어가서 해당 웹 어플리케이션을 선택한 후 Editor 버튼을 선택한다.




5. Editor 버튼을 통해서 띄우진 Editor Web Module 창에서 Auto reloading enabled 체크 항목을 해제한 후 OK 버튼을 눌러 설정을 종료한다.


이로써 모든 설정은 끝났으며 java 파일을 작성한 후 테스트 해본다.




데몬 프로그램에서는 다음과 같은 형태로 실행한다.


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



springloaded-1.2.6.RELEASE.jar

springloaded-1.2.4.RELEASE.jar

springloaded-1.2.7.RELEASE.jar






6. intellij 일 경우도 방법은 비슷하다. Edit Configurations 메뉴에서 톰갯을 등록해준 후 VM options 란에 옵션을 4.절에와 같이 추가해주면 된다.



그리고 On 'Update' action, On frame deactivation 옵션을 Update classes and resources 항목을 선택해주면 된다.

Update classes and resources 항목이 보이지 않는다면 Deployment 탭으로 이동한 후  Deploy at the server startup 항목의 배포 형식이 :exploded로 설정한다.





참고사이트 : 

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



java.lang.reflect를 이용하면 우리가 원하는 클래스에 대한 invoke가 가능하다는 것은 알고 있을 것이다.

하지만 classpath에 등록안되어진 클래스들에 대해서는 어떻게 할 것인가? 


일일이 사용자에게 클래스 패스를 설정하게 할수만은 없는 일이다. 


보통의 엔진들을 보게 되면 install되어진 디렉토리의 위치만을 세팅하도록 하고 있다. 

set JAVA_HOME이라던지 

set ANT_HOME이라던지.. 


쉘스크립트에 의하여 그러한 것들을 정의하여 java process를 띄우곤 하는데 그러면 

내가 ant.jar등을 등록하지 않았음에도 불구하고 해당 애플리케이션들이 잘 작동하는 이유는 무엇일까? 

그것은 바로 ClassLoader에 숨겨져 있다. 

아래에서 보여지는 샘플코드는 classpath 프로퍼티에 등록이 되어지지 않은 클래스들에 대한 조작을 할 것이다. 


그렇게 함으로서 이 글을 읽는 당신이 만든 애플리케이션이 별다른 클래스로딩 정책 없이도 작동이 될수 있겠다. 

그러려면 또한 잘 알아야 하는것이 reflection API이거늘... 

이부분에서는 그러한 것을 생략하고 URLClassLoader를 이용하여 디렉토리나 jar파일을 등록하여 가져오는 

방법을 설명하도록 하겠다. 


ClassLoader클래스는 이미 1.0API부터 존재해왔으면 URLClassLoader는 1.2에 새롭게 추가된 클래스이다. 

우리가 사용하는 파일시스템이 URL이란 이름하에 조작이 될 수 있다는 것을 우선 명심하기 바란다. 

왜냐면 file:/// 이란 URI를 사용하기 때문이다. 


아래에서는 특정한 디렉토리 안의 jar파일에 대한 class loading샘플을 보여준다.. 

  1. import java.io.*;
  2. import java.net.*;
  3. public class ClassLoading {
  4.   public static void main(String [] args) throws Exception {
  5.     // Create a File object on the root of the directory containing the class file
  6.     File file = new File("D:/_Develop/jmxSamples/customMBean/log4j-1.2.8.jar");    
  7.     try {
  8.       // Convert File to a URL
  9.       URL url = file.toURL();          // file:/D:/_Develop/jmxSamples/customMBean/log4j-1.2.8.jar
  10.       URL[] urls = new URL[]{ url };
  11.       System.out.println(urls);
  12.        
  13.       // Create a new class loader with the directory
  14.       ClassLoader cl = new URLClassLoader(urls);
  15.       System.out.println(cl);
  16.        
  17.       // Load in the class; Logger.class should be located in
  18.       // the directory file:/D:/_Develop/jmxSamples/customMBean/log4j-1.2.8.jar
  19.       Class cls = cl.loadClass("org.apache.log4j.Logger");
  20.       System.out.println(cls);    
  21.     } catch (MalformedURLException e) {
  22.       e.printStackTrace();
  23.     } catch (ClassNotFoundException e2) {
  24.       e2.printStackTrace();
  25.     }  
  26.   }
  27. }




위에서 보는 것처럼 디렉토리를 설정하거나 특정 jar파일을 사용할 수 있도록 작성한다. 

특정파일이 가르키지 않으면 해당 디렉토리의 class파일들을 package형태로 참조하도록 할 수 있는데 

해당 디렉토리에 대한 클래스 로딩 샘플을 아래와 같다. 

  1. import java.io.*;
  2. import java.net.*;
  3. public class ClassLoading {
  4.   public static void main(String [] args) throws Exception {
  5.     // Create a File object on the root of the directory containing the class file
  6.     File file = new File("D:/_CVSDevelop/jca_hello_adapter/build/classes");
  7.      
  8.     try {
  9.       // Convert File to a URL
  10.       URL url = file.toURL();          // file:/D:/_CVSDevelop/jca_hello_adapter/build
  11.       URL[] urls = new URL[]{ url };
  12.       System.out.println(urls);
  13.        
  14.       // Create a new class loader with the directory
  15.       ClassLoader cl = new URLClassLoader(urls);
  16.       System.out.println(cl);
  17.        
  18.       // Load in the class; Test.class should be located in
  19.       // the directory file:/D:/_CVSDevelop/jca_hello_adapter/build/classes/com/bea/jca/test/Test
  20.       Class cls = cl.loadClass("com.bea.jca.test.Test");
  21.       System.out.println(cls);
  22.      
  23.     } catch (MalformedURLException e) {
  24.       e.printStackTrace();
  25.     } catch (ClassNotFoundException e2) {
  26.       e2.printStackTrace();
  27.     }  
  28.   }
  29. }
  30.  

위 예제는 classpath의 루트로 잡은 디렉토리를 기준의 package형태로 설정되 파일을 로딩하여 사용할수 있도록 한다. 


이 이후의 코딩에는 class가 newInstance를 취한 후 method를 invoking해야 하는 과정을 거치게 되는데

한 가지 주의할 점은 해당 클래스를 반드시 reflection API를 이용하여 호출해야 한다는 점이다. 


대략 아래의 코드정도를 이용하여 main 메소드등을 호출하는 클래스를 작성할 수 있을 것이다. 

  1. public void invokeClass(String name, String[] args)
  2.     throws ClassNotFoundException,
  3.           NoSuchMethodException,
  4. {
  5.     Class c = loadClass(name);
  6.     Method m = c.getMethod("main"new Class[] { args.getClass() });
  7.     m.setAccessible(true);
  8.     int mods = m.getModifiers();
  9.     if (m.getReturnType() != void.class || !Modifier.isStatic(mods) ||
  10.         !Modifier.isPublic(mods)) {
  11.         throw new NoSuchMethodException("main");
  12.     }
  13.     try {
  14.         m.invoke(nullnew Object[] { args });
  15.     } catch (IllegalAccessException e) {
  16.         // This should not happen, as we have disabled access checks
  17.     }
  18. }


[출처]http://www.yunsobi.com/blog/136