반응형
반응형

java application server의 경우 별도로 구성을 하지 않으면,

쪼그마한 텍스트 파일을 변경하여도, 서버를 내렸다 올려야하는 번거로움이 추가된다.

 

하지만, properties파일의 경우에는 수시로 변경이 가능하게 해야 하는 요구사항이 들어오기도 한다.

 

기본적으로, Spring Framework를 사용한다면, .properties파일의 경우 @Value를 이용하기 위해

PropertyPlaceholderConfigurer를 사용하게 된다.

 

물론, 이 기능이 사용하기는 가장 편리하지만, auto reload를 지원하지 않는다.

 

그래서 PropertyPlaceholderConfigurer대신 ReloadResourceMessageSource를 이용해야만,

가장 간단하게 auto reload를 구현할 수 있다.  

(apache commons의 configuration도 있긴하지만.. 이건 톰캣 사용 시 autoDeploy를 false로 바꿔야 되는 한계가 존재함.)

 

ReloadResourceMessageSource의 경우 에러메시지 / 로컬라이징 등에서 많이 사용하고 있을텐데, Basename옵션으로 지정을 해두었을 것이다.

클래스를 직접 까보면 Basenames라는 옵션이 별도로 있다.  

이걸 이용해서, 기존에 사용하던 에러메시지 / 로컬라이징의 properties도 넣어주고, 새로 추가될 .properties도 넣어줄 수가 있다.

(당연하겠지만, properties파일의 key값은 다르게 해주는 센스가 필요함..  

동일한 key가 있는 경우 어떻게 처리할지에 대해선 고민도 안해봤고, 테스트도 해보지 않았다-_- 난 그렇게 안만들거니깐)

 

앞에 포스팅에도 작성한바 있지만, reload가 되게 하려면 cacheSeconds옵션을 +값으로 지정해주어야 하며,

톰캣 server.xml의 reloadable은 false로 지정해주자.

반응형
,
반응형

Ehcache에서 제공하는 @Cacheable에 들어갈 수 있는 key는 단일 String이다.

결론적으로, 하나의 String key만 지원하는 것이었고, 즉 multiple key를 지원을 하지 않기 때문에, 대충 문자열 조합으로 생각을 해보았다.  

(redis나 memcached에서도 이런 방법을 많이 사용하는듯)

 

처음엔 아래와 같이 작성하였다. 


@Cacheable(value="#a:#b") 
public Object cache(String a, int b) {
  // TODO 
}

 

결과는 실패..

원인은.. 콜론을 그냥 사용하면, Spring Expression과 어긋난다는 거였다.

 

그래서 아래 Spring Expression을 확인해보았다. 

http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/expressions.html

 

결론은 문자열을 붙이고 싶으면, concat을 이용하라는 것 같다.

 

@Cacheable(value="#a.concat(:).concat(#b)") 
public Object cache(String a, int b) {
  // TODO 
}



But! 앞에 인자값이 int인 경우에는 int에 concat 메소드가 없기 때문에 에러가 발생한다.

그래서.. Integer.toString을 해본 후에 넣어봤는데, spEl에서 Integer를 찾을 수 없다고 한다.


해결방법 : int에 그냥 toString() 붙이고 concat하니까 잘 된다. 

int 자체에는 toString이 없는데 spEL에서 Object형태로 변환을 하나보다.


@Cacheable(value="#a.toString().concat(:).concat(#b)") 
public Object cache(int a, int b) {
  // TODO 
}
반응형
,
반응형

개발자에게 문서 작성이란 참 귀찮은 일이다.

하지만, 뭔가 프로토콜을 구현하다보면 문서 작성은 필수적인 일이다.

 

Swagger는 이런 귀찮은 프로토콜 문서 작성을 자동화 시켜준다.

 

Swagger 자체부터 파고들면, 어려웠겠지만 Spring MVC전용으로 누군가 커스터마이징해서 올려둔 게 있어서

이걸 적용해 보았다. 

 

