Clean Code that Works.

오 갓~
이런 멋쟁이들이 ReflectionTestUtils 라는 아주 좋은 클래스를 만들어 두다니!!!

컨트롤러 테스트 케이스를 작성 중인데..
컨트롤러에서는 mav로 리턴한 값들을 체크하는게 주요 목적이다.

여기서 리턴한 값들 중에 가장 중요한것이 return url이 잘 맞게 넘어 오느 냐 확인하는 것이다.

근데 이것들 테스트 하기 위해서는 컨트롤러를 실행 시키는 테스트를 만들어야 하는데...
여기서 부터 고민에 빠지기 시작한다.
컨트롤러 안에 있는 Service코드 호출 하는 코드들은 어떻게 할 것인가.

컨트롤러를 @Autowired 해서 하면, Service 및 Repository로 주입 받아서 하기 때문에, 원하지 않는 통합 테스트가 되어 버린다.
컨트롤러 -> 서비스 -> 레파지토리

웁스 -,.-;;

하여, 열심이 구글링을 좀 해봤는데..
사실 목으로 서비스 만들어서 이것을 어떻게 리플렉션으로 해서 컨트롤러에 있는 서비스를 목 서비스로 바꿔 줄 수 없나.. 하는 방법을 찾고 있었다.

그런데 있었다. ㅋ_ㅋ

위 ReflectionTestUtils 라는 클래스를 통해서, 컨트롤러 클래스의 서비스 인스턴스를 목 인스턴스로 바꿔치기 할 수 있다.!!

ReflectionTestUtils.setField(action, "adManageBO", admanageBO);

위에처럼!!! 자세한 내용은 문서를 보면 아주 알 수 있다. 

목키토는 http://mockito.org/ 참고




스프링에서는 Spring Web MVC ModelAndView objects를 테스트하기 위한 메서드를 몇가지 제공 하는데, 
이것에 대해서 알아보자.


assertAndReturnModelAttributeOfType : 모델에서 해당 값이 특정 타입과 일치하는지 확인한다.

assertCompareListModelAttribute : List 타입의 모델의 값을 비교 한다.(sorting 제외)

assertModelAttributeAvailable : 모델에서 해당 값이 있는지 확인한다.

assertModelAttributeValue : 모델에서 해당 값이 expectedValue와 일치 하는지 확인 한다.

assertModelAttributeValues : 모델에서 해당 값이 expectedValue와 일치 하는지 확인 한다.(Map으로 여러개)

assertSortAndCompareListModelAttribute : 순서도 비교 해서 확인

assertViewName : mav가 리턴한 view 값과 일치 하는지 확인


테스트를 잘 작성 하자 @_@
단위 테스트에 집중!

http://blog.idm.fr/2010/09/spring-security-redirecting-to-faviconico.html

만약 스프링 시큐리티를 사용하여 웹사이트 전체를 로그인 폼과 함께 보안적용할 경우, 로그인 인증하는 중에 사용자가 "/favicon.ico"로 리다이렉트 되는 이슈가 발생할 수도 있다. 스프링 시큐리티 문서에서는 아래와 같이 설명되어 있다.

ExceptionTranslationFilter는 사용자가 요청한 원본 요청을 캐쉬 하고 있다. 사용자가 인증 되었을 경우, request handler는 캐쉬된 요청을으로 부터 원본 url 을 얻어서 그쪽으로 redirect한다. 원본 요청은 재 작성 되고 대안으로 사용된다.
이 이슈는 브라우저 캐쉬가 비어있고, 사용자가 이쪽으로 들어 온다면 다음과 같은 일이 발생한다.
  • 사용자가 "/" URL을 요청하고, 이 URL은 캐쉬 된다
  • 브라우저는 "/favicon.ico"에 요청을 한다. 이 URL은 인승시 redirect될 새로운 URL이 된다.
  • 사용자가 로그인 요청을 통해 로그인 한 후 "/favicon.ico"로 리다이렉트 된다.
이것을 수정 하기 위해서는 "/favicon.ico"를 인증을 안하는 resource로 변경을 해야 한다.

<intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" />



If you are using Spring Security to secure an entire website with form login, you might encounter an issue where users are redirected to "/favicon.ico" upon athentication. A note in the Spring Security documentation explains this:

