본 포스팅은 프로그래머스 미니 데브 코스를 공부하며
학습을 기록하기 위한 목적으로 작성된 글입니다.
Spring MVC는 어떻게 웹 어플리케이션의 개발을 도와줄까?
이미지 출처 : https://mossgreen.github.io/Servlet-Containers-and-Spring-Framework
요약
DispatchServlet 장점
DispatchServlet Flow
알아두자!
자세한 흐름
출처 : https://terasolunaorg.github.io/guideline/public_review/Overview/SpringMVCOverview.html
XML
, JSON
, JSP
등일 수 있다.Front Controller에 등록된 다른 컨트롤러를 호출해서 로직 처리를 위임 -> 응답을 받아 뷰 생성
서블릿 등록 시 web.xml, WebApplicationInitializer(스프링 제공 방법)을 이용할 수 있었다.
WebApplicationInitializer의 구현체를 사용하면 WebApplicationContext를 만들어서 등록할 수 있다.
서블릿을 만들어서 연결시켜준다.
패스 경로를 등록시켜준다.(app아래의 요청은 모두 dispatcher서블릿으로 가도록)
단 하나의 서블릿
서블릿을 여러 개 만들 수는 있 지 만
스프링은 하나의 서블릿 하위에 컨트롤러를 등록하는 방식으로 어플리케이션을 만들게 해준다.
이미지 출처 : https://mossgreen.github.io/Spring-Certification-Spring-MVC/
뷰 이름
에 매칭되는 뷰를 return한다. XML
, JSON
, JSP
등일 수 있는데, InternalResourceViewResolver는 JSP
를 처리하기 위해 쓰이는 뷰 리졸버.해결방법(영역 분리)
실습 코드
[scr - main - webapp - resources]에 이미지 리소스 추가
resourceResolver(중요)
@EnableWebMvc // Spring MVC에 필요한 bean들 자동 생성
@Configuration
@ComponentScan(basePackages = "org.prgrms.kdt.customer")
@EnableTransactionManagement
static class AppConfig implements WebMvcConfigurer {
…
// ResourceHandler 추가
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**") // resource에 대한 전체 요청 발생 시
.addResourceLocations("/resources/") // resource에 대한 위치 설정 - resources 하위로 매핑
.setCachePeriod(60) // 리소스에 캐시 설정 가능
.resourceChain(true) // 리졸버 체인 설정 :
.addResolver(new EncodedResourceResolver()); // Request Header의 Accept-Encoding 정보를 보고 resource를 gzip형태로 매핑?
}
알아두자
jstl
의존성 필요
뷰 처리에 이용하는 비율
URL 매핑은 개별 메소드 단위
spring-boot-starter-thymeleaf
의존성 필요
// ApplicationInitializer
@EnableWebMvc // Spring MVC에 필요한 bean들 자동 생성
@Configuration
@ComponentScan(basePackages = "org.prgrms.kdt.customer")
@EnableTransactionManagement
static class AppConfig implements WebMvcConfigurer, ApplicationContextAware {
// 특정 viewResolver setup
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp().viewNames("jsp/*");
// thymeleafViewResolver
var springResourceTemplateResolver = new SpringResourceTemplateResolver();
springResourceTemplateResolver.setApplicationContext(applicationContext); // springResourceTemplateResolver는 ApplicationContext필요
springResourceTemplateResolver.setPrefix("/WEB-INF/"); // Prefix 설정
springResourceTemplateResolver.setSuffix(".html"); // Suffix 설정
var springTemplateEngine = new SpringTemplateEngine(); // TemplateEngine 선언
springTemplateEngine.setTemplateResolver(springResourceTemplateResolver); // TemplateEngine에 TemplateResolver필요
var thymeleafViewResolver = new ThymeleafViewResolver();
thymeleafViewResolver.setTemplateEngine(springTemplateEngine); // thymeleafViewResolver에는 TemplateEngine필요
thymeleafViewResolver.setOrder(1); // 순서 설정
thymeleafViewResolver.setViewNames(new String[]{"views/*"}); // 뷰 이름 설정
registry.viewResolver(thymeleafViewResolver);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
}
// Controller
@Controller
public class CustomerController {
// 컨트롤러에서 jsp에 접근하기 위한 서비스 주입
private final CustomerService customerService;
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
1. ModelAndView를 리턴하는 방법
// GET메소드
// @GetMapping("/customers")
@RequestMapping(value = "/customers", method = RequestMethod.GET) // URL 매핑은 개별 메소드 단위
public ModelAndView findCustomers() {
var allCustomers = customerService.getAllCustomers();
// 뷰 이름을 views로 줌 -> HTML을 타고 Thymeleaf의 viewresolver를 타게 된다.
return new ModelAndView("views/customers",
Map.of("serverTime", LocalDateTime.now(),
"customers", allCustomers));
}
2. 모델을 주는 방법
// GET메소드
@GetMapping("/customers")// URL 매핑은 개별 메소드 단위
public String findCustomers(Model model) {
var allCustomers = customerService.getAllCustomers();
model.addAttribute("serverTime", LocalDateTime.now());
model.addAttribute("customers", allCustomers);
return "views/customers";
}
// 파라미터로 정의해두면 핸들러가 알아서 모델을 넣어준다.
}
Thymeleaf Expression
${OGNL}
Model
<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>
==> ctx.getVariable("today");
#{코드}
@{링크}
<img th:src="@{/resources/image.png}" class="img-fluid">
*{OGNL}
<tbody>
<tr th:each="customer: ${customers}" th:object="${customer}" >
<td th:text="${customer.customerId}"></td>
<td th:text="${customer.name}"></td>
<td th:text="${customer.email}"></td>
<td th:text="${customer.createdAt}"></td>
<td th:text="${customer.lastLoginAt}"></td>
</tr>
</tbody>
-- 선택변수식 적용
<tbody>
<tr th:each="customer: ${customers}" th:object="${customer}" >
<td th:text="*{customerId}"></td>
<td th:text="*{name}"></td>
<td th:text="*{email}"></td>
<td th:text="*{createdAt}"></td>
<td th:text="*{lastLoginAt}"></td>
</tr>
</tbody>
오류해결
- 뷰 처리 시 HTTP 상태 500 – 내부 서버 오류가 발생
1.절대 URI인 [http://java.sun.com/jsp/jstl/core]을(를), web.xml 또는 이 애플리케이션과 함께 배치된 JAR 파일 내에서 찾을 수 없습니다.
2.Request processing failed; nested exception is org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
해결 @Controller에서 생성한 GET메소드의 return값인 ModelAndView key값 오타.
ccustomer(x) -> customer(o)
새로 알게된 용어
- validation 체크 : 유효성 체크
- 정적 리소스(Static Resource)
클라이언트로 부터 요청이 들어왔을 때 요청에 대한 리소스가 이미 만들어져 있는 것.
TIP
gzip --keep --best -r src/main/webapp/resources
gzip으로 파일을 압축해주는 gzip 명령어를 윈도우 환경에서 사용하려면 intellij의 터미널 설정을 git shell로 바꿔주어야 한다. 👉 git shell로 터미널 바꾸는 방법
더 공부하면 좋을 포스팅
Rf