'Spring'에 해당되는 글 7건

  1. 2019.06.27 springframework mvc i18n(국제화) and localization(지역화=현지화) by 파이팅야
  2. 2012.09.26 RestTemplate 으로 첨부 파일내용 보내기 by 파이팅야 8
  3. 2012.07.09 jQuery.validate와 spring validate 연동 by 파이팅야 2
  4. 2012.03.22 '웹 개발자를 위한 Spring 3.0 프로그래밍'을 읽고... by 파이팅야 3
  5. 2012.03.19 spring mvc 3 date by 파이팅야 4
  6. 2012.03.18 spring mvc 3 code 관리 (enum) by 파이팅야 4
  7. 2012.03.08 spring mvc 3 error log by 파이팅야 4

국제화와 지역화

위키백과 내용 - https://ko.wikipedia.org/wiki/%EA%B5%AD%EC%A0%9C%ED%99%94%EC%99%80_%EC%A7%80%EC%97%AD%ED%99%94

국제화와 현지화는 출판물이나 하드웨어 또는 소프트웨어 등의 제품을 언어 및 문화권 등이 다른 여러 환경에 대해 사용할 수 있도록 지원하는 것을 의미한다. 이때 국제화는 제품 자체가 여러 환경을 지원할 수 있도록 제품을 설계하는 것을 의미하며, 현지화는 제품을 각 환경에 대해 지원하는 것을 의미한다.

 

최자의 보물창고 블로그 내용(신빙성은 적을 수 있음) - https://jwchoi85.tistory.com/tag/internationalization

컴퓨터에서 국제화와 현지화 라는것은, 다른 언어와 지역적인 차이들을 소프트웨어에 적용시킨다는 것을 말합니다. 국제화라는 것은 여러가지 다른 언어와 지역적 차이들을 기술적인 변경 없이 소프트웨어에 적용할 수 있도록 설계하는 과정을 말합니다. 현지화 라는 것은 국제화가 된 소프트웨어에 특정 지역이나 언어에 대해서 지역특색의 컴포넌트를 추가한다거나 번역을해서 적용시키는 것을 말합니다

 

 

LocaleResolver 종류

 

  • AceeptHeaderLocaleResolver (default)
    • 브라우저의 언어값으로 사용하는 것
    • HTTP 'Accept-Language' header값을 사용하는것
    • setLocale()메소르드를 지원하지 않음 사용하면 UnsupportedOperationException 발생함.
    • Locale aceeptHeaderLocale = new HttpServletRequest().getLocale();

  • CookieLocaleResolver
    • 쿠키 기반의 로케일 정도를 사용하는 것
    • <bean id="localeResolver"
          class="org.springframework.web.servlet.i18n.CookieLocaleResolver" >
          <property name="cookieName" value="clientlanguage"/>
          <property name="cookieMaxAge" value="100000"/>
          <property name="cookiePath" value="web/cookie"/>
      </bean>

    • 속성 설명

      프로퍼티 

      설 명  

       cookieName

       사용할 쿠키 이름

       cookieDomain

       쿠키 도메인

       cookiePath

       쿠키 경로, 기본값은 "/"이다.

       cookieMaxAge

       쿠키 유효 시간 (second), -1로 지정하면 브라우저를 닫을 때 쿠키를 지우겠다는 것

       cookieSecure

       보안 쿠키 여부, 기본값은 false 이다.

    • 만약 저장된 쿠키값이 없어서 특정값으로 세팅하고자 한다면 defaultLocale 파라미터값을 세팅하면 된다.

    • SessionLocaleResolver나 CookieLocaleResolver에 defaultLocale이 없다면 브라우저의 언어설정(AceeptHeaderLocaleResolver)을 따르게 된다.
    • CookieLocaleResolver에 보면 몇가지 추가 세팅할 수 있는 값이 있음
      • languageTagCompliant - Specify whether this resolver's cookies should be compliant with BCP 47 language tags instead of Java's legacy locale specification format. The default is falseNote: This mode requires JDK 7 or higher. Set this flag to true for BCP 47 compliance on JDK 7+ only.  
      • 위키백과의 내용과 같이 ko-KR과 같이 '_'가 아닌 '-'를 붙여서 표현하는 방식으로 쿠키값을 심을지 여부값 이다. true값이 되면 'ko-KR', 'en-US'와 같이 '-'가 들어간 형태로 cookieValue값이 세팅된다. springframework 내부적으로는 'ko_KR' 형식으로 사용하고 쿠키값(cookieValue)만 변경됨.
        • languageTagCompliant 값에 따른 parsing하는 코드
          • (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale))
      • Cookie 관련 정보(CookieGenerator class를 상속받아 cookie관련 설정값을 추가로 세팅 할 수 있음)
        • HttpOnly (CookieGenerator.cookieHttpOnly에 있는 값을 상속받음.) - javascript에서 document.cookie로 cookie값을 조작할 수 없도록 하고 웹서버에서만 cookie값을 조작할 수 있도록 할지 여부 값, 참고 링크 https://nsinc.tistory.com/121
        • secure (CookieGenerator.cookieSecure 에 있는 값을 상속받음) - HTTPS가 아닌 통신에서는 쿠키를 전송하지 않을지 여부 값. 참고 링크 https://nsinc.tistory.com/121
  • SessionLocaleResolver
    • 세션 기반의 로케일 정보를 사용하는 것
    • request가 가지고 있는 session에 있는 값을 가지고 오는 것
      <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />

    • 설정 예

    • <beans:bean id="localeResolver"

          class="org.springframework.web.servlet.i18n.SessionLocaleResolver">

          <beans:property name="defaultLocale" value="ko" />

      </beans:bean>

    • SessionLocaleResolver나 CookieLocaleResolver에 defaultLocale이 없다면 브라우저의 언어설정을 따르게 된다.

  • FixedLocaleResolver
    • 웹 요청과 상관없이 defaultLocale 속성값을 사용는 것
    • FixedLocaleResolver는 setLocale() 메소드를 지원하지 않음 호출하면  UnsupportedOperationException이 발생함.