[Swagger-SpringMVC 설정 (https://github.com/martypitt/swagger-springmvc)] 

1. Maven에 의존성을 추가해준다.  

 - 현재 Swagger-SpringMVC의 경우 가장 최신버전이 0.9버전인데, Maven Central Repositories에서는 0.88버전인가까지밖에 올라와 있지 않다.

 - 0.9버전을 사용하기 위해, Repository를 따로 등록해준다.


<repository>
          <id>jcenter-release</id>
          <name>jcenter</name>
          <url>http://oss.jfrog.org/artifactory/oss-release-local</url>
</repository>
<dependency>
            <groupId>com.mangofactory</groupId>
            <artifactId>swagger-springmvc</artifactId>
            <version>0.9.0</version>
</dependency>

 

2. Jackson을 사용하고 있지 않다면, Jackson도 의존성에 추가해준다. (Swagger-SpringMVC는 내부적으로 JacksonMessageConverter를 사용하기 때문) - 현재 가장 최신버전은 2.4.3이다. 


<dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.4.3</version>
 </dependency>
 <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.4.3</version>
 </dependency> 

 

3. 2번에 설명한 것과 같이 JacksonMessageConverter를 Spring Config에 추가해주어야 한다.

 - Java Config기준으로 WebMvcConfigurerAdapter를 상속받은 클래스(Servlet설정)에서  

configureMessageConverters Method를 override해서 처리한다. 

 

4. Spring Servlet설정(3번에 명시한 WebMvcConfigurerAdapter) 클래스에 @EnableSwagger를 추가해준다.

 - 여기서 주의할 점은, ComponentScan등으로 Controller 어노테이션 등 문서화할 REST API들을 찾아 Bean으로 만들어주는 작업이 선행되어 있어야 한다는 것

 

5. 확인은 로컬 톰캣 기본 설정 기준으로 http://localhost:8080/{어플리케이션ROOT Path}/api-docs에서 한다.

 

[Swagger UI 설정 (https://github.com/wordnik/swagger-ui) - GitHub에서 생략되어 있는 설명들이 너무나도 많다.-_-]

1. git bash등을 이용해서 https://github.com/wordnik/swagger-ui.git를 clone으로 로컬에 땡겨온 후에, 웹 프로젝트에 추가해준다.

 - 땡겨오게되면, dist라는 폴더만 있는데 그 안에 있는 내용들만 WEB-INF/views/swagger-ui 폴더를 생성하여 넣어주도록 한다.

 

2. Spring Servlet설정에서 InternalResourceViewResolver를 생성해주고, 리소스 경로를 루트에서 사용할 수 있도록 설정해준다.  

(가장 중요한 설정이며, 뭔가 잘 안되면 무조건 이 설정에서 경로가 잘못지정된 것이다. /를 넣느냐 마느냐가 매우 중요한 역할을 함..--) 


@Bean
public InternalResourceViewResolver getInternalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("**")
                .addResourceLocations("/WEB-INF/views/swagger-ui/")
                .setCachePeriod(0);
}

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
}

 

3. WEB-INF/views/swagger-ui/index.html파일을 index.jsp로 바꾸어준다. 

 - 확장자만 바꾸면 안되고, jsp상단에 항상 포함되어 있는,  

<%@ page language="java" contentType="text/html; charset=EUC-KR" pageEncoding="EUC-KR"%> 도 같이 넣어준다.

 

4. ModelAndView형식의 컨트롤러를 하나 만들어서, index.jsp파일을 열도록 해준다.


@RequestMapping(value = "/ui")
public ModelAndView ui(ModelAndView mav) {
        mav.setViewName("/swagger-ui/index");
        return mav;
}

 

5. 확인

 - http://localhost:8080/{Root Path}/ui

 

 

세팅은 여기까지가 완료이고, 더 연구해 보아야할 것들을 정리해보면..

1. UI상에 Parameter와 Response들이 표시가 되는데, @RequestParam이나 @RequestBody등을 사용하지 않고

Pojo로 받는 경우에는 해당 Pojo의 클래스명만 나오고, 클래스의 내부 variable들은 보이지 않는다.

 

2. 파라미터를 입력할 수 있는 Text Box에 1번처럼 @RequestParam, @RequestBody를 사용하지 않고, Pojo인 경우

아무것도 없는 비어있는 Text Box가 나온다. (1번이 해결되면 같이 해결될 문제인 것 같음)

 

 

현재 개발중인 어플리케이션이 파라미터는 Pojo로 받고, 리턴은 Map으로 하기 때문에,

뭔가 커스터마이징해야 원하는대로 사용할 수 있을 것 같다.  

사용할 수 있는건, 현재 구현되어 있는 API가 어떤게 있다라는 정도...

 

고생한 거에 비해 소득이 별로 없으니, 커스터마이징을 해봐야겠다.  

 

[커스터마이징 결과]

UI에 보이는 Parameter DataType부분을 Pojo에 선언된 변수들을 Reflection으로 열어서, Json String으로 만들어 뿌려주는 것은 성공

하지만, 이 작업에는 SwaggerSpringMvcPlugin의 옵션중 customAnnotationReaders옵션을 이용하여

Reader를 직접 구현하여 넣어주어야 하며, 기존에 등록되어 있던 Reader가 고스란히 동작하는 바람에, 두번 처리가 되어

UI상에도 두줄로 표시되고 있었다.. ㅡㅡ;;  

 

RequestParam이랑 RequestBody쓰면 암것도 안건드리고, 그냥되는건데

왜 Json문자열 앞에 파라미터명이 있고, 그 파라미터로 Json통 문자열이 들어와버리니 참 골치아픔..

 

결론은.. 여기에 맞게 사용하려면 Reader를 제거할 수 있는 옵션이 나오길 기다려야되는듯;;; 

반응형
,
반응형

기존에 많이 사용하던, MappingJackson2HttpMessageConverter 대신 Json 처리를 할 때 사용할 수 있도록

GsonHttpMessageConverter가 Spring Framework 4.1부터 추가가 되었다.

 

1년전 Jackson보다 Gson이 성능이 좋다는 이야기를 듣고,  

Gson을 사용하는 HttpMessageConverter를 만들었던 적이 있는데

이제는 공식으로 지원을 한다.

 

그래도 암호화 등 기타 처리를 위해서는 커스터마이징은 필요할 것으로 보인다.

 

직접 만든거랑 코드가 비슷해서 놀랐다는;; 

반응형
,
반응형

WebApplicationInitializer에서 dispatcherServlet 설정에 InitParameter옵션을 아래와 같이 해주면된다.


dispatcher.setInitParameter("throwExceptionIfNoHandlerFound", "true");


위와 같이 설정해두면, 404 에러에 대해서 org.springframework.web.servlet.NoHandlerFoundException를 던져주게 된다.

반응형
,
반응형
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(objectMapper);
converters.add(jackson2HttpMessageConverter);



서블릿 설정 중, MessageConverter를 추가해주는 부분에서 

위와 같이 세팅해주면, pretty하게 정렬이 된 JSON String을 볼 수 있게 된다.

반응형
,
반응형

비동기 처리 시 기본적으로 @Async를 사용한다.


JavaConfig기준 Spring 설정파일에서 @EnableAsync을 붙이기만 하면 바로 사용이 가능해진다.


하지만, 위 설정만으로 사용하게 되면, 기본적으로 단일 쓰레드로 동작을 하게 된다.

(ThreadPool설정을 보니 Default값이 1로 되어 있다.)


log를 @Async를 이용해서 보내게 되는 경우,

확실히 대규모 접속 서비스 같은 경우 단일쓰레드만으로는 사용이 어렵다.


로드러너를 이용한 성능 테스트에서도 목표 성능에 미치지 못했었던 적이 있다.


위 문제를 해결하기 위해, ThreadPooling을 해야 될 것 같아서,

스프링 사이트를 뒤져봤다.


참조 : http://docs.spring.io/spring/docs/4.1.x/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html


AsyncConfigurer interface를 구현하겠다고 선언하면, 아래와 같이 두 가지 메소드에 대해서 구현을 하라고 나오는데,

getAsyncExecutor()가 내가 원하던 ThreadPooling역할을 해줄 것이다.

getAsyncUncaughtExceptionHandler()를 구현해서 이용할 수 있는 에러 핸들링은 필요에 따라 사용하는걸로...


@Override
public Executor getAsyncExecutor() {
     ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
     executor.setCorePoolSize(10);         // 최초에 생성할 Pool 개수
     executor.setMaxPoolSize(50);          // 몇개까지 Pool을 생성할 것인지
     executor.setQueueCapacity(100);      // Async 처리 시 Queue Size 
                                                      // (설정하지 않으면 Integer.MAX이기 때문에 성능에 문제가 발생함)
     executor.setThreadNamePrefix("LogAsync-");   // Thread 생성 시 가장 앞에 붙는 이름을 뭘로 할 것인지
     executor.initialize();
     return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
}



참고로 위 설정에서 사용하는, ThreadPoolTaskExecutor가 생각보다 목표 성능에 미치지 않고 불안정하다면, 

ConcurrentTaskExecutor를 이용해서 동시성에 대한 처리를 개선할 수 있다고 한다.

참조 : http://blog.outsider.ne.kr/1066 


전에 단일쓰레드 사용으로 문제가 있었으니, 

쓰레드풀링으로 다시 이용해보고 결과는 다시 포스팅해야 겠다.


[테스트 결과]

위 처럼 QueueCapacity를 100으로 잡고, 부하를 줘봤더니 일반적인 부하에서는 문제가 없었지만,

임계치까지 부하를 준 경우에 담아둘 수 있는 Queue Capacity가 작아서 Exception을 떨구며, 처리를 못하는 것들이 꽤 생겼다.


그래서, QueueCapacity를 CorePoolSize * MaxPoolSize * 2  = 1000 수치로 잡고 테스트를 해보았더니

임계치까지 부하를 줘도 모두 처리가 가능했고, CPU 사용률도 양호했다.


실무에서는, 로그를 DB에 insert하는 부분에서 사용하고 있다.

반응형
,
반응형

application.properties를 열어서 logging.path를 지정해주면 끝남.


아래 링크에 자세히 설명되어 있다.

http://www.slideshare.net/whiteship/ss-47273947

반응형
,
반응형

프로젝트 생성은 start.spring.io에 들어가서 만들던, sts에서 starter project로 만들던 똑같다.

freemarker랑 web만 체크를 하고 만들면됨.


내부적으로 뭔가만 해줄뿐, 다른 Spring Boot 프로젝트와 디렉토리 형식은 별반 다를게 없다.


내부적으로 해주는 뭔가에 대해 spring 공식 사이트에서 알아보니..


src/main/resources폴더에 있는 static 폴더를 internelResource로 알아서 세팅을 해주며,

templates 안에 있는 파일은 freemarkerViewResolver로 알아서 설정을 해준다고 한다.


그래서, 기존에 복잡하게 설정파일을 이리저리 고칠일이 없어졌다.


만약 ftl파일이 들어갈 경로를 templates 경로 말고 다른 경로를 사용하고 싶다면.. (웬만하면 그럴일은 없겠지만)

application.properties 파일을 열어서 필요한 옵션을 추가하여 override시켜주면 된다. (아래 링크 참고)

http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-view-resolvers



sts가 이제 3.7버전이 되면서 application.properties파일에서도 code assistance를 해준다;;

spring boot도 그렇고 IDE도 그렇고, 점점 개발자가 뭔가를 설정할 일이 없어지는듯?


하나 주의할 점은, 추가로 WebMvc관련된 interface나 class(WebMvcConfigurerAdapter 등...)를 상속받아서

새롭게 설정파일을 만들 경우 spring boot가 자동으로 해줬던 것들이 제대로 동작을 하지 않는 것 같다.


Object를 Json으로 매핑하기 위해서 WebMvcConfigurer를 상속받아서 MappingJackSon2MessageConverter를 추가했더니

viewResolver건, static 폴더 접근이건 둘다 안됬었다.


하지만, 기본으로 MappingJackson2MessageConverter를 지원해주는 것 같아서, 설정파일에서 지워버리니 해결이 됨..


자동으로 해주는게 너무나 많아서, 뭔가를 추가할때 복잡하긴한데, 어느정도 타협을 해야될것 같음.

반응형
,
반응형

view가 전혀 필요없는, API만 지원하는 서비스에서는 그냥 @RestController를 쓰면 된다.


하지만, view가 필요한 곳에서는 @RestController를 사용해서 클래스를 매핑해버리면,

view로 접근을 못하는 문제가 있다.


@RestController가 @ResponseBody를 포함하고 있기 때문이다.


그래서 view가 필요하고, 해당 클래스에서 restfulAPI와 view를 함께 묶어버리고 싶은 경우에는 클래스에 @Controller로 지정해주고,

restfulAPI에는 @ResponseBody를 일일히 붙여주도록 사용을 해야 되는 것 같다.

반응형
,
반응형