반응형
반응형

현재 진행 중인 프로젝트에 로컬라이징을 적용하다가

스프링의 강력한 기능 중 하나인 ReloadedResourceBundleMessageSource에 대하여 정리가 되어, 포스트를 작성합니다.

 

[요구사항 및 해결]

1. 다국어 지원  

  - 서버가 한국에 있다고 무조건 korean으로 나와서는 안되며, 클라이언트에서 컨트롤이 가능하도록 해야 함.

 

  > 해결 : LocaleChangeInterceptor를 등록하고, 옵션 값인 ParamName을 이용하여,  

     해당 파라미터가 클라이언트로부터 도착하면 Locale을 변경하도록 설정.  

     추가로 SessionLocaleResolver라는 것을 localeResolver라는 Bean이름으로 등록해 주어야함. 

     그 뒤에 messageSource.getMessage()를 이용하면, 파라미터로 Locale을 지정해주지 않아도,  

     서버의 Locale을 참조하여, 원하는 메시지를 찾음!!

 

  * 결론 : 기본적으로 LocaleChangeInterceptor를 등록하면, localeResolver라는 이름을 가진 Bean을 찾아서 처리를 하게 되는데,

              SessionLocaleResolver를 localResolver라는 이름으로 등록하지 않으면,  

              기존에 이미 존재하던 default Resolver(AcceptHeaderLocaleResolver)에 대하여 처리가 되버림.

              AcceptHeaderLocaleResolver는 구현부를 보면 알겠지만, 특정 파라미터에 의해서 Locale을 바꾸는 것을 지원하지 않음.

              따라서, SessionLocaleResolver로 교체를 해주면, 위에 Interceptor에서 지정한 ParamName으로 Locale을 바꾸는 기능을  

              사용할 수 있게 됨.

 

2. 서버 재시작 없이 메시지 내용 변경

   - 텍스트를 하나 바꾸었다고 재시작을 한다는 것이 말이 안된다고 느끼는 분들이 좀 많음.  

     (php만 했던 사람들은 당연히 그렇게 느끼던..) 

 

   > 해결 : ReloadedResourceBundleMessageSource에 옵션으로 CacheSeconds라는게 있음.

               이게 기본으로는 -1 (영원히 캐싱, 따라서 리로드도 하지 않음)으로 되어 있기 때문에,

               1초라도 넣어주어야 해당 주기만큼 캐싱을 하고, 캐싱된 게 없으면 변경내역을 바로 불러와서 적용을 해준다.

               Tomcat을 사용하는 경우 reloadable옵션을 false로 해줘야 사용이 가능함. (true로 쓰는 경우는 거의 없지만...)

 

  ※ .properties은 모든 부분에서 사용이 가능함. 

 

 

해결은 했는데, getMessage 자체의 defaultMessage 기능이 좀 이상하게 동작하더군요.

파라미터로 온 값에 해당하는 Locale을 못찾았으면 localeResolver에 defaultLocale로 지정한 Locale에 해당하는 메시지를 찾을 줄 알았는데, 무작정 defaultMessage에 써놓은걸 뿌려버리던.... 그래서 전 try catch를 이용해서 구현을 했답니다.ㅠㅠ

이거 말고는 특별히 문제는 없는듯... 

반응형
,
반응형

Java Config를 이용하는 경우에 해당합니다. (xml 설정은 1년전에 안쓰기로 버렸기 때문에... 본 포스트에는 내용이 없습니다.)

 

4.1.0으로 넘어오면서 override되는 메소드가 두개정도 추가되었습니다만..  

필수로 구현해야 하는건 아닌거 같더군요. 

 

1. 설정 


@Configuration
@EnableCaching
public class CachingConfig implements CachingConfigurer {

    @Bean(destroyMethod="shutdown")
    public net.sf.ehcache.CacheManager ehCacheManager() {
        CacheConfiguration staticConfig = new CacheConfiguration();
        staticConfig.setName("StaticConfig");  // 캐시 이름
        staticConfig.setMemoryStoreEvictionPolicy("LRU");  // 메모리에 저장되는 방식을 결정
        staticConfig.setMaxEntriesLocalHeap(100);  // 최대로 캐싱할 수 있는 Entries의 수
        staticConfig.setTimeToIdleSeconds(3600);   
        staticConfig.setTimeToLiveSeconds(3600);
        
        net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
        config.addCache(staticConfig);

        return net.sf.ehcache.CacheManager.newInstance(config);
    }
    
    @Bean
    @Override
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheManager());
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {        
        return new SimpleKeyGenerator();
    }

    @Override
    public CacheResolver cacheResolver() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public CacheErrorHandler errorHandler() {
        // TODO Auto-generated method stub
        return null;
    }

 

2. 사용     


@Cacheable(value="StaticConfig", key="#configNum")   // StaticConfig라는 캐시에 configNum을 기준으로 캐싱한다.
    public Object selectStaticConfig(int configNum) {
        // TODO
    }

    @CacheEvict(value="StaticConfig")    // StaticConfig에 캐싱되어 있던 내용을 초기화한다.
    public Object selectStaticConfigInitCache(int configNum) {
        // TODO
    } 

 

매우 간단합니다. 

반응형
,
반응형

@Before : Point cut 표현식에 해당하는 메소드를 처리하기 이전에 실행됨.

 

@After : Point cut 표현식에 해당하는 메소드를 처리한 후에 실행됨.

 

@Around : Point cut 표현식에 해당하는 메소드 주위에서 실행됨.

 