Locale 변경시

  • LocaleResolver를 이용한 Locale 변경
    • LocaleResolver를 빈으로 등록했다면, 컨트롤러에서 LocaleResolver를 이용해서 Locale을 변경할 수 있게 된다. 예를 들어 다음과 같이 LocaleResolver를 설정했다고 하자.

    • <bean class="com.xxx.test.controller.LocaleChangeController">

              <property name="localeResolver" ref="localeResolver" />

      </bean>

      <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />

       

      이 경우, 컨트롤러 클래스는 다음과 같이 LocaleResolver의 setLocale() 메서드를 호출해서 클라이언트의 웹 요청을 위한 Locale을 변경할 수 있다.

    • import org.springframework.web.servlet.LocaleResolver;

      @Controller

      public class LocaleChangeController {

          private LocaleResolver localeResolver;

          @Inject

          public void setLocaleResolver(LocaleResolver localeResolver) {

              this.localeResolver = localeResolver;

          }

       

          @RequestMapping("/changeLocale")

          public String change(@RequestParam("language") String language, HttpServletRequest request, HttpServletResponse response) {

              // Locale locale = (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale))  // <-- CookieLocaleResolver를 사용 하는 경우

              Locale locale = new Locale(language);   // <-- cookie가 아닌 다른 LocaleResolver를 사용하는 경우

       

              // LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); // <-- DI를 받지 않고 현재 request에서 LocaleResolver를 가져올 수 도 있음.

       

              localeResolver.setLocale(request, response, locale);

              return "redirect:/index.jsp";

          }

      }

      LocaleResolver를 이용해서 Locale을 변경하면, 이후 요청에 대해서는 지정한 Locale을 이용해서 메시지 등을 로딩하게 된다.

      ResolverContextUtils는 현재 request에서 LocaleResolver를 가져올 수 있음.

  • LocaleChangeInterceptor를 사용하는 경우
    • org.springframework.web.servlet.i18n.LocaleChangeInterceptor는 url를 통해서 언어를 변경할 경우 사용 아래와 같은 설정 상태면 
    • <mvc:interceptors>

          <beans:bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">

          <beans:property name="paramName" value="lang" />

          </beans:bean>

      </mvc:interceptors>

 

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/LocaleResolver.html

Posted by 파이팅야
,

이렇게 쓸 필요는 없을 것 같지만... 막 짜본 내용

ㅇ. servlet-context.xml (FormHttpMessageConverter 추가 한다.)

  <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="messageConverters">
    <list>
    <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
    <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
    <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
    <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
    </list>
    </property>
    </bean>

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

        <property name="maxUploadSize">  

            <value>104857600</value>  

        </property>  

     </bean>

ㅇ. view 내용

<spring:url value="/test/upload" var="addUrl"/>

<form id="inquiryForm" action="${addUrl}" method="POST" enctype="multipart/form-data" >

<input type="file" name="upload"/>  

<input type="submit" id='test'>

</form>


ㅇ. 호출 하는 쪽 Controller (Base64로 file 정보를 encoding한다.)

@RequestMapping(value="/upload", method=RequestMethod.POST)

public void index(@RequestBody MultipartFile upload) throws IOException {

String url = mwDefaultUrl + "/inquiry/testUpload" ;

String encodedString = Base64Helper.encodeToString(upload.getBytes(), false);

formData.add("upload", encodedString);

formData.add("fileName", upload.getOriginalFilename()); 

                HttpHeaders requestHeaders = new HttpHeaders();  

    requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);

    HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(formData, requestHeaders);  

ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); 

System.out.println("in web response.getBody[" + response.getBody() + "]");

}


ㅇ. 받는 쪽 Controller(Base64로 decoding한다.)

    @RequestMapping(value="/testUpload" , method=RequestMethod.POST )

