개발 (65)
spring profile 사용시 주의점

최근 삽질


application.yml에 값을 설정 해두고

spring:
profiles: local
sleep:
min: 100
max: 500
---

spring:
profiles: dev

sleep:
min: 500
max: 1000


application.properties에 아래와 같이 프로파일을 설정해주었다

spring.profiles.active=dev


어플리케이션 구동했으나 계속 발생하는 오류.  심지어 다른 서버에서는 정상 동작


org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sleepAspect': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'sleep.min' invalue "${sleep.min}"


오류를 유심히 보니 프로파일이 다른것으로 적용이 되고 있었음
INFO(6347)[main] [n.d.TestApplication:656] The following profiles are active: dev1


원인은 서버 환변경수에 다음과 같이 프로파일이 적용되고 있었음

$ env | grep PROFILE

SPRING_PROFILES_ACTIVE=dev1


결론은 프로퍼티보다 서버 환경변수의 값이 먼저 적용하기 때문에 쉘 스크립트에서 환변경수를 초기화

프로그램안에만 적용되기 때문에 다른 프로그램에는 영향 없음

SPRING_PROFILES_ACTIVE=

nohup java -cp application.properties -jar donnert.jar > console.log 2>&1 &



0  Comments,   0  Trackbacks
댓글 쓰기
@Scope 어노테이션 사용하기(request+ProxyMode)

[개발/JAVA] - Threadlocal을 이용하여 사용자별 요청 처리하기


ThreadLocal과 같이 보시면 좋습니다.

스프링에서 빈 Scope 타입은 여러가지 방식이 있으며 아래를 참고해 주세요.

https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html


별도의 옵션이 없다면 빈은 싱글톤으로 관리되며 부모의 속성을 따라갑니다.

즉 controller는 따로 설정을 하지 않았기 때문에 singleton으로 동작하게 되며 그 안에서 주입받은 빈 역시 scope를 request로 줘도 singleton으로 동작하게 됩니다.

빈을 생성해 주고 위에 보이는것처럼 scope를 request로 선언해줍니다. 


그리고 위처럼 컨트롤러를 구현합니다.

위 경우 Cart가 매 요청마다 생성될것 같지만 이미 Controller(부모)가 싱글톤으로 잡혀있고 빈을 주입받은 상태이기 때문에 Cart 역시 싱글톤으로 동작합니다.  결국 Cart와 Mart는 동작 상 다른 점이 없습니다.


이럴땐 아래처럼 ProxyMode를 지정하게 되면 싱글톤 객체를 중간 프록시에서 새로운 오브젝트로 전달을 해주게 됩니다.

이전 글에서 Threadlocal을 이용했지만 스프링의 빈 만으로도 쉽게 개발이 가능합니다.


https://github.com/donnert/spring-boot/tree/master/scope-proxy

0  Comments,   0  Trackbacks
댓글 쓰기
Threadlocal을 이용하여 사용자별 요청 처리하기





개발시 소스 전체에서 사용할 수 있는 전역변수처럼 데이터를 할당하고 싶을떄가 있습니다.

Threadlocal을 이용하여 이를 구현해 봅니다.


ShoppingController.java

@RestController
public class ShoppingController {

	private int melon;

	@RequestMapping("/")
	private String test() {
		this.addMelon();
		
		return String.valueOf(melon);
	}
	
	private void addMelon() {
		melon++;
	}
}

사용자별로 요청 시 카트에 멜론을 담는 기능을 구현하려고 합니다.  이 떄 카트 수량은 여러 메소드에서 사용할 수 있기 때문에 멤버변수로 선언을 합니다.


이렇게 작성 후 실행을 해보면 카운트가 초기화가 되지 않고 계속 증가하는 것을 볼 수 있습니다.  스프링의 경우 빈 관리를 싱글톤으로 하기 때문에 ShoppingController 객체는 요청 시 재사용 됩니다.  멤버변수 역시 초기화가 되지 않습니다.




아래는 Threadlocal을 이용해 사용자별 카트를 구현한 소스입니다.


ThreadRepository.java

public class ThreadRepository {
	private static ThreadLocal<Cart> threadLocal = new ThreadLocal<Cart>() {
		@Override
		protected Cart initialValue() {
			return new Cart();
		};
	};
	
	public static Cart getCart() {
		return threadLocal.get();
	}
	
	public static void remove() {
		threadLocal.remove();
	}
}

카트를 담을 저장소를 만들어 줍니다.  Thradlocal을 이용하여 Thread별로 저장소가 생성이 됩니다.


Cart.java

public class Cart {
	private int apple = 0;
	private int orange = 0;
	private int melon = 0;
	
	public void addApple() {
		this.apple++;
	}

