Service와 Repository는 Include 하고 Controller는 Exclude 한다.
Servlet Context설정
Service와 Repository는 Exclude 하고 Controller는 Include 한다.
이렇게 설정하는 이유는 스프링이Transaction을 적용하기 위해서이다.
스프링 트랜잭션은 AOP를 이용해서 해당 빈의 proxy를 만들어서 tx가 적용되는 bean을 바꿔치기 한다. 그러면 원래@Service(또는 @Transactional)어노테이션이 붙은 빈은 뒤로 숨고 tx가 적용된 proxy bean이@Service가 붙은 bean으로 대치된다.
만약Application Context와Servlet Context가 모든 stereotype의 컴포넌트를 풀 스캔 할 경우, tx 설정은Application Context에만 적용되어 있기 때문에Application Context의 @Service는 트랜잭션이 적용이 되지만Servlet Context의 @Service는 트랜잭션이 적용이 안된다.
Bean 객체는Servlet Context가 우선되므로@Controller가 동작하여 같은 context(Servlet Context)안에서 검색을 하고,@Service가 지정된 bean이 있으므로 이 bean을 사용한다. 이@Service가 지정된 bean은 트랜잭션 적용이 안 되어 있어 트랜잭션 처리가 안된다.
컨트롤러는 최종적으로 결과를 출력할 뷰와 뷰에 전달할 객체를 담고 있는 ModelAndView 객체를 리턴한다.
DispatherServlet은 ViewResolver를 사용하여 결과를 출력할 View 객체를 구하고, 구한 View 객체를 이용하여 내용을 생성한다.
JSP를 뷰 기술로 사용할 경우 다음과 같이 InternalResourceViewResolver 구현체를 빈으로 등록해주면 된다.
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="WEB-INF/view"/>
<property name="suffix" value=".jsp/>
// 이는 ViewResolver가 "WEB-INF/view/뷰이름.jsp"를 뷰 JSP로 사용한다는 것을 의미한다.
// 즉, 앞의 예에서 HelloController는 뷰 이름으로 "hello"를 리턴하므로,
// 실제로 사용되는 뷰 파일은 "WEB-INF/view/hello.jsp"파일이 된다.
</bean>
InternalResourceViewResolver는 컨트롤러가 지정한 뷰 이름으로부터 실제로 사용될 뷰를 선택하는데, 이 때 컨트롤러가 지정한 뷰 이름 앞뒤로 prefix 프로퍼티와 suffix 프로퍼티를 추가한 값이 실제로 사용될 자원의 경로가 된다.
2. ViewResolver 구현 클래스
스프링 컨트롤러는 뷰에 의존적이지 않다. 컨트롤러는 아래 코드와 같이 결과를 생성할 뷰의 이름만 지정할 뿐이다.
컨트롤러가 지정한 뷰 이름으로부터 응답 결과 화면을 생성하는 View 객체는 ViewResolver가 구한다.
스프링은 몇 가지 ViewResolver 구현 클래스를 제공하고 있는데, 이중 주료 ViewResolver 구현 클래스는 다음과 같다.
3. ContentNegotiatingViewResolver 이해하기
ContentNegotiatingViewResolver 는 View 를 찾기위해 요청 URL의 확장자와 AcceptHeader를 사용하는 ViewResolver 입니다.
자체적으로 View 를 찾지는 않으며 viewResolvers 에 설정된 ViewResolver 를 사용하여 View 를 찾습니다.
ContentNegotiatingViewResolver의 기본적인 설정방법은 아래와 같습니다.
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<!-- 확장자와 contentType 을 연결해 준다. -->
<property name="mediaTypes">
<map>
<entry key="atom" value="application/atom+xml"/>
<entry key="html" value="text/html"/>
<entry key="json" value="application/json"/>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<!-- 위의 viewResolvers 에 의해 view를 얻지 못했을 경우에 사용되는 view -->
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
</list>
</property>
<property name="defaultContentType" value="application/json" />
</bean>
mediaType은 URL 의 확장자와 contentType 을 연결해주는 일종의 맵입니다.
http://localhost/user.html 이 들어왔을 경우에는 URL 의 확장자인 html 에 연결된 text/html (ContentType) 을 처리하는 View 를 찾게되고
http://localhost/user.json 이 요청되었을 경우에는 위와 마찬가지로 URL의 확장자인 json 과 연결된 application/json (ContentType) 을 처리하는 View 를 찾습니다.
viewResolvers ContentNegotiatingViewResolver 가 View 를 찾기위해 사용하는 ViewResolver 들
defaultViews는 viewResolvers 에 의해 view 를 찾지 못했을 경우에 사용되는 view 입니다.
MappingJacksonJsonView 상속 관계를 보면 최상위에 org.springframework.web.servlet.View 인터페이스가 있습니다.
org.springframework.web.servlet.View 인터페이스 내에는 해당 View 가 처리할 수 있는 ContextType 를 정의해야 하는getContentType()메소드가 있는데 여기서 리턴되는 값을 통해서 해당 View 가 처리해야할 ContentType 을 정의하고 있습니다.
MappingJacksonJsonView 에서 직접적으로 getContentType() 메소드를 구현하지 않는데 구현은 AbstractView 에 이미 들어가 있습니다.