public @ResponseBody String index(@RequestParam("upload") String encodedString, String fileName, HttpServletRequest httpRequest) {

    byte[] upload = Base64Helper.decode(encodedString);

OutputStream out = null;

try {

out = new FileOutputStream("d:\\data\\" + fileName);

out.write(upload);

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} finally {

if (out != null)

try {

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

           return "hoho test upload";

    }



Posted by 파이팅야
,


다음과 같은 jQuery.validate를 사용하는 jQuery plug-in을 만들고

;(function($){  

$.fn.ajaxHandler = function(instanceSettings) {

$.fn.ajaxHandler.defaultSettings = {

validator : null,

action : null,

method : null,

data : null,

dataType : "json",

cache : false,

eventName : "submit",

// function

beforeSendFunction : null,

successFunction : null,

errorFunction : null,

completeFunction : null,

};

var settings = $.extend({}, $.fn.ajaxHandler.defaultSettings, instanceSettings || {});


var ajaxThis = $(this);

//jQuery validation을 통과한 데이터를 이용해서 Spring에 ajax 호출을 시도한다.

$(this).off(settings.eventName);

$(this).on(settings.eventName, function() {

$.ajax({

url : (settings.action == null) ? $(this).attr("action") : settings.action,

type : (settings.method == null) ? $(this).attr("method") : settings.method,

data : (settings.data == null) ? $(this).serialize() : settings.dataType,

dataType : settings.dataType,

cache : settings.cache,

// ajax와 validation을 동시에 사용하기 위해서 사용한다.

beforeSend : function() {

var beforeSendResult = true;

if (settings.beforeSendFunction != null)

beforeSendResult = settings.beforeSendFunction();

  

var validationResult = true;

if (ajaxThis.get(0).tagName == "FORM" && settings.validator != null) {

validationResult = ajaxThis.valid();

if (!validationResult)

settings.validator.focusInvalid();

}

return (validationResult && beforeSendResult) ? true : false;

},

complete : function (request) {

if (settings.completeFunction != null)

settings.completeFunction(request);

},

success : function (jsonResult, textStatus, httpRequest) {

if (settings.successFunction != null)

settings.successFunction(jsonResult, textStatus, httpRequest);

},

error : function (httpRequest, ajaxOptions, thrownError) {

if (settings.errorFunction != null)

settings.errorFunction(httpRequest, ajaxOptions, thrownError);

if (400 == httpRequest.status && 

settings.validator != null &&

ajaxThis.get(0).tagName == "FORM") {

validationError(httpRequest, settings.validator);

} else {

$.processErrorForAjax(httpRequest);

}

}

});

 

// browser가 form을 submit하는 것을 막기 위해서 false를 리턴한다.

// true를 리턴할 경우 Spring이 반환하는 jsonResult 데이터가 화면에 표시된다.

return false;

});

//Validation Error 공통으로 처리하는 함수

//Spring에서 전달한 jsonData를 <filed, message>형태로 생성한후

//jQuery Validation을 통해서 Error message를 출력한다.

function validationError(request, validator) {

if (null == request)

return;

if (400 == request.status) {

var requestResult = $.parseJSON(request.responseText).result;

var errors = new Array();

$.each(requestResult, function() {

errors[this.field] = this.message;

});

validator.showErrors(errors);

validator.focusInvalid();

}

};

};

})(jQuery);


$(document).ready(function () {

$.validator.setDefaults({

errorElement: "span",

errorPlacement: function(error, element) {

var field = element.attr('id');

error.attr('id', field + '.errors');

error.insertAfter(element);

}

});

});


Controller에서는 다음과 같이 호출하고

private JsonResult getValidationErrorResult(BindingResult result, JsonResult jsonResult) {

    jsonResult.setCode(HttpServletResponse.SC_BAD_REQUEST);

    jsonResult.setMessage("Validation Error");

    

    List<ValidationError> errors = new ArrayList<ValidationError>(); 

    for (FieldError fieldError : result.getFieldErrors()) {            

        String message = this.messageSource.getMessage(

                fieldError.getCode(), fieldError.getArguments(), Locale.getDefault());

        errors.add(new ValidationError(fieldError.getField(), fieldError.getCode(), message));

    }

    jsonResult.setResult(errors);

    

    return jsonResult;

}

@RequestMapping(value="/save", method=RequestMethod.POST)

public @ResponseBody JsonResult getFaxSearchList(HttpServletResponse httpResponse, 

@Validated({Default.class}) @ModelAttribute FaxHistory faxHistory, BindingResult result) { 

JsonResult response = new JsonResult();


if (result.hasErrors()) {

      response = getValidationErrorResult(result, response);

       httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);

      return response;

}

// restTemplate로 DB Insert 처리

// Business Process

return response;

}



다음과 같이 호출하면 jQuery.validate의 에러처리와 spring의 에러처리하는 부분을 일관되게 하며, ajax 호출하는 부분도 일괄되게 할수 있지 않을지? 소스는 좀더 하면서 가다들어야 할듯...

// ajaxHandler 연동

$("#frmFaxHistory").ajaxHandler({

validator : faxHistoryValidator,

successFunction: function(jsonResult, textStatus, httpRequest) {

alert(' success Function 사용자 정의 호출 시');

}//,

// errorFunction : function(httpRequest, ajaxOptions, thrownError){

// alert(' error Function 사용자 정의 호출시');

// }

});


Posted by 파이팅야
,

 

개책개인적으로 참고 되었으면 하는 내용... 에구 붙여넣기 하니 들여쓰기가 이상해 지넹...

C.    
P64) 스프링컨테이너가 DI를 해줘서 OracleArticleDao articleDao = new OracleArticleDao(); 와 같은 구문이 필요없게 되고 해당 구문이 class에 들어가 있으면 의존성이 강해지게 된다.

D.    P70) lookup method injection

ii.        Singleton bean들은 spring container가 처음 한번만 bean 객체를 생성할 때 해당 singleton bean들의 property나 생성자에 DI를 한다. 그러므로 singleton bean에서 non-singleton bean DI할 수가 없다.(non-singleton bean끼리는 호출시점에 2개의 객체가 생성되어 서로 DI하면 되고, non-singleton에서 singleton을 참조하는 경우는 호출 시점에 non-singleton을 생성하고 초기에 이미 생성되어 있는 singleton DI하면 되므로 문제되지 않는다.)

1.   Application 이나 Business용 서버는 main 함수에서 applicationContext context에 접근할 때,

2.   WebApplication web.xml에서 <listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>를 등록해서 root web application context 생성하고 <servlet><servlet-class>org.springframework.web.servlet.DispatcherServlet></servlet-class>를 등록해서 servlet web application context를 생성한 시점에 spring container singleton bean객체들이 생성된다. Root web application