	public void addOrange() {
		this.orange++;
	}

	public void addMelon() {
		this.melon++;
	}

	@Override
	public String toString() {
		return "Cart [apple=" + apple + ", orange=" + orange + ", melon="
				+ melon + "]";
	}
}

카트를 생성해줍니다.


ShoppingController.java

@RestController
public class ShoppingController {

	@RequestMapping("/")
	private String test() {
		ThreadRepository.remove();
		
		this.addMelon();
		
		return ThreadRepository.getCart().toString();
	}
	
	private void addMelon() {
		ThreadRepository.getCart().addMelon();
	}
}

컨트롤러가 위처럼 수정되었습니다.  ThreadRepository에서 getCart를 했을때 해당 요청 쓰래드에 대한 Cart가 나오기 때문에 소스 어떤부분에서든 해당 카트의 데이터를 넣고 빼는 것이 가능합니다.

이때 ThreadLocal을 이용하였기 때문에 요청 시 새로운 스래드가 생성되며 그 스래드에서만 사용할 수 있게 관리됩니다.


* 일반으로 WAS는 쓰래드풀을 쓰기 때문에 요청이나 응답 시 해당 Threadlocal을 초기화 하지 않을 경우 이전에 사용한 정보가 남아 있습니다.  쓰래드 재사용시 반드시 remove를 통해 초기화 시켜주어야 합니다.



참고소스

https://github.com/donnert/spring-boot/tree/master/thread-local




0  Comments,   0  Trackbacks
댓글 쓰기
한글 byte로 자르기(깨짐 없이)

쓸때마다 새로만드는 것 같아서 저장용


UTF-8일 경우

subStringBytes("블라블라블라라", 10, 3);

EUC-KR일 경우

subStringBytes("블라블라블라라", 10, 2);


public String subStringBytes(String str, int byteLength, int sizePerLetter) {
	int retLength = 0;
	int tempSize = 0;
	int asc;
	if (str == null || "".equals(str) || "null".equals(str)) {
		str = "";
	}

	int length = str.length();

	for (int i = 1; i <= length; i++) {
		asc = (int) str.charAt(i - 1);
		if (asc > 127) {
			if (byteLength >= tempSize + sizePerLetter) {
				tempSize += sizePerLetter;
				retLength++;
			}
		} else {
			if (byteLength > tempSize) {
				tempSize++;
				retLength++;
			}
		}
	}

	return str.substring(0, retLength);
}


0  Comments,   0  Trackbacks
댓글 쓰기
이클립스 플러그인(vrapper) 설정파일(vrapperrc)

2013/05/23 - [개발/JAVA] - [Eclipse] 이클립스에서 VI(VIM) 방식으로 코딩하기


아주 오래전에 올린 vrapper 플러그인 관련 글인데

.vrapperrc 설정파일이 너무 빈약해서 계속 업데이트 해왔지만 블로그로 다시 정리


" 기본설정

set ignorecase

set smartcase

set scrolloff=5

set tabstop=4

set shiftwidth=4

set expandtab

set hlsearch

set incsearch


" 입력모드 탈출(문자열에 jj있으면 탈출해버리니 주의!! 두번주의!!)

imap jj <ESC>


" 하단의 <leader>케릭터 매핑

let mapleader="g"


" ;입력시 :입력모드로 변경

nnoremap ; :

vnoremap ; :


" H와 L을 라인 앞으로 뒤로

noremap H ^

noremap L $


" gt탭좌측으로, gy탭우측으로(한 작업창안에서)

nnoremap <leader>y <C-W>gt

nnoremap <leader>t <C-W>gT


" 작업창 이동(창이 split되어 있을 경우 hjkl을 gh gj gk gl로 입력하여 이동

nnoremap <leader>h :wincmd h<CR>

nnoremap <leader>j :wincmd j<CR>

nnoremap <leader>k :wincmd k<CR>

nnoremap <leader>l :wincmd l<CR>


.ideavimrc


0  Comments,   0  Trackbacks
댓글 쓰기
스프링 부트(spring boot)로 톰캣에서 실행하기(이클립스)

스프링 부트로 개발된 웹 어플리케이션을 톰캣에 올리는 방법입니다.

이클립스에서 단독 실행 모드와 톰캣 배포 방식 두가지 다 가능하기 때문에

개발할떄는 단독으로 띄워서 개발을 하고 배포할때만 톰캣을 띄워서 쉽게 배포를 하셔도 됩니다.


pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

 

  <groupId>net.donnert</groupId>

  <artifactId>spring.boot.web</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>jar</packaging>

 

  <name>spring.boot.web</name>

  <url>http://maven.apache.org</url>

 

  <properties>

    <project.build.sourceencoding>UTF-8</project.build.sourceencoding>

    <java.version>1.8</java.version>

  </properties>

  <parent>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>1.4.0.RELEASE</version>

  </parent>

 

  <dependencies>

    <dependency>

      <groupId>org.springframework.boot</groupId>

      <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

      <groupId>org.springframework.boot</groupId>

      <artifactId>spring-boot-starter-tomcat</artifactId>

      <scope>provided</scope>

    </dependency>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>


기존에 웹에서 사용하던 파일과 동일합니다.

추가적으로 spring-boot-starter-tomcat을 넣어줍니다.


Application.java

package net.donnert.spring.boot.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(Application.class);
	}
	
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

SpringBootServletInitializer를 상속받으셔야 톰캣 기동 시 정상적으로 스프링 모듈이 동작합니다.

그리고 configure를 오버라이드 해주셔야 서블릿 컨테이너 기동 시 스프링 설정을 적용시켜줍니다.


TestController.java

package net.donnert.spring.boot.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
	@RequestMapping("test")
	public String test() {
		return "This is spring";
	}
}

그냥 테스트용 컨트롤러이니다.


테스트

Application을 실행시켜줍니다. 스프링 부트 어플리케이션이 실행이 됩니다.

http://localhost:8080/test를 실행해보시면 This is spring이 찍히는 것을 볼 수 있습니다.


톰캣에 올리기

이미 Application에 SpringBootServletInitializer를 상속받아놨기 때문에 별다른 설정은 없습니다.

톰캣에 올리기 위해 Dynamic Web Module 프로젝트로 설정해줍니다.

(프로젝트 우클릭-Properties-Project Facets-Dynamic Web Module 체크)

설정된 톰캣에 우클릭을 해서 Add and Remove를 선택하면 스프링 어플리케이션이 보일겁니다.

(톰캣 설정은 따로 안다루겠습니다.)

추가 후 모듈의 Path를 /spring로 변경합니다.

톰캣 스타트 후 http://localhost:8080/spring/test를 때려보시면 This is spring이 보입니다.



0  Comments,   0  Trackbacks
댓글 쓰기
스프링 부트(Spring boot)로 restful API 서버 개발하기

스프링 부트를 이용한 api서버를 만들겠습니다.

서버에 요청하면 json형식으로 결과를 내려주는 서버를 만들게 됩니다.


프로젝트 생성

메이븐 프로젝트를 생성해 줍니다.

new-project-Maven Project해서 생성하시던지

그냥 프로젝트 생성 후 confiture-convert to maven project를 해줍니다.

아래와 동일한 프로젝트 구조가 생성이 됩니다.

2016/09/07 - [개발/JAVA] - 스프링 부트(Spring boot)로 개발하기


라이브러리 추가

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>net.donnert</groupId>
  <artifactId>spring.boot.web</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>spring.boot.web</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceencoding>UTF-8</project.build.sourceencoding>
  </properties>
 
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.1.RELEASE</version>
  </parent>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>
</project>

스프링 부트 parent와 starter-web을 달아줍니다.

배치때 사용한 것은 그냥 starter입니다. 뒤에 web을 붙여줍니다.


Application.java

package net.donnert.spring.boot.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

그냥 스타트.


Article.java

package net.donnert.spring.boot.web;

public class Article {
	
	private long seq;
	private String title;
	private String text;
	private String writer;
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
	public String getWriter() {
		return writer;
	}
	public void setWriter(String writer) {
		this.writer = writer;
	}
	public long getSeq() {
		return seq;
	}
	public void setSeq(long seq) {
		this.seq = seq;
	}
}

클라이언트로 전달 될 클래스입니다.


ArticleController.java

package net.donnert.spring.boot.web;

import java.util.ArrayList;
import java.util.List;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ArticleController {
	
	@RequestMapping(value = "list", method = RequestMethod.GET)
	public List
test() { List
list = new ArrayList
(); for(int i=1; i<=10; i++) { Article article = new Article(); article.setSeq(i); article.setText("This is text"); article.setTitle("This is title"); article.setWriter("I am writer"); list.add(article); } return list; } @RequestMapping(value = "article/{seq}", method = RequestMethod.GET) public Article detail(@PathVariable("seq") long seq) { Article article = new Article(); article.setSeq(seq); article.setText("This is text"); article.setTitle("This is title"); article.setWriter("I am writer"); return article; } }

@RestController 어노테이션을 통해 컨트롤러를 restful로 정의해줍니다.

이렇게 될 경우 @ResponseBody가 필요 없어집니다.


목록과 상세 2개의 url이 매핑되어있습니다.


테스트