The ExceptionTranslationFilter caches the original request a user makes. When the user authenticates, the request handler makes use of this cached request to obtain the original URL and redirect to it. The original request is then rebuilt and used as an alternative.
The issue is, when the browser cache is empty and a user comes in, here is what happens:
  • the user requests URL "/". This URL is cached.
  • the browser makes a requests to "/favicon.ico". This URL becomes the new URL where to redirect to upon authentication.
  • the user posts the login form and is redirected to "/favicon.ico".
To fix this, you need to set "/favicon.ico" as being a non-secured resources:

<intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" />

보통 프로퍼티 값을 Service나 Repository에서 가져다 쓸 때

<!-- Properties Files -->
<context:property-placeholder location="classpath:properties/*.properties"/>

처럼 프로퍼티 파일을 정의 해 두고,
@Value("${jdbc.username}") String userName;
이렇게 사용한다.

하지만, Controller에서 @Value("${jdbc.username}")로 주입을 받을려고 해 보면 안되고
값을 출력 해 보면 "${jdbc.username}" 값이 출력 된다.

왜 그럴까 해서 구글링을 좀 해 봤더니

http://www.dotkam.com/2008/07/09/spring-web-application-context-visibility/

아래에서 답을 알 수 있었다.

Here is the semi-official version “why” from Juergen (Spring Lead Developer):

“PropertyPlaceholderConfigurer is an implementation of the BeanFactoryPostProcessor interface: This interface and its sibling BeanPostProcessor just apply to the BeanFactory that defines them, that is, to the application context that defines them.

If you combine multiple config files into a single contextConfigLocation, a PropertyPlaceholderConfigurer defined in any of the files will apply to all of the files, because they are loaded into a single application context.

However, a DispatcherServlet has its own application context, just using the root web application context as parent. Therefore, it needs to define its own BeanFactoryPostProcessors and/or BeanPostProcessors, in this case its own PropertyPlaceholderConfigurer.”

Happy Springing!


알다 시피 웹 환경에서
부모(보통 applictionContext) - 자식(dispathcerServlet)으로 구성을 한다.
컨텍스트 영역이 다르 다는 말이다.

PropertyPlaceholderConfigurer는 BeanFactoryPostProcessors and/or BeanPostProcessors 이것인데
부모와 자식의 생성 주기가 틀리므로 부모의 PropertyPlaceholderConfigurer를 적용할 수 없다.
그래서 각자 자신의 PropertyPlaceholderConfigurer를 만들어서 써야 한다는 말



작년에는 스프링을 알고 썼다면, 올해는 이해하고 쓸 수 있도록 하자!!
화이팅!!

봄싹에서 메일 보내는 서비스 클래스 코드를 보면
new 해서 메일을 보내는 것을 확인할 수 있다.

이번에 메일 템플릿을 사용해서 메일을 보낼려고 하는데
이렇게 사용하면, 빈들을 DI 받아서 쓸 수가 없기 때문에 이 클래스를 프로토타입 빈 으로 만들어서 쓰기로 결정.

이 프로토타입 빈을 사용하는 서비스 빈에서는 DI 받아서 사용하지 말고(DI는 빈이 처음 생성될 때 한번만 진행되기 때문에,
프로토타입빈을 매번 똑같은것을 사용하게 됨) DL 받아서 사용하는 방식으로 하자.

기존의 코드(모임에 의견이 달리면 메일을 보내게 되어 있다.)
public void notifyCommentAdded(Meeting meeting, Comment comment) {
        Iterator<Attendance> Iter = meeting.getAttendances().iterator();
        Collection<Member> members = new ArrayList<Member>();
        while (Iter.hasNext()) members.add(((Attendance) Iter.next()).getMember());
        commentNotiService.sendMessage( new CommentMailMessage( comment, meeting, null, members));
    }

여기서 저기 new 하는 부분이 메일 템플릿을 생성하는 것이고, 실제 메일을 보내는것은 commentNotiService가 하게 된다.
여기서 문제점은 CommentMailMessage가 bean이 아니기 때문에,
CommentMailMessage에서 사용할 velocityEngine(벨로시티로 메일보낼때 사용) bean을 주입 받을 수 없다.
뭐 생성자에 추가해서 넘겨주면 되긴 하지만, 사실 저렇게 쓰는거 보다야 Prototype Bean을 만들어서 쓰는것이 좀 더 스프링 답다고 할까?