iii.        Singleton bean A non-singleton bean B를 사용한다고 할 때, container singleton bean A를 단지 container가 한번만 생성할 것이고, 따라서 property도 역시 한번만 설정될 것이다. Container bean B가 필요한 매 순간 새로운 객체를 생성하여(특정 method를 호출해서) bean A에게 제공해야 하는 경우 사용 함. 책의 예제에서는 getCommandFactory() method를 호출할 때마다 새로운 CommandFactoryImpl 객체가 생성되어 Processor.process()를 사용할 수 있다.

iv.        http://www.egovframe.org/wiki/doku.php?id=egovframework:rte:fdl:ioc_container:dependencies

v.        http://www.javajigi.net/display/SFL/Method+Injection

E.     Lazily-instantiated beans(spring container에서 바로 bean 생성하지 않고 호출시 생성)

vi.        <bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>

F.     p85) 자동설정은 사용하지 않는 것이 명확할 듯 싶습니다.

G.     p101) InitializingBean이나 DisposableBean과 같은 Spring 프레임웤 소스에 종속성이 있는 것을 사용하기 보다는 init-method destroy-method 속성을 이용하여 initializingBean DisposableBean을 대체 할 때 다음과 같은 장점이 있습니다. (http://whiteship.tistory.com/552)

vii.        초기화 메소드와 소멸자 메소드 이름을 마음대로 정할 수 있습니다.

viii.        Spring 프레임웤 종속성이 생기지 않습니다.

ix.        모든 bean들 에게 공통의 이름을 사용하고 싶다면 <beans /> 태그에서 default-init-method default-destroy-method 속성을 사용하면 됩니다.

x.        <beans /> 태그에 있는 defaut-xxx-mehot 속성을 <bean /> 태그에 있는 xxx-method oveeriding 합니다.

H.    P105) PropertyPlaceholderConfigurer @Configuration @Bean으로 정의하면 안된다. (http://toby.epril.com/?p=964)

xi.        class내에서 사용할 때는 @Value("${database.username}") private String username 과 같은 형식으로 사용하면 된다.

xii.        System property os명을 다음과 같이 가져올 수도 있음 @Value("#{systemProperties['os.name']}")

 

I.      P109) eclipse plug-in ResourceBundle Editor을 사용해서 properties 파일들을 수정하면 편함

xiii.        ApplicationContextAware를 상속받아서 사용하면 spring에 종속적으로 되므로 MessageSourceAccessor 를 사용해서 종속성을 줄여주는 것이 좋다. (http://masterofcoding.tistory.com/category/PROGRAMES/SPRING)

xiv.        ReloadableResourceBundleMessageSource를 사용하면 Application을 재시작하지 않아도 되고 변경된 내용을 참조하고 한글문제도 없다고 하나MessageSourceAccessor에서 읽는 문제가 있다고 좀더 테스트 해봐야 같습니다. Wmc java framework 그대로 사용함. (http://www.javajigi.net/pages/viewpage.action?pageId=2468)

A.     P 205) dispatcher servlet url-pattern '/'로 한 경우 javascript, css등의 static resource에 대해서 servlet이 처리 할 수 있도록 <mvc:resource>를 사용해야 한다. (http://blog.naver.com/PostView.nhn?blogId=kkkkang45&logNo=70092718296), <mvc:view-controller path="/" view-name="welcome"/> 것도 있음

B.     P 208) HTTP Header Accept라는 Header 값을 browser가 고정해서 전달해서 내가 원하는 midiatype으로 반환 받을 수 없으므로…, URL 확장자(http://localhost/test.json), request 'format' parameter, request accept header, defaultContentType 순으로 mediatype을 구하고 해당 mediatype에 맞는 viewResolver viewResolvers defaultViews에 정의되어 있는 값과 비교해서 적당한 것을 찾아서 해당 View를 사용하게 됩니다. ContentNegotiationViewResolver를 주로 사용합니다.

i.        http://dev.anyframejava.org/docs/anyframe/plugin/restweb/1.0.1/reference/html/ch03.html

ii.        http://stove99.tistory.com/19

C.     P 216) forceEncdoing property 값을 true로 해야 response 값도 encoding 됩니다.(http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/filter/CharacterEncodingFilter.html) 이 설정을 하면 form POST 방식으로 전송되는 것에 대해서 encoding이 되고, form GET방식으로 전송되는 것도 encoding하기 위해서는 apache server.xml의 내용을 수정해야 합니다. (http://springmvc.egloos.com/513986)

D.    P 259) return "account/creationForm"의 처리는 .net postback과 같이 생각하면 되면 입력했던 값들을 그대로 가지고 있다. 성공인 경우에는 redirect하는 것이 일반적임

 

E.     P 269) ASP.NET MVC와 같이 client validation 체크는 지원되지 않는다. Bean Validation을 이용할려면 http://www.slideshare.net/kingori/spring-3-jsr-303내용 참고 하고 error메시지를 properties 파일과 연동할려면 파일명을 'ValidationMessage_ko.properies'와 똑같이 만들고(물론 국가별로 '_xx' 는 추가해서 넣고) annotation명에 대한 validation 실패 에러메세지를 같이 작성한다.('예제로 쉽게 배우는 스프링 프레임워크 3.0' 책의 p436~440, p447 내용 참고하세요… 책은 제 자리에 있습니다.)

iv.        Javax.validation.constraints.Min.message={value}이상인 값을 입력해 주세요.