Application.java를 실행하면 자체 서버가 뜹니다.

2016-10-07 15:20:52.554  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/list],methods=[GET]}" onto public java.util.List net.donnert.spring.boot.web.ArticleController.test()
2016-10-07 15:20:52.556  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/article/{seq}],methods=[GET]}" onto public net.donnert.spring.boot.web.Article net.donnert.spring.boot.web.ArticleController.detail(long)
2016-10-07 15:20:52.561  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-10-07 15:20:52.562  INFO 4148 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2016-10-07 15:20:52.611  INFO 4148 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-10-07 15:20:52.611  INFO 4148 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-10-07 15:20:52.758  INFO 4148 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-10-07 15:20:53.325  INFO 4148 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-10-07 15:20:53.686  INFO 4148 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)

스프링 부트 스타터 웹에는 톰캣이 내장되어있습니다.

내장 톰캣이 8080으로 뜨는걸 볼 수 있습니다.


브라우저를 열고 url을 입력합니다.(http://localhost:8080/list)

[{"seq":1,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":2,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":3,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":4,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":5,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":6,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":7,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":8,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":9,"title":"This is title","text":"This is text","writer":"I am writer"},{"seq":10,"title":"This is title","text":"This is text","writer":"I am writer"}]

article 10개가 json형식으로 변환되어서 출력되는 것이 보입니다.


브라우저를 열고 url을 입력합니다.(http://localhost:8080/article/3)

{"seq":3,"title":"This is title","text":"This is text","writer":"I am writer"}

3번 article 역시 json으로 보여지고 있습니다. @PathVariable를 통해 url의 파라메터를 가져왔습니다.


간단하게 api서버가 완성(?)되었습니다.

logback, mybatis연결 등은 배치 예제에서와 동일한 방법으로 하시면 됩니다.

yml을 이용한 프로파일 적용 역시 동일합니다.

2016/09/23 - [개발/JAVA] - 스프링 부트(Spring boot)에서 mybatis(oracle) 적용하기

0  Comments,   0  Trackbacks
댓글 쓰기
스프링 부트(Spring boot)에서 mybatis(oracle) 적용하기

2016/09/19 - [개발/JAVA] - 스프링 부트(Spring boot)에서 logback 적용하기


이어서.. 스프링 부트에서 마이바티스를 연동해 보도록 하겠습니다.

실무에서 주로 사용되는 mapper xml을 이용한 방법을 살펴보겠습니다.

DB는 오라클을 사용하지만 드라이버만 바꾸면 다른 종류의 DB도 사용이 가능합니다.


pom.xml

오라클 드라이버는 메이븐 중앙 저장소에 없기 때문에 오라클 저장소를 추가해 줍니다.

parent와 동일 레벨로 저장소를 추가합니다.


<repositories>

        <repository>

            <id>oracle</id>

            <name>ORACLE JDBC Repository</name>

            <url>http://maven.jahia.org/maven2</url>

        </repository>

    </repositories>

그리고 오라클 드라이버와 mybatis spring goot starter 종속성을 추가해줍니다.

마이바티스에서 스프링 대응하여 나온 것으로 기존 마이바티스는 필요없습니다.

(http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure)

<dependency>

        <groupId>org.mybatis.spring.boot</groupId>

        <artifactId>mybatis-spring-boot-starter</artifactId>

        <version>1.1.1</version>

    </dependency>

         

    <dependency>

        <groupId>com.oracle</groupId>

        <artifactId>ojdbc6</artifactId>

        <version>11.1.0.7.0</version>

    </dependency>


application.yml

spring.datasource 설정 부분이 추가되었으며, mybatis 설정 부분이 추가되었습니다.

mybatis-config.xml형식으로 설정을 하고 싶으면 mybatis.config-location설정을 쓰시면 됩니다.

다양한 설정 항목들이 있으니 스프링, 마이바티스 문서를 참고해 주세요.

datasource항목은 대부분 개발과 상용이 다를테니 샘플보다는 프로파일별로 옮겨서 관리를 하시는게 좋습니다.

spring:
  profiles: 
    active: dev
  timerName: exampleTimer
  datasource:
    driver-class-name: oracle.jdbc.driver.OracleDriver
    url: jdbc:oracle:thin:@111.222.111.222:1521:SIDHERE
    username: donnert
    password: password
---
mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  configuration:
    lazyLoadingEnabled=true
    aggressiveLazyLoading=false
    mapUnderscoreToCamelCase=true
---
spring:
  profiles: local
  task:
    fixedDelay: 1000
    name: localTask
---
spring:
  profiles: dev
  task:
    fixedDelay: 5000
    name: devTask

Application.java

기존에 사용하시던대로 MapperScan어노케이션으로 매퍼 패키지 위치를 정의해 줍니다.

package net.donnert.spring.boot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@MapperScan("net.donnert.spring.boot")
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

TestMapper.java

쿼리 xml과의 인터페이스를 제공해줍니다.

package net.donnert.spring.boot;

public interface TestMapper {
	public String getValueFromDatabase();
}


testMapper.xml

src/main/resources하위에 mapper폴더 생성 후 testMapper.xml을 생성해 줍니다.

내용은 위에서 정의한 인터페이스 쿼리를 작성해줍니다.




    

Timer.java

매퍼 빈을 주입 받아서 그냥 호출하면 됩니다.

package net.donnert.spring.boot;

import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Component
public class Timer {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	private AtomicInteger loopCounter = new AtomicInteger();
	
	@Autowired
	private StopWatch watch;
	
	@Autowired
	private TestMapper testMapper;
	
	@Value("${spring.task.name}")
	private String taskNamePrefix;
	
	@Value("${spring.timerName}")
	private String timerName;
	
	@PostConstruct
	public void init() {
		logger.info("{} init", timerName);
		watch.start();
	}

	@Scheduled(fixedDelayString = "${spring.task.fixedDelay}")
	public void tick() throws InterruptedException{
		watch.stop();
		logger.info(testMapper.getValueFromDatabase());
		String taskName = taskNamePrefix + "-" + String.valueOf(loopCounter.getAndIncrement());
		watch.start(taskName);
	}

	@Bean
	public StopWatch watch() {
		return new StopWatch();
	}
}

테스트

실행해 보면 아래처럼 쿼리 로그와 결과가 정상적으로 찍히는 모습을 볼 수 있습니다.

2016-09-20 13:56:08.904  INFO(12012)[main] [net.donnert.spring.boot.Timer:33] exampleTimer init
2016-09-20 13:56:09.286  INFO(12012)[main] [o.s.j.e.a.AnnotationMBeanExporter:431] Registering beans for JMX exposure on startup
2016-09-20 13:56:09.349  INFO(12012)[main] [o.s.s.a.ScheduledAnnotationBeanPostProcessor:244] No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2016-09-20 13:56:09.372  INFO(12012)[main] [n.d.spring.boot.Application:57] Started Application in 3.229 seconds (JVM running for 3.793)
2016-09-20 13:56:10.812 DEBUG(12012)[pool-2-thread-1] [n.d.s.b.T.getValueFromDatabase:145] ==>  Preparing:  SELECT NAME FROM TEST WHERE ROWNUM=1
2016-09-20 13:56:10.902 DEBUG(12012)[pool-2-thread-1] [n.d.s.b.T.getValueFromDatabase:145] ==> Parameters: 
2016-09-20 13:56:10.930 DEBUG(12012)[pool-2-thread-1] [n.d.s.b.T.getValueFromDatabase:145] <==      Total: 1
2016-09-20 13:56:10.932  INFO(12012)[pool-2-thread-1] [net.donnert.spring.boot.Timer:40] 테스트이름

간단한 예제같지만 프로파일, 로깅, DB연결 등 필요한 부분들은 모두 들어가 있기 떄문에 실무에서도 

배치나 데몬 프로세스로 충분히 사용하실 수 있습니다.

스프링 부트를 이용하면 복잡한 설정도 필요없고

이미 구현된 것들을 설정만 해서 쓰는 방식이기 때문에 개발 시간이 많이 단축됩니다.


완성소스

https://github.com/donnert/spring-boot/tree/master/spring-boot-batch


2  Comments,   0  Trackbacks
  • 크로노
    정말 정말 감사합니다^^
    계속 해맷는데 이제야 되는 소스를 찾았네요^^
  • jepark3452
    pom.xml에 오라클 추가시 에러나시는분들은

    <!-- dependencies 태그 위쪽에 추가 하세요.-->
    <repositories>
    <repository>
    <id>oracle</id>
    <name>ORACLE JDBC Repository</name>
    <url>https://maven.atlassian.com/3rdparty/</url>
    </repository>
    </repositories>

    <!-- dependencies 태그 안쪽에 추가하세요. -->
    <dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>12.1.0.1-atlassian-hosted</version>
    </dependency>


    참고하세요~

    ※ 참고 : https://github.com/regenea8/GroupWare/blob/master/GroupWare/pom.xml
댓글 쓰기
스프링 부트(Spring boot)에서 logback 적용하기

2016/09/08 - [개발/JAVA] - 스프링 부트(Spring boot)에서 프로퍼티 사용하기


전에 이어서 이번에는 logback을 적용시켜보겠습니다.

사실 이미 스프링 부트가 알아서 다 적용시켜놨지면 설정파일을 이용해서

프로파일 적용 및 파일에 쓰는 방법을 해보겠습니다.


logback.xml --> logback-spring.xml로 만드세요

src/main/resources 폴더에 logback.xml을 작성합니다.

아래 샘플은 제가 사용하고 있는 파일입니다.

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="1 minutes">

<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

<property value="/temp/log/log" name="LOG_FILE_PREFIX"/>

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">

<encoder>

<charset>UTF-8</charset>

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p}\(${PID:- }\)[%t] [%logger{30}:%line] %msg%n</pattern>

</encoder>

</appender>

<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE">

<encoder>

<charset>UTF-8</charset>

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p}\(${PID:- }\)[%t] [%logger{30}:%line] %msg%n</pattern>