검색해서 @Around에 대한 정보를 찾다보면, 메소드 주위라는 말이 정확하게 이해가 안되더군요.

그래서 테스트를 해봄으로써 확실히 알게되었습니다.

 

@Around의 경우 다른 것들과는 달리 ProceedingJoinPoint라는 것을 argument로 가질 수 있는데요.

ProceedingJoinPoint에는 proceed() 라는 메소드가 존재합니다.

 

메소드 주위라는 말은 전과 후를 모두 처리할 수 있는 것이며,

전과 후를 구분하는 기준은 proceed() 메소드가 호출된 시점으로부터, 전 후를 의미합니다.

 

역시 글만 대충 읽어보고 적용할지 여부를 결정할 게 아니라, 테스트를 해봐야 정확한 결과를 얻을 수 있다는.. ㅠㅠ 

반응형
,
반응형

DB서버를 Scale Out 해서 사용하는 경우나, DB서버가 원격지에 분리되어 있는 경우

단일 트랜잭션만으로는 한계가 있기 때문에 (물론 사용하지 않도록 설계하는 것이 가장 좋겠지만...) 글로벌 트랜잭션을 사용하게 됩니다.

 

많이 알려진 게 아래 세가지 인데요..

2번은 3번으로 기능이 넘어가면서 Deprecated되었더군요. 

 

1. Atomikos XA TransactionManager 

 

2. Neo4j ChainedTransactionManager (Deprecated) 

 

3. Spring Data Commons ChainedTransactionManager

 

1번과 3번의 차이는 1번의 경우 2Phase Commit을 지원하는 것이고, 3번의 경우 2Phase Commit까지는 아니지만, 비슷하게 지원을 한다는 점과,  

가장 큰 것은 1번의 경우 설정이 매우 복잡하다는 점과, 3번은 설정이 매우 간편하다는 점 및 스프링과 통합이 쉽다는 점 입니다.

 

개인적인 생각으로 굳이 2Phase Commit까지 지원해야 되나.. 라는 생각과 왠지 Spring Data Commons의 경우 Spring Web MVC에서 사용하면, 안정적이지 않을까라는 생각으로 3번을 선택하게 되었습니다.

 

설정은 매우 간단합니다.

 


@Bean           // Bean 등록 시 이름이 매우 중요합니다. 이름이 다른 경우 defaultTransactionManager를 사용하게 됨.
public ChainedTransactionManager transactionManager() {
    ChainedTransactionManager transactionManager = new ChainedTransactionManager(transactionManagerGame(), transactionManagerGame_2(), transactionManagerGame_3(), transactionManagerGame_4(), ...);
    return transactionManager;
}
 
@Override        // @Transactional을 사용할 때 사용할 transactionManager를 지정해줌. (xml설정에서의 annotation-driven)
public PlatformTransactionManager annotationDrivenTransactionManager() {
    return transactionManager();
}
반응형
,
반응형

먼저 SpringFramework를 3.2버전 이상 사용하시는 분만 보시기 바랍니다.

(ControllerAdvice가 3.2버전때 만들어졌습니다.) 

 

@ControllerAdvice는 @Controller 나 @RestController(4.0부터 지원)에서 발생하는 작업을 캐치해주는 기능을 가지고 있습니다.

(특정 컨트롤러만 캐치하는 것도 가능.. 4.0부터 지원이라네요. ControllerAdvice Annotation Documentation 참조) 

저는 이것을 에러핸들링 용도로 사용해보았습니다. 

 

클래스 위에 @ControllerAdvice를 붙여주고 어떤 Exception을 캐치할 것인지, 

내부 메소드를 선언하여 메소드 상단에는 @ExceptionHandler(Exception.class) 와 같이 붙여줍니다.

최종적으로 아래와 같은 모습이 됩니다. 

 

@ControllerAdvice
@RestController 
public class ControllerAdviceHandler {
       @ExceptionHandler(NullPointerException.class)
       public Object nullPointerHandle(HttpServletRequest request, Exception e) {
            return "null"; 
       } 
        
       @ExceptionHandler(Exception.class)
       public Object exceptionHandle(HttpServletRequest request, Exception e) {
            return "exception"; 
       }

}


 

Exception의 가장 상위인 Exception을 핸들링하고, 그 하위인 NullPointerException을 핸들링하는 메소드가 따로 있는 경우,

NullPointerException은 NullPointerException 핸들링 메소드로 들어가서 처리가 됩니다. 

반응형
,
반응형

● execution 명시자 : Advice를 적용할 메서드를 명시할 때 사용

 

 기본 형식

 execution(수식어패턴? 리턴타입패턴 클래스이름패턴?이름패턴(파라미터패턴)

  ▶ 수식어패턴 : public, private 등등의 수식어를 명시, 생략 가능

  ▶ 리턴타입 : 리턴 타입을 명시

  ▶ 클래스이름, 이름패턴 : 클래스 이름 및 메서드이름을 패턴으로 명시

  ▶ 파라미터패턴 : 매칭될 파라미터에 대해 명시

  ▶ '*' : 모든 값을 표현

  ▶ '..' : 0개 이상을 의미

 

 

출처 : http://0px.kr/296

 

 

저 위에 명시된 것들은 &&나 ||를 활용해서 확장이 가능하다.

 

공통으로 처리해야될 일이 많다보니, 참고할 일이 많아서 퍼옴.. ㅠㅠ

반응형
,
반응형

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를 만들었던 적이 있는데

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

 

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

 

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

반응형
,
반응형