v.        Org.hibernate.validator.constraints.NotEmpty.message=입력해 주세요

F.     P 272) 여러 파일 첨부기능을 확인 할 때 아래의 코드를 참고하고, 파일 확장자는 'StringUtils.getFilenameExtension("testFolder/testjco.txt")'로 구할 수 있음

vi.        List<MultipartFile> multipartFileList = multipartHttpServletRequest.getFiles("file");

vii.             multipartFileList.isEmpty() ß 파일 첨부 안 해도 true값임

viii.         

ix.        for(MultipartFile multipartFile : multipartFileList) {

x.            multipartFile.isEmpty() ß 파일 첨부했는지 확인 가능 함

G.     P 287) SimpleMappingExceptionResolver를 사용해서 특정 Exception일 때 특정 view를 호출하도록 하는 것 많이 사용함, spring에서 발생하는 에러와 jsp에서 발생하는 에러 처리 관련(http://cranix.net/184http://cranix.net/186)

2.     기타

기타 참고 내용
H.   
Spring mvc ajax - http://maxheapsize.com/2010/07/20/spring-3-mvc-ajax-and-jquery-magic-or-better-simplicity/

I.      Spring mvc log4j 관련 - http://www.mkyong.com/spring-mvc/spring-mvc-log4j-integration-example/

J.      view에서 상대경로 참고할 때 - http://www.sivalabs.in/2011/07/context-root-relative-urls-using.html

K.     Spring mvc 3.1 에서 변경된 것 - http://blog.springsource.org/2011/06/13/spring-3-1-m2-spring-mvc-enhancements-2/

L.     jstl 관련 - http://sunfuture.springnote.com/pages/3514717

M.    myBatis 관련 - http://kaludin.egloos.com/2717395, http://www.mybatis.org/spring/sample.html

Posted by 파이팅야
,

spring mvc 3 date

Spring 2012. 3. 19. 19:56
화면에 특정 날짜형식으로 입력 받고, Controller에서 입력받은 값을 특정 날짜 형식으로 Model에 Binding하고 입력사항 검사도 할려면 다음과 같이 하면 될것 같다.

spring context에 'config.properties' file을 추가하고
<bean id="propertyPlaceHolder"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config.properties</value>
</list>
</property>
<property name="fileEncoding" value="UTF-8"/> 
</bean>

'config.properties'에 다음과 같이 date format pattern을 넣고
date.format=yyyy-MM-dd
datetime.format=yyyy-MM-dd HH:mm:ss

joda-time-2.1.jar 파일을 다운받고 다음과 같이 model에 @DateTimeFormat(parrent="${date.format}")를 입력해서 property파일과 연동 한 후
public class DateModel {
private String name;
@DateTimeFormat(pattern="${date.format}")
private Date registerDate;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getRegisterDate() {
return registerDate;
}
public void setRegisterDate(Date registerDate) {
this.registerDate = registerDate;
}
@Override
public String toString() {
return "DateModel [name=" + name + ", registerDate=" + registerDate
+ "]";
}
}
 
jsp파일에서 다음과 같이 입력 받거나 출력하면 된다.
<form:input path="registerDate"/> 

<spring:eval expression="dateModel.registerDate" />  


@DateTimeFormat를 사용하지 않은 Model의 날짜 형식을 다르게 해서 출력하고 property file의 내용을 참고 할려면... spring tag가 없는것 같아서 다음과 같이 해야 할것 같다..
Controller에 다음의 코드를 넣고

@Value("${date.format}")
private String dateFormat;

@ModelAttribute("dateFormat")
public String dateFormat() {
return this.dateFormat;
}

jsp파일에서 다음과 같이 사용한다.(깔금한 방법은 아니다. spring tag로 지원이 되어야 할것 같다. 아니면 다른 방법이 있는지... 찾아 봐야 할듯...)
<fmt:formatDate pattern="${dateFormat}" value="${communication.registerDate}"/> 


이렇게 하면 config.properties안의 날짜형식 pattern 값으로 날짜 형식을 관리 할 수 있을듯...
jQuery의 datepicker를 사용하는 부분은 다음과 같은 .js파일을 만들고 datepicker사용하는 부분에서 include해서 사용하고 국가별로 변경해서 사용하면 될듯...
jQuery(function ($) {
    $.datepicker.regional['ko'] = {
        monthNames: ['. 01', '. 02', '. 03', '. 04', '. 05', '. 06', '. 07', '. 08', '. 09', '. 10', '. 11', '. 12'],
        dayNamesMin: ['일', '월', '화', '수', '목', '금', '토'],
        showMonthAfterYear: true,
        dateFormat: 'yy-mm-dd',
        showOn: 'both',
        buttonImage: 'http://static.xxx.co.kr/calendar.gif',
        buttonImageOnly: true,
        changeYear: true
        //        yearSuffix: '',
        //        firstDay: 0,
        //        weekHeader: 'Wk',
        //        closeText: '닫기',
        //        prevText: '이전달',
        //        nextText: '다음달',
        //        currentText: '오늘',
    };


    $.datepicker.setDefaults($.datepicker.regional['ko']);
});
 
Posted by 파이팅야
,
code 관리를 enum으로 할 때 해당 코드들을 웹 화면에 comboBox, radiobutton, checkbox 등등으로 보여줄때 다국어 지원이 안된다. (<form:select> 안에 resourceBundle과 연동되어 처리되는 attribute가 있었으면 좋을련만...) 그럴때 다음과 같이 하면 어떨지... 간단하게 만들어봄.... refactoring은 더 해야 하지만...(DB에는 short type의 column에 숫자 코드값만 들어가는 경우임, DB의 table에 코드성 데이터가 있더라도 동적으로 추가 가능한 기능(페이지)가 없다면 enum으로 관리 할 수도 있을것 같다.


spring context에

<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:/message/validation</value>
<value>classpath:/message/label</value>
</list>
</property>
       <property name="defaultEncoding" value="UTF-8" />
       <property name="cacheSeconds">
           <value>60</value>
       </property>
</bean>

label_ko.properties 에 다음과 같이 WriterTypeCode에 대한 label명을 넣고
label.WriterTypeCode.CUSTOMER = \uACE0\uAC1D
label.WriterTypeCode.MANAGER  = \uAD00\uB9AC\uC790
label.WriterTypeCode.SYSTEM   = \uC2DC\uC2A4\uD15C

Code interface를 만들고
public interface ICode {
short getCode();
}


사용할 Code enum을 만들고
public enum WriterTypeCode implements ICode {
CUSTOMER((short)1),
MANAGER((short)2),
SYSTEM((short)3);
private short code;
private String codeLabel;
private WriterTypeCode(short code) {
this.code = code;
}
@Override
public short getCode() {
return code;
}
public void setCode(short code) {
this.code = code;
}
public String getCodeLabel() {
return codeLabel;
}
public void setCodeLabel(String codeLabel) {
this.codeLabel = codeLabel;
}
}

controller에서 modelAttribute를 만들고
@Controller
@RequestMapping("/code/*")
public class CodeEnumController {

@Autowired
private MessageSource messageSource;

@ModelAttribute("writerTypeCodes")
public EnumSet<WriterTypeCode> writerTypeCodes() {
EnumSet<WriterTypeCode> writerTypeCodes = EnumSet.allOf(WriterTypeCode.class);
String message = null;
for(WriterTypeCode writerTypeCode : writerTypeCodes) {
message = this.messageSource.getMessage(
"label.WriterTypeCode." + writerTypeCode.name(), null, Locale.getDefault());
writerTypeCode.setCodeLabel(message);
}
return writerTypeCodes;
}

@RequestMapping(value="/code", method=RequestMethod.GET)
public ModelAndView code() {
CodeGroup codeGroup = new CodeGroup();
codeGroup.setName("testName");
codeGroup.setOrder(1);
codeGroup.setWriterTypeCode(WriterTypeCode.MANAGER);
ModelAndView mav = new ModelAndView();
mav.setViewName("code/code");
mav.addObject("codeGroup", codeGroup);
return mav;   



jsp에서 보여준다.
<form:select path="writerTypeCode" items="${writerTypeCodes}" itemLabel="codeLabel" itemValue="code"></form:select> 

<spring:message code="label.WriterTypeCode.${communication.writerTypeCode}" 
var="writerTypeCodeValue" />
writerTypeCode [${writerTypeCodeValue}]


post 방식으로 Model에 binding될때는 Controller에 다음의 코드를 넣고
@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(WriterTypeCode.class, new CodePropertyEditor(WriterTypeCode.class));
}

@RequestMapping(value="/code", method=RequestMethod.POST)
public ModelAndView codePost(@ModelAttribute CodeGroup codeGroup ) {
System.out.println("################## codeGroupResult[" + codeGroup + "]");
ModelAndView mav = new ModelAndView();
mav.setViewName("code/code");
mav.addObject("codeGroup", codeGroup);
return mav;   
}

CodePropertyEditor 를 만든다.
public class CodePropertyEditor extends PropertyEditorSupport {
@SuppressWarnings("rawtypes")
Class clazz; 

@SuppressWarnings("rawtypes")
public CodePropertyEditor(Class clazz) {
this.clazz = clazz;
}

@Override
public void setAsText(String text) throws IllegalArgumentException {
if (!StringUtils.hasLength(text)) {
super.setValue(null);
throw new IllegalArgumentException();
}
if (!text.matches("\\d+")) {
super.setValue(null);
throw new IllegalArgumentException();
}
if (!ICode.class.isAssignableFrom(this.clazz)) {
super.setValue(null);
throw new IllegalArgumentException();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
EnumSet typeCodes = EnumSet.allOf(this.clazz);
int code = -1;
int parameterCode = Integer.valueOf(text);
for(Object typeCode : typeCodes) {
code = ((ICode)typeCode).getCode(); 
if (code == parameterCode) {
super.setValue(typeCode);
break ;
}
}
}

여기까지 하면 웹에서의 처리는 끝나고 DB쪽에 binding하기 위해서는 myBatis인 경우 다음과 같이 TypeHandler를 만들어서 연동 하면 된다.  다음과 같이 ACodeTypeHandler를 만들고 
public abstract class ACodeTypeHandler<E extends Enum<E>> implements TypeHandler<E> {
//** log와 validation 체크 해야 함
private EnumSet<E> enumSet;

public ACodeTypeHandler(Class<E> enumClass){
if(!ICode.class.isAssignableFrom(enumClass))
throw new IllegalArgumentException(); // 메세지 추가하기
this.enumSet = EnumSet.allOf(enumClass);
}
@Override
public E getResult(ResultSet resultSet, String columnName)
throws SQLException {
if (resultSet.wasNull())
return null;
return valueOf(resultSet.getShort(columnName));
}

@Override
public E getResult(ResultSet resultSet, int columnIndex)
throws SQLException {
if (resultSet.wasNull())
return null;
return valueOf(resultSet.getShort(columnIndex));
}

@Override
public E getResult(CallableStatement callableStatement, int columnIndex)
throws SQLException {
if (callableStatement.wasNull())
return null;
return valueOf(callableStatement.getShort(columnIndex));
}

@Override
public void setParameter(PreparedStatement preparedStatement, int parameterIndex, 
E parameter, JdbcType jdbcType) throws SQLException {
     if (parameter == null)
     preparedStatement.setNull(parameterIndex, Type.SHORT);
     else
     preparedStatement.setShort(parameterIndex, ((ICode)parameter).getCode());
}
public E valueOf(short value) {
for(E e: this.enumSet){
if((((ICode)e)).getCode() == value)
return e;
}
return null;
}
}

WriterTypeCodeTypeHandler 를 만들고
public class WriterTypeCodeTypeHandler extends ACodeTypeHandler<WriterTypeCode> {
public WriterTypeCodeTypeHandler() {
super(WriterTypeCode.class);
}
}

mybatis config 파일에 다음의 내용을 넣으면 preparedStatement로 값셑팅하기 전과 ResultSet으로 결과값 Model에 binding 할때 자동으로 변환 된다.
<configuration>
<typeHandlers>
<typeHandler javaType="net.board.type.WriterTypeCode" handler="net.board.type.handler.WriterTypeCodeTypeHandler" />
</typeHandlers>
</configuration>


모든 Controller에서 사용할 수 있도록 AnnotationMethodHandlerAdapter.webBindingInitializer에 등록해도 되고... 이렇게 하면 enum으로 코드 관리가 되지 않을지... 

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

mybatis와의 연동을 위해서는 ICode를 사용해야 하지만... 웹에서 comboBox나 listBox등에 enum의 내용을 출력하고 결과값을 해당 모델에 바인딩할때 ICode를 제거하고 spring:message와 연동해서 roof 돌면서 보여주고 '선택해 주세요'와 같은 것이 제일 상단에 보여지는 경우에는 결과값을 ''으로 해서 처리하면 '@ModelAttribute("writerTypeCodes")'의 소스코드와 initBinder()의 소스코드를 사용하지 않아도 된다. 예로 다음같이 view에서 enum값을 화면에 출력하고

<%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:enumSelect path="modifyTypeCode" type="Header" ></t:enumSelect>


다음과 같은 tag 내용이면 modifyTypeCode용 @ModelAttribute가 필요없고 initBinder()에 소스추가도 필요없게 된다.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>

<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>

<%@ attribute name="path" type="java.lang.Object" rtexprvalue="true" required="true" %>

<%@ attribute name="type" type="java.lang.String" rtexprvalue="true" required="false" %>

<spring:eval expression="'${nestedPath}'.substring(0, '${nestedPath}'.length() - 1)" var="basePath"/>

<spring:eval expression="${basePath}" var="baseBean"/>

<spring:eval expression="new org.springframework.beans.BeanWrapperImpl(baseBean)" var="beanWrapper"/>

<spring:eval expression="beanWrapper.getPropertyType('${path}')" var="pathClass"/>

<!-- Create a select option for each of the enum constants. -->

<form:select path="${path}">

    <c:if test="${type == 'Header'}">

            <form:option value=""><spring:message code="${pathClass.simpleName}.Header"/></form:option>

    </c:if>

    <c:if test="${type == 'All'}">

            <form:option value=""><spring:message code="All"/></form:option>

    </c:if>

    <c:forEach items="${pathClass.enumConstants}" var="enumConst">

        <!-- Value of option is the enum constant name, which spring knows

         how to bind using enum.valueOf(). -->

        <form:option value="${enumConst}">

        <!-- Label is pulled from propfile as simpleName.constant, e.g Color.RED -->    

        <spring:message code="${pathClass.simpleName}.${enumConst}"/>

        </form:option>

    </c:forEach>

</form:select>

Posted by 파이팅야
,

spring mvc 3 error log

Spring 2012. 3. 8. 17:44
에러에 대해서 급하게 정리 해봄

 web.xml에 http error와 web container error에 대해서 다음과 같이 기술하고
<!-- errorPage 설정 -->
   <error-page>
      <exception-type>java.lang.Exception</exception-type>
      <location>/WEB-INF/view/error/http/exception.jsp</location>
   </error-page>
   <error-page>
      <error-code>404</error-code>
      <location>/WEB-INF/view/error/http/404.jsp</location>
    </error-page>

spring mvc 설정에 다음과 같이 exception 발생 시 exception.jsp파일이 호출되게 한 후
<!--  Exception 처리 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">/error/exception/exception</prop>
</props>
</property>
</bean>
 

해당 jsp 파일에 다음의 내용을 추가해서 로그를 찍으면 좀더 유지보수에 좋지 않을지...
<%@ page import="org.apache.log4j.Logger" %>
<%
Logger logger = Logger.getLogger("spring exception");

// client의 cookie나 request Uri 정보등등을 로그에 남기기
String newLine = System.getProperty("line.separator");
StringBuffer sb = new StringBuffer()
.append("requestURL[").append(request.getRequestURL()).append("]").append(newLine)
.append("pathInfo[").append(request.getPathInfo()).append("],").append(newLine)
.append("method[").append(request.getMethod()).append("],").append(newLine)
.append("queryString[").append(request.getQueryString()).append("],").append(newLine)
.append("remoteHost[").append(request.getRemoteHost()).append("],").append(newLine)
.append("remoteAddr[").append(request.getRemoteAddr()).append("],").append(newLine)
.append("servletPath[").append(request.getServletPath()).append("],").append(newLine);
    // http header값도 로그에 추가
java.util.Enumeration names = request.getHeaderNames();
    String headerName;
    while (names.hasMoreElements()) {
    headerName = (String)names.nextElement();
    sb.append(headerName).append("[").append(request.getHeader(headerName)).append("],").append(newLine);
    }
    sb.append("serverName[").append(request.getServerName()).append("]");
logger.error(sb.toString());
// Exception 정보도 로그에 추가
if (request.getAttribute("javax.servlet.error.exception") == null) {
logger.debug("request.getAttribute('javax.servlet.error.exception') is null");
return; // Return from the JSP servelet handler.
}
// javax.servlet.error.status_code: 에러 상태 코드를 말해 주는 정수이다.
// javax.servlet.error.exception_type: 에러가 생기게 된 예외 형을 지적해 주는 클래스 인스턴스이다. 
// javax.servlet.error.message: 예외 메시지를 말해주는 스트링이며, 예외 컨스트럭터로 보내어 진다.
// javax.servlet.error.exception: 실제 예외가 없어지면 버릴 수 있는 객체이다. 
// javax.servlet.error.request_uri: 문제를 일으킨 리소스의 URI를 말해주는 스트링이다.
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
logger.error("spring exception", throwable);
%>

결과 화면
17:33:02,150 ERROR spring exception:101 - requestURL[http://localhost:8080/board2/WEB-INF/view/error/exception/exception.jsp]
pathInfo[null],
method[GET],
queryString[null],
remoteHost[0:0:0:0:0:0:0:1],
remoteAddr[0:0:0:0:0:0:0:1],
servletPath[/WEB-INF/view/error/exception/exception.jsp],
accept[*/*],
accept-language[ko-KR],
user-agent[Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET CLR 1.1.4322; .NET4.0C; .NET4.0E)],
accept-encoding[gzip, deflate],
host[localhost:8080],
connection[Keep-Alive],
cookie[JSESSIONID=4218FE934DC460160394497F33DEE71E],
serverName[localhost] 

 
/WEB-INF/view/error/http/exception.jsp 파일에는 다음의 부분만 추가 및 수정하고...
response.setStatus(javax.servlet.http.HttpServletResponse.SC_OK);
Logger logger = Logger.getLogger("http exception"); 

 
asp.net mvc 에서는 다음과 같이 global.asax.cs에 넣고 ...
         protected void Application_Error(Object sender, EventArgs e)
        {
            if (Config.RedirectCustomErrorPage)
            {
                var exception = Server.GetLastError();
                var httpException = exception as HttpException;

                Response.Clear();
                Server.ClearError();

                var routeData = new RouteData();
                routeData.Values["controller"] = "Errors";
                routeData.Values["action"] = "ServerError";
                routeData.Values["exception"] = exception;

                Response.StatusCode = 500;

                if (httpException != null)
                {
                    /* 
                     * 100 Series : Informational 
                     * 200 Series : Success 
                     * 300 Series : Redirection
                     * 400 Series : Client Error
                     * 500 Series : Server Error
                     */
                    Response.StatusCode = httpException.GetHttpCode();
                    if (400 <= Response.StatusCode && Response.StatusCode < 500)
                        routeData.Values["action"] = "HttpError"; //400대 에러가 들어온다
                    else
                        routeData.Values["action"] = "ServerError"; // 500대 에러가 들어온다
                }

                IController errorController = new ErrorController();
                var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
                errorController.Execute(rc);
            }
            else
            {
                System.Text.StringBuilder sbErrMsg = new System.Text.StringBuilder();
                sbErrMsg.Append("[URL : (");
                sbErrMsg.Append(Request.Url.ToString());
                sbErrMsg.Append(")][URLRefferrer : (");
                sbErrMsg.Append(Request.UrlReferrer.ToString());
                sbErrMsg.Append(")][PATH_INFO : (");
                sbErrMsg.Append(Request.ServerVariables["PATH_INFO"].ToString());
                sbErrMsg.Append(")][REQUEST_METHOD : (");
                sbErrMsg.Append(Request.ServerVariables["REQUEST_METHOD"].ToString());
                sbErrMsg.Append(")][QUERY_STRING : (");
                sbErrMsg.Append(Request.ServerVariables["QUERY_STRING"].ToString());
                sbErrMsg.Append(")][HTTP_COOKIE : (");
                sbErrMsg.Append(Request.ServerVariables["HTTP_COOKIE"].ToString());
                sbErrMsg.Append(")][UserHostName : (");
                sbErrMsg.Append(Request.UserHostName.ToString());
                sbErrMsg.Append(")][HTTP_USER_AGENT : (");
                sbErrMsg.Append(Request.ServerVariables["HTTP_USER_AGENT"].ToString());
                sbErrMsg.Append(")][HTTP_ACCEPT_LANGUAGE : (");
                sbErrMsg.Append(Request.ServerVariables["HTTP_ACCEPT_LANGUAGE"].ToString());
                sbErrMsg.Append(")][SERVER_NAME : (");
                sbErrMsg.Append(Request.ServerVariables["SERVER_NAME"].ToString());
                sbErrMsg.Append(")]");

                Exception exErr = Server.GetLastError();
                Logger.GetInstance().Log.Error("(Global.Application_Error) Application Error, ["
                    + sbErrMsg.ToString() + "]", exErr);
            }
        }
Posted by 파이팅야
,