</encoder>

<file>${LOG_FILE_PREFIX}.log</file>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<fileNamePattern>${LOG_FILE_PREFIX}_%d{yyyyMMdd}.log</fileNamePattern>

</rollingPolicy>

</appender>


<springProfile name="local">

<logger name="net.donnert.spring.boot" level="DEBUG" />

<root level="INFO">

<appender-ref ref="CONSOLE"/>

</root>

</springProfile>


<springProfile name="!local">

<logger name="net.donnert.spring.boot" level="DEBUG" />


<root level="INFO">

<appender-ref ref="CONSOLE"/>

<appender-ref ref="FILE"/>

</root>

</springProfile>

</configuration>


테스트

springprofile노드를 이용해 현재 적용중인 프로파일 별 설정이 가능합니다.

xml을 보시면 현재 프로파일이 local인 경우는 CONSOLE appender를 이용, 콘솔에만 출력

local이 아닌 경우 CONSOLE, FILE appender를 이용 파일에도 출력을 하게 됩니다.


아래처럼 로깅 패턴이 정의된 형식으로 바뀌었으며 프로파일을 dev(local이 아닌 프로파일)로 바꾸게 되면 추가적으로 D:\temp\log\log.log파일이 생성되는 것을 보실 수 있습니다.


2016-09-19 11:03:28.664  INFO(10952)[main] [net.donnert.spring.boot.Timer:30] exampleTimer init

