<-- 1. POST로 해당 url에 요청 -->
<form th:action="@{/customers/new}" method="post">
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<-- 2. input name을 설정해줄 것. -->
<input type="email" name="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Name</label>
<input type="text" name="name" class="form-control" id="exampleInputPassword1">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
@PostMapping("/customers/new")
public String addNewCustomer(CreateCustomerRequest createCustomerRequest){
customerService.createCustomer(createCustomerRequest.email(), createCustomerRequest.name());
return "redirect:/customers";
}
CreateCustomerRequest에서는 1번의 폼에서 전달해준 input의 name에 해당하는 요소를 받아 처리한다.
public record CreateCustomerRequest(String email, String name) {
}
@GetMapping("/customers/{customerId}")
public String findCustomer(@PathVariable("custoemrId") UUID customerId, Model model){
var maybeCustomer = customerService.getCustomer(customerId);
if(maybeCustomer.isPresent()){
model.addAttribute("customer",maybeCustomer.get());
return "views/customer-details";
}else{
return "views/404";
}
}
여러 서블릿이 공유가 가능한 내용을 Servlet Context에 저장
모든 서블릿들이 접근 가능한 루트 서블릿이 서블릿
WebApplicationContext: ApplicationContext를 상속받음. 거기에 Servlet Context접근하는 기능이 추가 되어 있음.
Servlet Context: 여러 서블릿이 공유가 가능한 정보를 담고 있음.
여러 dispatcher servlet에서도 접근이 가능함.
여러 dispatcher servlet이 만들어지면 여러 WebApplicationContext이 만들어짐.
각 Context는 어떤 관계를 가지는가? 모든 context에 접근 가능한 Bean이 없을까?
-> 모든 ApplicationContext들이 접근 가능한 root ApplicationContext이 필요함.
root ApplicationContext
Servlet Context가 만들어질때 root ApplicationContext가 생김.
Servlet Context에 attribute로 들어가있음.
그 후 생겨난 servlet들은 Servlet Context에 접근해 root ApplicationContext를 사용하게됨.
처음에 Dispatcher Servelet의 WebApplicationContext의 parent는 없다.
모든 빈들이 WebApplicationContext 컨테이너에 다 등록되어 있다.
-> root ApplicationContext을 만들고, 거기에 서비스랑 데이터 access 관련된 빈들을 등록하고 관리할것임.
-> dispatcher servelet 의 WebApplicationContext에는 mvc 관련된 빈만 등록하도록 하고 루트랑 부모 자식으로 연결되도록.
@EnableWebMvc
@Configuration
//컨트롤러에 해당하는 빈만 스캔하겠다.
@ComponentScan(basePackages = "org.prgrms.kdt.customer",
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class),
useDefaultFilters = false
)
static class AppConfig implements WebMvcConfigurer, ApplicationContextAware {
//스프링의 app context
ApplicationContext applicationContext;
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp().viewNames("jsp/*");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
//컨트롤러 제외하고 다른 빈들(서비스, 데이터관련)만 스캔하겠다.
@Configuration
@ComponentScan(basePackages = "org.prgrms.kdt.customer",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class)
)
@EnableTransactionManagement
static class RootConfig{
@Bean
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
//그 외 트랜잭션 및 jdbcTemplate 관련 Bean 설정
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//1.RootConfig로 root컨텍스트 만들어주기
var rootApplicationContext = new AnnotationConfigWebApplicationContext();
rootApplicationContext.register(RootConfig.class);
//2. 로드 리스너 설정해주기 -> 부모 자식 연결해줌
var loaderListener =new ContextLoaderListener(rootApplicationContext);
servletContext.addListener(loaderListener);
//3. DispatcherServlet은 AppConfig로 만들어주기
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(AppConfig.class);
var dispatcherServlet= new DispatcherServlet(applicationContext);
logger.info("Starting Server...");
ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("test", dispatcherServlet);
servletRegistration.addMapping("/");
servletRegistration.setLoadOnStartup(1);
}
CRUD 명 | 의미 |
---|---|
0 이상 | ServletContext 초기화 될때 ServletContextListener 호출해서 하위 인스턴스 다 생성 |
-1 | ServletContext 초기화, 이후에 사용될때 각 서블릿 인스턴스 생성하고 초기화 진행 |
디폴트 값이 -1로 정의되어 있음.
REST(Representational Transfer): 웹 상의 자료를 HTTP위에서 SOAP이나 쿠키를 통한 세션 트랙킹 같은
별도의 전송 계층 없이 전송하기 위한 아주 간단한 인터페이스
API(application programming interface) :
REST API : REST 아키텍쳐 스타일을 따르는 API
REST 아키텍쳐 스타일
균일한 인터페이스 : URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍처 스타일
SOAP : 단 하나의 url에 요청을 xml에 기술하고 전달
LV 1: 하나의 리소스에 대해 다양한 형태로 표현이 가능하다
LV 2: http 메서드(get,post,put,delete)를 도입하는 것
LV 3: HATEOAS(Hypermedia as the Engine of Application State)
API 설계
실습
스프링에서 레스트api 개발을 위해 annotation 제공
1. RequestBody 전달받은 요청 메시지를 원하는 형태로 변환.
2. ResponseBody 우리가 결과낸 모델클래스가 response로 변환.
3. RestController Controller + ResponseBody
RestController를 적용하면 하위 모든 메서드에 respnse Body가 생긴다.
Dispatch Servlet의 요청을 받아
핸들러매핑으로 핸들러 찾고, 핸들러 어댑터가 argument Resolver를 사용해서 적절히 변형해 핸들러에 전달
@RequestBody,@ResponseBody, HttpEntity를 리턴할경우, HTTP 메시지 컨버터가 동작해 결과 모델을 HTTP 메시지로 변환한다.
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//디폴트 메시지 컨버터가 싹 바뀐다.
var messageConverter = new MarshallingHttpMessageConverter();
var xStreamMarshaller = new XStreamMarshaller();
messageConverter.setMarshaller(xStreamMarshaller);
messageConverter.setUnmarshaller(xStreamMarshaller);
converters.add(messageConverter);
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//디폴트 메시지 컨버터에서 확장하는 방식으로 사용가능
//1.xml 방식으로 컨버팅하도록 추가
var messageConverter = new MarshallingHttpMessageConverter();
var xStreamMarshaller = new XStreamMarshaller();
messageConverter.setMarshaller(xStreamMarshaller);
messageConverter.setUnmarshaller(xStreamMarshaller);
converters.add(0, messageConverter);
//2. 날짜의 경우 변환해주는 다른 메시지 컨버터를 추가
var javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ISO_DATE_TIME));
var modules = Jackson2ObjectMapperBuilder.json().modules(javaTimeModule);
converters.add(1, new MappingJackson2HttpMessageConverter(modules.build()));
}