복습
DI Container
- 컨테이너로 객체 주입받기
- 컨테이너 객체 생성시점: 어플리케이션의 entry point
- Generic구조: 설정파일 위치가 동적으로 바뀔 수 있음
- 생성된 컨테이너로부터 필요한 빈 주입받아 쓰면됨
- 컨테이너의 계층구조
- 상위 컨테이너가 가진 공통빈들을 하위 컨테이너가 물려받는 구조
Web MVC
- springDispatcherServlet : 요청의 엔트리포인트 (기존: 프론트컨트롤러)
- 하나의 어플리케이션 안에서 관리저와 유저의 컨테이너를 분리할 수 있다 (서블릿2개등록)
- 문제점: 관리자, 유저 모두 상품정보 조회
- 상품에 대한 정보는 db에 저장
- 관리자, 유저 모두 persistence에 접근해야함
- ProdDaoImpl이 관리자, 유저 모두에 등록이되어야함
- 이럴때 사용하는게 계층구조 컨테이너
- 계층구조 컨테이너
- root-context: 웹에 종속되지 않는 빈, 하위컨테이너들도 쓸 빈을 등록
- controllr를 exclude
- component, service, repoistory, configuration
- servlet-context: 디스패처서블릿 생성시 등록할 수 있도록
- 기본설정을 끔
use-default-filters="false"
- include로 컨트롤러만 등록될 수 있도록
- auto di핵심: component-scan
- layer간의 의존관계
- 컨테이너를 사용하려면 처음부터 끝까지 쓰던지 아예 안써야함 -> 중간에 컨테이너가 사용하지 않는 코드가 들어가서 언제 nullpointer가 뜰지 모름
- new MemberServiceImpl() : 해봤자 컨테이너밖에 있기때문에 MemberServiceImpl의 어노테이션들이 작동하지않음
- 디스패처서블릿이 handlerMapping, handlerAdapter, view resolver 순서를 정함 -> servlet-context.xml
ViewResolver
- 커스터마이징되어있는 view의 위치 및 확장자를 등록해줘야함
- IRVR
- 마샬링과 직렬화를 대신해주는 뷰리졸버
- BeanNameViewResolver: bean id와 일치하는 곳으로 제어권 넘김
정적요청처리
- 톰캣이라는 was가 정적처리를 할 수 있었던 이유는 DefaultServlet으로 웹서버역할을 대신하고 있던것 (web.xml:98)
- springDispatcherServlet도 /로 매핑되어있어서 정적요청처리를 못하게되는 상황
- 정적요청의 위치는 다다름, 처리하는 컨트롤러를 만든다는게 쉬운일이 아님 -> 그래서 스프링에서 제공이 됨
servlet-context.xml
1번방법: 우린지금쓸수없음
<resources location="/resource/" mapping="/resources/**"></resources>
- /resources/ 구조를 쓸 때 장점
- 정적자원 모아서 관리할수 있다는 특성
- cache-period 속성추가할 수 있음: cache-period="0"
- cache와 관련된 설정을 자동으로 관리 -> 정적자원을 전혀 캐싱하지 않음
2. default-servlet-handler
<default-servlet-handler/>
- 정적자원을 일괄관리 할 수 없다는 것이 단점
- 정적자원에 대한 요청을 default-servlet으로 넘겨버림
- 캐시기간을 servelt-context.xml에서 할수없으니 필터에서 해결함
- web.xml에 등록한 filter의 객체를 관리하는 것은 톰캣, CharacterEncodingFilter는 spring container 밖에 있는 것임
- 그래서 이안에서는 spring container bean을 주입받을 수 없음
- 이것을 handler intercepter라는 기능으로 처리함
스프링의 이벤트 처리구조
- 시작과 종료가 명확하게 결정이 되어있다면 이벤트가 발생할 것
- 스프링에서는 이벤트처리구조를 지원해줌
- 상위컨테이너생성 시점에 cPath를 미리 만들어야함
@EventListener
파일업로드
파일업로드인지 판별
- 필터가 아닌 multipart resolver 형태로 관리
- 필터로 관리시 컨테이너 밖에있게됨
- commonsmultipartresolver
- standardservletmultipartresolver : 일반 파트
- multipartResolver: hanlder adapter가 받아서 멀티파트에 대한 처리를 함
Handler Adapter 이용한 객체 검증
- @RequestParam에 대한 검증을 H.A가 하고있음
- 그러면 command object도 H.A가 해야겠지?
@Valid
는 인서트안되네
@Validated(InsertGroup.class)
<annotation-driven>
을 넣었을때 Validator(우리가만든 CommonValidator랑 비슷)가 같이 들어가서 이 어노테이션만으로도 검증이 되는것
- 검증결과는 어쩌죠?
- Map이 사라져서 리퀘스트에도 못담고 그럼 jsp에서
${errors.mem_id}
도 이제 못씀
- 커스텀태그를 사용하여 해결해야함
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
- 폼 인풋을 만들어줌, 실패하면 초기값으로 id가 들어가있다?
- ``<form:input path="mem_id"/>
- 근데 이거쓰면 jquery가 복잡해지고 서버사이드에서 추가설정ㅇ이 있음
- 그래서 이거 안쓸거지만 검증결과메시지는 이걸로 받아올거야
<form:errors path="모델에들어간이름.프로퍼티명" element="span" cssClass="error"></form:errors>
정규식으로 바꾸기
<span class="error">\$\{errors\.mem_(\w+)\s*\}</span>
<form:errors path="mem_$1" element="span" cssClass="error" />
표기 주의 ★
public String insert(@Validated(InsertGroup.class) @ModelAttribute("member") MemberVO member,
Errors errors, Model model){
} // MemberVO 다음 바로 Errors
- 검증대상 커맨드오브젝트 다음에 errors 넣어줘라
커스텀 에러메시지 사용
- 커스텀태그 fmt 사용시
- 우선 스프링 국제화 태그부터
스프링 국제화 태그
기본사용
1. 메시지 번들만들기
SELECT LOWER(TABLE_NAME)||'.'||LOWER(COLUMN_NAME)||'='||COMMENTS
FROM USER_COL_COMMENTS
WHERE TABLE_NAME = 'MEMBER';
2. 번들 컨테이너에 로딩하기
- view에서 사용할 거니까 메시지번들 로딩을 하위컨테이너인 servlet-context에서 함 (루트도되긴하지만 view는 웹에 종속되어있기때문에)
<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="kr.or.ddit.msg.message"
/>
- 지금은 서버의 로케일을 따라가는데, 추후 수정할 예정
3. view에서 커스텀태그로 사용하기
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<thead>
<tr>
<th><spring:message code="member.mem_id"/></th>
<th><spring:message code="member.mem_name"/></th>
<th><spring:message code="member.mem_hp"/></th>
<th><spring:message code="member.mem_mail"/></th>
<th><spring:message code="member.mem_add1"/></th>
<th><spring:message code="member.mem_mileage"/></th>
</tr>
</thead>
4. HandlerIntercepter 사용하여 적용하기
- 로케일을 한번에 적용하자니,,
- 컨트롤러 마다 쓴다면 중복파티
- 디스패처서블릿에 쓰자니 중요하지도 않은 부가기능을 넣기가좀;;
- 필터를 쓰자니 컨테이너 밖에 있는놈이고.. 어쩌지
- handler intecepter는 handler adapter와 controller사이에서 필터의 역할을 해준다!!
- servlet-context.xml
intercepters = filter chain (여러개 등록가능)
- H.I 동작순서에 맞게 LocaleChangeInterceptor가 작동
- lang 이 있으면 그에맞는 로케일로 변경
- lang 이 없으면 클라이언트 리퀘스트 로케일
<interceptors>
<beans:bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"
p:paramName="lang"
/>
</interceptors>
- 매핑 정보 생략시 전체매핑과같음
- 파람디폴트값은 locale
<interceptors>
<interceptor>
<mapping path="/**" />
<beans:bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"
p:paramName="lang"
/>
</interceptor>
</interceptors>
위와 같음
근데이렇게 돌리면터짐, 쿠키에 저장해야하니까 쿠키로케일리졸버
<beans:bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"
p:cookieName="localeCookie"
p:cookiePath="/"
p:cookieMaxAge="#{60*60*24*7}"
/>
이게있어야함
객체 검증 및 메시지 관련
<beans:bean id="errorMessageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="kr.or.ddit.msg.errorMessage"
/>
<beans:bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"
p:validationMessageSource-ref="errorMessageSource"
/>
스프링 사용 시 주의점
- 필요한 객체는 더이상 생성하지 않고 injection 받아야한다
- 스프링 개발자의 실력은 session, request, response 를 최대한 안쓰도록 노력, handlerapdater를 최대한 활용할 것
- jsonView써서 마셜링하려면 꼭 Model에 담아야한다. req에 넣어봤자 모름
미션