2016-09-19 11:03:28.833  INFO(10952)[main] [o.s.j.e.a.AnnotationMBeanExporter:431] Registering beans for JMX exposure on startup

2016-09-19 11:03:28.846  INFO(10952)[main] [o.s.s.a.ScheduledAnnotationBeanPostProcessor:244] No TaskScheduler/ScheduledExecutorService bean found for scheduled processing

2016-09-19 11:03:28.857  INFO(10952)[pool-2-thread-1] [net.donnert.spring.boot.Timer:37] StopWatch '': running time (millis) = 189



2016/09/23 - [개발/JAVA] - 스프링 부트(Spring boot)에서 mybatis(oracle) 적용하기



5  Comments,   0  Trackbacks
  • qnrtjd123
    혹시 보시게 되면 에러확인좀 부탁드려요

    Failed to auto configure default logger context
    Reported exception:
    ch.qos.logback.core.joran.spi.JoranException: Problem parsing XML document. See previously reported errors.
    at ch.qos.logback.core.joran.event.SaxEventRecorder.recordEvents(SaxEventRecorder.java:65)
    at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:141)
    at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:103)
    at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:53)
    at ch.qos.logback.classic.util.ContextInitializer.configureByResource(ContextInitializer.java:75)
    at ch.qos.logback.classic.util.ContextInitializer.autoConfig(ContextInitializer.java:150)
    at org.slf4j.impl.StaticLoggerBinder.init(StaticLoggerBinder.java:84)
    at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:55)
    at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
    at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
    at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:412)
    at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:357)
    at org.apache.commons.logging.impl.SLF4JLogFactory.getInstance(SLF4JLogFactory.java:155)
    at org.apache.commons.logging.impl.SLF4JLogFactory.getInstance(SLF4JLogFactory.java:132)
    at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:273)
    at org.springframework.boot.SpringApplication.<clinit>(SpringApplication.java:189)
    at com.board.BoardApplication.main(BoardApplication.java:12)
    Caused by: org.xml.sax.SAXParseException; lineNumber: 53; columnNumber: 1; 예기치 않은 파일의 끝입니다.
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1239)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:648)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:332)
    at ch.qos.logback.core.joran.event.SaxEventRecorder.recordEvents(SaxEventRecorder.java:59)
    ... 16 more
    14:33:58,963 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
    14:33:58,963 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
    14:33:58,963 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/D:/springBoot/workspace/board/target/classes/logback.xml]
    14:33:59,019 |-ERROR in ch.qos.logback.core.joran.event.SaxEventRecorder@21300751 - XML_PARSING - Parsing fatal error on line 53 and column 1
    14:33:59,019 |-ERROR in ch.qos.logback.core.joran.event.SaxEventRecorder@21300751 - org.xml.sax.SAXParseException; lineNumber: 53; columnNumber: 1; 예기치 않은 파일의 끝입니다.




    • 첫줄에
      <?xml version="1.0" encoding="UTF-8"?>
      가 빠진것 같습니다. 본문 수정했으니 적용해보세요
    • jepark3452
      <?xml version="1.0" encoding="UTF-8"?>

      <configuration scan="true" scanperiod="1 minutes">
      <include resource="org/springframework/boot/logging/logback/defaults.xml" />
      <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
      <!-- 변수 지정 : 로그저장 경로는 자신의 환경에 맞게 수정해야 함. -->
      <property value="C:/spring-tool-suite-3.8.4/workspace/spring.boot/logs/schedule-log" name="LOG_FILE_PREFIX" />
      <!-- <property value="/temp/log/log" name="LOG_FILE_PREFIX" /> -->

      <!-- FILE Appender -->
      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <file>${LOG_FILE_PREFIX}.log</file>
      <!-- 일자별로 로그파일 적용하기 -->
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${LOG_FILE_PREFIX}_%d{yyyyMMdd}.log</fileNamePattern>
      <maxHistory>60</maxHistory> <!-- 일자별 백업파일의 보관기간 -->
      </rollingPolicy>
      <encoder>
      <charset>UTF-8</charset>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p}\(${PID:- }\)[%t] [%logger{30}:%line] %msg%n</pattern>
      </encoder>
      </appender>

      <!-- CONSOLE Appender -->
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
      <charset>UTF-8</charset>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p}\(${PID:- }\)[%t] [%logger{30}:%line] %msg%n</pattern>
      </encoder>
      <!-- <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>%d{yyyy-MM-dd HH:mm:ss} [%-5p] [%F]%M\(%L\) : %m%n</pattern>
      </layout> -->
      </appender>

      <!-- TRACE > DEBUG > INFO > WARN > ERROR, 대소문자 구분 안함 -->
      <!-- profile 을 읽어서 appender 을 설정할수 있다.(phase별 파일을 안만들어도 되는 좋은 기능) -->
      <springProfile name="local">
      <root level="INFO">
      <appender-ref ref="CONSOLE" />
      </root>
      </springProfile>
      <springProfile name="!local">
      <root level="INFO">
      <appender-ref ref="FILE" />
      <appender-ref ref="CONSOLE" />
      </root>
      </springProfile>
      </configuration>
    • jepark3452
      저도 에러가 나서 여기저기서 검색끝에 해결했습니다. 위에 댓글 확인해 보세요~
    • 어랏 감사합니다
      코드 하이라이터 제거과정에서 뭔가 어긋난거 같네요