이 Prototype Bean과 이것을 사용하는 방법은 토비의 스프링3에 보면 잘 나와있다.
여기서는 책에 설명되어 있는것 중에서 Provider<T>를 사용하는 방법으로 bean을 생성해서 사용해 보도록 하자.

결론을 보자면..아래와 같이 되겠다.
@Inject Provider<CommentMailMessage> commentMail;

public void notifyCommentAdded(Meeting meeting, Comment comment) {
  Iterator<Attendance> Iter = meeting.getAttendances().iterator();
        Collection<Member> members = new ArrayList<Member>();
  while (Iter.hasNext()) members.add(((Attendance) Iter.next()).getMember());
  CommentMailMessage mail = commentMail.get();
  mail.setDatas(comment, meeting, null, members);
  commentNotiService.sendMessage( mail);
 }

CommentMailMessage를 빈으로 등록 해주는 것을 잊지 말고, get()해서 사용!!

샘플 SVN URL
https://dev.naver.com/svn/springstudy/security

최근 영문 포스팅
http://krams915.blogspot.com/2011/01/spring-security-simple-acl-using_19.html

봄싹 발표 URL
http://www.springsprout.org/study/4494/meeting/4596/presentation/4688

주의 해야 할 점.

IE 에서는 new Date(calendarObj.getDate)가 안 먹는다.
getDate를 slice 해서 year, month, day로 구분해 주자. ㄱ- 뭐이래

js에서 month 는 0 부터다. + 1, - 1을 적절히 해서 우리가 보는 것과 동일 하게 해주자.

numberOfMonths 옵션을 사용 할 때, css에 지정되어 있는 숫자는 최대 4까지다.(25%)
더 쓰고 싶으면 css에 추가 해주자
ex) .ui-datepicker-multi-5 .ui-datepicker-group { width:20%; }

js에서 getYear는 Deprecated 되고 getFullYear()를 사용 해야 한다.(ie 8에선 getYear가 여전히 작동)
ff에서는 안먹는데 왜 getYear 가 deprecated 됬지.

예전부터 UX에도 관심이 있던 터라..
읽는 중 @_@.
아래 반문은 기억해 뒀다가 나중에 말도 안되는 일정을 요구할 경우에 써먹어야겠다.
디자인 단계 뿐만 아니라 개발 단계에서도 적용할 수 있으니...

개발하다보면 일단 개발 부터 진행 할려고 하는데.. 나도 그렇고..
이를 지양 하고 디자인 프로세스를 잘 마무리 하고, 그 기간에 개발을 하고 싶으면 다양한 프로토타이핑으로 개발 단계를 준비하는 것으로 대체해야지.

start.

실무에서 프로젝트를 진행할 때 자주 접할 수 있는 상황이 있다. 이런 상황에서 오가는 말을 살펴보는 것으로 이 장을 마무리 할려고 한다.

이 제안은 무척 훌륭합니다. 이상적인 세계에서는 아주 좋은 방법이에요. 하지만 현실에서는 그렇지 않죠. 마감일도 맞춰야 하고 비용도 제한돼 있으니까요. 이런 프로세스를 일일이 거치기에는 일단 시간과 비용이 허락하질 않아요. 현실에서는 이상적인 것만 추구하는 건 불가능 합니다. '적당히 good enouth' 괜찮은 방법을 차용할 수밖에 없어요.


이 말을 들으면 나는 거의 항상 아래와 같은 두 가지 반문으로 응답한다.

제대로 된 기획과 디자인 방법을 적용할 만한 비용이 없다는 건 이해할 수 없어요. 제품 출시가 늦어지는 데 들어가는 비용은 항상 충당하고 있잖아요? 엉터리 기획과 디자인, 점검 방법 덕분에 쏟아지는 수많은 오류를 수정하는 데 들어가는 비용은 어디서 나오는 거죠?
제대로 된 기획과 디자인 프로세스를 적용하는데 필요한 비용은 오류가 가득한 엉터리 제품을 시장에 늦게 출시하는 데 들어가는 비용에 비하면 아무것도 아니에요. 그런데 어째서 비용이 허락하지 않는다는 거죠?