댓글 쓰기
스프링 부트(Spring boot)에서 profile, yml 사용하기


2016/09/08 - [개발/JAVA] - 스프링 부트(Spring boot)에서 프로퍼티 사용하기

이전 글에 이어서...

이번에는 프로파일별로 프로퍼티를 설정 해 볼 예정입니다.

개발을 하다보면 로컬->개발기->상용기 형식으로 바뀌면서 설정값들이 바뀌어야 하는 경우가 많습니다.

개발할때는 로그를 1초마다 찍고 싶고 개발기에서는 5초마다 찍고 싶을때 주석을 풀고 지우고 하지 말고 아래처럼 프로파일로 관리를 하면 됩니다.

yaml형식의 파일을 사용하며 하나의 파일 안에서 프로파일 별로 설정값들이 설정됩니다.


application.properties

더 이상 사용하지 않습니다.  지워주세요.


application.yml

이제 모든 설정은 이 yml파일 안에서 하게 됩니다.

yml파일은 탭문자가 들어가면 안되며, 아래와 같이 구분은 ---으로 하게 됩니다.


첫번째 섹션은 현재 어떤 프로파일로 동작을 할건지를 선언해주고 있습니다.

(application.properties 파일의 spring.profiles.active=local과 동일합니다)

또한 프로파일에 구분없이 사용할 값들도 정의되어있습니다.


그 다음 local 프로파일이 적용 되었을 경우 사용할 설정값과 

dev 프로파일이 적용되었을 경우 사용할 설정 값이 ---로 구분되어서 선언되어 있습니다.


spring:

  profiles: 

    active: local

  timerName: exampleTimer


---

spring:

  profiles: local

  task:

    fixedDelay: 1000

    name: localTask


---

spring:

  profiles: dev

  task:

    fixedDelay: 5000

    name: devTask


Timer.java

프로파일과 상관없이 돌아가는 timeName속성을 찍는 로그를 추가해 줍니다.

package net.donnert.spring.boot;

import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Component
public class Timer {
	Logger logger = LoggerFactory.getLogger(this.getClass());
	private AtomicInteger loopCounter = new AtomicInteger();
	
	@Autowired
	private StopWatch watch;
	
	@Value("${spring.task.name}")
	private String taskNamePrefix;
	
	@Value("${spring.timerName}")
	private String timerName;
	
	@PostConstruct
	public void init() {
		logger.info("{} init", timerName);
		watch.start();
	}

	@Scheduled(fixedDelayString = "${spring.task.fixedDelay}")
	public void tick() throws InterruptedException{
		watch.stop();
		logger.info(watch.prettyPrint());
		String taskName = taskNamePrefix + "-" + String.valueOf(loopCounter.getAndIncrement());
		watch.start(taskName);
	}

	@Bean
	public StopWatch watch() {
		return new StopWatch();
	}
}


실행(local)

현재 설정되어 있는 상태로 수행을 합니다.

아래처럼 공통 설정인 timerName는 exampleTimer로 찍히면서 

프로파일별 설정을 따라 localTask가 1초 마다 찍히고 있습니다.


  .   ____          _            __ _ _

 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )

  '  |____| .__|_| |_|_| |_\__, | / / / /

 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::        (v1.4.0.RELEASE)


2016-09-12 13:28:03.434  INFO 6600 --- [           main] net.donnert.spring.boot.Application      : Starting Application on ezen-PC with PID 6600 (D:\Java\workspace\Ezens\paynow_simulator\spring.boot\target\classes started by donne in D:\Java\workspace\Ezens\paynow_simulator\spring.boot)

2016-09-12 13:28:03.437  INFO 6600 --- [           main] net.donnert.spring.boot.Application      : The following profiles are active: local

2016-09-12 13:28:03.498  INFO 6600 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@49d8c528: startup date [Mon Sep 12 13:28:03 KST 2016]; root of context hierarchy

2016-09-12 13:28:04.442  INFO 6600 --- [           main] net.donnert.spring.boot.Timer            : exampleTimer init

.....

...

2016-09-12 13:28:08.617  INFO 6600 --- [pool-1-thread-1] net.donnert.spring.boot.Timer            : StopWatch '': running time (millis) = 4170

-----------------------------------------

ms     %     Task name

-----------------------------------------

00166  004%  

01002  024%  localTask-0

01000  024%  localTask-1

01002  024%  localTask-2

01000  024%  localTask-3



실행(dev)

application.yml의 3번째 라인의 active: local을 active: dev로 바꿔줍니다.

아래처럼 공통 설정인 timerName는 exampleTimer로 찍히면서 

프로파일별 설정을 따라 devTask가 5초 마다 찍히고 있습니다.

  .   ____          _            __ _ _

 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )

  '  |____| .__|_| |_|_| |_\__, | / / / /

 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::        (v1.4.0.RELEASE)


2016-09-12 13:30:32.465  INFO 10976 --- [           main] net.donnert.spring.boot.Application      : Starting Application on ezen-PC with PID 10976 (D:\Java\workspace\Ezens\paynow_simulator\spring.boot\target\classes started by donne in D:\Java\workspace\Ezens\paynow_simulator\spring.boot)

2016-09-12 13:30:32.468  INFO 10976 --- [           main] net.donnert.spring.boot.Application      : The following profiles are active: dev

2016-09-12 13:30:32.521  INFO 10976 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@65efb4be: startup date [Mon Sep 12 13:30:32 KST 2016]; root of context hierarchy

2016-09-12 13:30:33.379  INFO 10976 --- [           main] net.donnert.spring.boot.Timer            : exampleTimer init

.....

...

2016-09-12 13:30:48.542  INFO 10976 --- [pool-1-thread-1] net.donnert.spring.boot.Timer            : StopWatch '': running time (millis) = 15158

-----------------------------------------

ms     %     Task name

-----------------------------------------

00154  001%  

05001  033%  devTask-0

05001  033%  devTask-1

05002  033%  devTask-2


마치며

스프링에서 프로파일은 콤마로 구분하여 여러개를 해도 됩니다.

자바에서 context.getEnvironment().getActiveProfiles()를 하면 현재 설정된 프로파일들을 가져올 수 있습니다.

이를 응용하여 확장이 용이한 프로그램 개발이 가능해집니다.


2016/09/19 - [개발/JAVA] - 스프링 부트(Spring boot)에서 logback 적용하기


0  Comments,   0  Trackbacks
댓글 쓰기