프로젝트 초기에는 디자인하려는 제품이 어떤 것인지 확실히 알 수 없다. 이런 사실을 인정하고 프로젝트를 관리해 나가기란 무척 어렵다. 따라서 프로젝트의 초기부터 제품을 결정해 버리고 개발 단계로 곧장 넘어가려는 경우가 많다. 하지만 현실은 그렇지 않다. 행운이 따르는 아주 극 소수의 경우를 제외하면 제대로 된 디자인 단계를 거치는 쪽이 훨씬 효과적이다. 시간과 비용을 절약하면서 훌륭한 제품을 제작하는 지름길이다.

바로 결련으로 넘어가버리는 지금의 방식보다는 명확한 디자인 단계를 프로세스에 도입하는 것이 훨씬 바람직 하다.

http://www.springsource.org/node/2966..

More content from SpringOne 2GX 2010 has just been posted on the InfoQ media library.

In this session Juergen Hoeller gives a presentation covering Spring and Java EE 6: Synergy or Competition?. This has been a topic of great interest recently and the presentation provides a good complement to Juergen's earlier blog post on a similar topic.

이번세션에서 유겐 휄러는 "스프링과 Java EE 6 : 시너지 인가 경쟁인가?"에 대한 발표를 했다.
이것은 최근에 큰 관심을 끄는 주제이고 이 프레젠테이션은 유겐이 예전에 포스팅한 비슷한 주제를 보완하고있다.

Juergen starts with a review of Spring's general deployment approach and then provides a brief review of some of the Java EE 6 specifications and how they relate to the Spring projects. Finally Juergen discusses some of the key considerations that developers and architects should think about when making a technology choice for their applications.

유겐은 스프링의 일반적인 배포 방법에 대해서리뷰를 하고 Java EE 6 명세와 이것이 어떻게 스프링 프로젝트들과 관계가 있는지 리뷰를 한다. 마지막으로 유겐은 개발자와 아키텍쳐가 그들의 어플리케이션을 만들 때 선택할 기술에 대해서 생각해야 할 몇몇 핵심 고려사항을 설명 한다.

Again, many thanks to InfoQ who was at SpringOne 2GX to capture so many of the great presentations and provided fantastic coverage for the show.

이런 발 번역 -_-


우옷!!!

아파치를 한번도 써보지 않았는데, 써야할 일이 생겨서 연동 방법에 대해 알아 보았다.

지금 하려는 방법은
로컬 PC에 아파치를 띄워놓고, 이클립스에서 띄워놓은 톰캣하고 연동을 시키는 것이다.

연동 방법 참고(http://theeye.pe.kr/240)

위 설명을 들어가 보면.. 리눅스 기반이긴 하지만, 뭐 거의 비슷하다.
하지만 이것은 이클립스의 톰캣이 아닌 실제 돌아갈 톰캣의 환경 설정을 변경하는 것이다.

그럼 이클립스에서의 톰캣과 아파치를 연동 할려면 어떻게 해야할까.
그냥 아파치 설정파일에서 모듈 한개 더 로드 하면 된다.

위 블로그 설명대로 하면,
[Thu Dec 02 19:33:10 2010] [error] [client 127.0.0.1] attempt to make remote request from mod_rewrite without proxy enabled: proxy:ajp://localhost:8009/
이런 오류가 나온다.

블로그 에서는 아래 두개의 so 파일을 로드 하도록 하게 했는데, 강조한 so 파일 한개를 추가로 해 주어야 한다.
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule proxy_module modules/mod_proxy.so

그런 다음에 가상 호스트 설정 파일을 불러 오게 지정 한 후.
Include conf/extra/httpd-vhosts.conf

아이님 블로그 설정 처럼 설정을 해준다.
<VirtualHost *:80>
    ServerAdmin
your@emailhere
    DocumentRoot "/var/www/html"
    ServerName localhost
    ErrorLog "logs/theeye.pe.kr-error.log"
    CustomLog "logs/theeye.pe.kr-access.log" common

    RewriteEngine On
   
RewriteCond %{REQUEST_FILENAME} .(htm|html|xhtml|css|jpg|gif|png|swf|js$)
    RewriteRule (.*) - [L]
    RewriteRule (.*) ajp://localhost:8009$1 [P]
</VirtualHost>


그 다음으로 ...는 없다.
이클립스의 톰캣 설정(server.xml)에서는 ajp 설정을 디폴트로 해주기 때문에,
포트를 특정 포트로 변경하지 않을 것이라면, 따로 지정해 주지 않아도 잘 연동 된다.

완료 후에 이클립스에서 톰캣 실행 후 아파치 실행하면 연결 할 수 있다.