본 포스팅은 프로그래머스 미니 데브 코스를 공부하며
학습을 기록하기 위한 목적으로 작성된 글입니다.
데이터 입력 페이지 제작
스프링이 웹 어플리케이션을 어떻게 만들까?
HTML의 폼은 부트스트랩을 이용해 처리할 수 있다.
Form 데이터로 받은 것을 Controller로 받는 POST 메소드 생성
도메인 모델에 접근하기 위한 서비스
// Controller
@Controller
public class CustomerController {
// 컨트롤러에서 jsp에 접근하기 위한 서비스 주입
private final CustomerService customerService;
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
// GET메소드
@GetMapping("/customers/new")
public String viewNewCustomerPage() {
return "views/new-customers"; // resources - viesws 폴더 아래에 html파일 생성
}
// POST메소드
@PostMapping("/customers/new") // URL은 같지만 HTTP 메소드는 다르다.
public String addNewCustomer(CreateCustomerRequest createCustomerRequest) { // Form data 매핑을 위한 클래스 생성 customerService.createCustomer(createCustomerRequest.email(), createCustomerRequest.name());
customerService.createCustomer(createCustomerRequest.email(), createCustomerRequest.name()); // customer생성은 customerService가 한다.(controller가 하지 않음) CreateCustomerRequest는 일종의 DTO.
return "redirect:/customers"; // 상세페이지 접속하거나 customers페이지 리다이렉트 가능
}
}
// CustomerSevice
public interface CustomerService {
void createCustomers(List<Customer> customers);
Customer createCustomer(String email, String name);
List<Customer> getAllCustomers();
}
// CustomerServiceImpl
@Service
public class CustomerServiceImpl implements CustomerService {
…
@Override
public Customer createCustomer(String email, String name) {
var customer = new Customer(UUID.randomUUID(), name, email, LocalDateTime.now());
return customerRepository.insert(customer);
}
}
// CreateCustomerRequest
// email과 name이 일치하면 form data를 객체화 시켜준다.
public record CreateCustomerRequest(String email, String name) {
}
상세페이지 접속 기능 구현 (과제)
// Controller
@Controller
public class CustomerController {
…
// Controller에 매핑하는 메소드
@GetMapping("/customers/{customerId}") // URL 패스 일부분을 변수화.
public String findCustomer(@PathVariable("customerId") UUID customerId, Model model){ // @PathVariable으로 매개변수와 customerId 매핑. 이때 형변환 발생. 문자열을 UUID로 자동변환 -> 실패 시 에러
var maybeCustomer = customerService.getCustomer(customerId);
if (maybeCustomer.isPresent()) {
model.addAttribute("customer", maybeCustomer.get()); // model 전달 ( customer 어트리뷰트 이용 -> DB에서 가져온 정보를 템플릿 작성 -> 뷰 출력 )
return "views/customer-details"; // view
} else {
return "views/404";
}
}
}
출처 : https://nesoy.github.io/articles/2019-02/Servlet
출처 : https://blog.csdn.net/demon7552003/article/details/103603877
IOC Container (Spring Container)
ApplicationContext
Bean Factory를 상속받아 확장
스프링이 관리하는 빈들이 담겨 있는 컨테이너.
ApplicationContext 인터페이스
를 구현한 객체들ApplicationContext는 인터페이스
이미지 출처 : https://www.oreilly.com/library/view/head-first-servlets/9780596516680/ch05s10.html
applicationContext
+ servletContext 접근기능 추가
컨트롤러
를 포함한 웹 관련 빈
을 등록하는 데 사용된다.
출처 : https://howtodoinjava.com/spring-mvc/contextloaderlistener-vs-dispatcherservlet/
이미지 출처 : https://velog.io/@dongeranguk/DispatcherServlet-%EC%84%A4%EC%A0%95
1. Web.xml
2. 코드 기반
웹 환경에서 Spring Application이 동작하는 방식을 살펴보자.
서블릿 컨테이너 안에 웹 어플리케이션이 만들어진다.
DispatcherServlet은
POJO bean 오브젝트
ApplicationContext.xml
web.xml
계층 기준
-1
으로 설정 가능장점
: 초반에 요청이 적을 때 서버를 빠르게 띄울 수 있다.// WebApplicationInitializer으로 서버등록
public class KdtWebApplicationInitializer implements WebApplicationInitializer {
private static final Logger logger = LoggerFactory.getLogger(KdtWebApplicationInitializer.class);
// WebapplicationContext(servletApplicationContext) 설정
@Configuration
@EnableWebMvc // Spring MVC에 필요한 bean들 자동 생성
@ComponentScan(basePackages = "org.prgrms.kdt.customer",
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class), // CustomerController만 assign되도록 설정
useDefaultFilters = false // 컴포넌트 스캔 시 다른 struct타입으로 annotation한 클래스들이 등록되는 것 방지
)
@EnableTransactionManagement
static class ServletConfig implements WebMvcConfigurer, ApplicationContextAware { … }
}
//root applicationContext 설정
@Configuration
@ComponentScan(basePackages = "org.prgrms.kdt.customer",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerController.class) // CustomerController가 아닌 것만 assign되도록 설정
)
@EnableTransactionManagement
static class RootConfig{ … } // webMVC 관련 설정 불필요. AppConfig에서 webMVC설정 제외한 설정 복사해오기
// onStartup 메소드
@Override
public void onStartup(ServletContext servletContext) {
logger.info("Starting Server...");
// rootApplicationContext 생성
var rootApplicationContext = new AnnotationConfigWebApplicationContext();
rootApplicationContext.register(RootConfig.class); // WebApplicationContext의 Bean 생성을 위한 메타데이터(설정정보) 전달
var loaderListener = new ContextLoaderListener(rootApplicationContext);
servletContext.addListener(loaderListener);
// ServletApplicationContext 생성
var applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(ServletConfig.class);
var dispatcherServlet = new DispatcherServlet(applicationContext);// DispatcherServlet 쓰려면 WebApplicationContext 필요
var servletRegistration = servletContext.addServlet("test", dispatcherServlet);// 테스트 서블릿을 추가할 수 있다.
servletRegistration.addMapping("/"); // 서블릿 매핑 시 넣었던 URL 패턴 입력
servletRegistration.setLoadOnStartup(-1); // setLoadOnStartup 옵션 -1으로 줘서 초기화
}
}
// 요즘은 컨테이너 자체를 서버로 분리하고 코드베이스도 분리시키는 형태
REST(ful) API에 대해 정말 이해하기 쉽게 정리해주신 글
👉 taeha7b 님의 API, REST API, RESTful API 개념정리
(개인적으로 공부한 내용을 다시 정리하면서 위 글을 정말 많이 참고하고 큰 도움을 받았다.)
RESTful API는 REST API 설계 가이드를 따라 API를 만드는 것이다.
RESTful API를 이해하기 위해서 알아야할 개념들을 쭉 정리해보았다.
API(Application Programming Interface)
웹 API
요청
과 응답
으로 작동한다.REST(Representational State Transfer)
리소스
를 HTTP URI로 표현하고, 리소스에 대한 행위
를 HTTP Method로 정의하는 방식이다.데이터
로 전송한다. 출처: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
REST인지 아닌지 판별해주는 모델
이미지 출처 : https://martinfowler.com/articles/richardsonMaturityModel.html
Level0
xml
로 정의 (응답도 xml
)POST
Level1
end Point
존재.
- representation data
hello- representation metadata
Content-Type: text/json // 콘텐트 타입 설정 가능
Content-Language: ko // 콘텐트 랭귀지 설정 가능
links필드
- 리소스로 할 수 있는 행위를 기술해준다.URI는 정보의 자원을 표현해야 한다.
❌ GET /members/delete/1
리소스명
은 동사보다는 명사를 사용한다. URI에서 동사는 GET, POST와 같은 HTTP Method를 표현하기 때문이다자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)로 표현한다.
✅ DELETE /members/1
❌ GET /members/show/1
✅ GET /members/1
✅ POST /task/1/run
슬래시 구분자(/)는 계층 관계를 나타내는 데 사용한다.
URI 마지막 문자로 슬래시(/)를 포함하지 않는다.
URI에 파일의 확장자(.json , .JPGE 등)을 포함 시키지 않는다.
가독성을 위한 가이드
스프링에서 REST API를 개발하기 위한 어노테이션
메소드
에 사용 OR 클래스 단위
사용@ResponseBody
, HttpEntity
를 return하면 동작JSON
)로 변환시켜준다.xstream
, spring-oxm
의존성 추가
controller 추가
@Controller
public class CustomerController {
…
// GET메소드
@GetMapping("/api/v1/customers")// api를 만들 때는 versioning 필요.
@ResponseBody // List<Customer>를 Http 메세지로 변환하기 위해 필요
public List<Customer> findCustomers() {
return customerService.getAllCustomers();
}
}
// JSON형태의 응답을 받는다
@Configuration
…
@EnableTransactionManagement
static class ServletConfig implements WebMvcConfigurer, ApplicationContextAware { // WebMvcConfigurer를 상속했기 때문에 반환받을 타입을 설정 가능
// MessageConverters가 반환하는 형식 설정 가능
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 리퀘스트를 보낼 때 accept하는 컨텐츠 타입을 지정해주어야 한다. 지정하지 않으면 기본적으로 아래 코드가 동작한다.
var messageConverter = new MarshallingHttpMessageConverter();
var xStreamMarshaller = new XStreamMarshaller(); // xml을 말아준다(?)
messageConverter.setMarshaller(xStreamMarshaller);
messageConverter.setUnmarshaller(xStreamMarshaller); // 자바 객체로 인스턴스화
converters.add(messageConverter);
}
}
@Configuration
…
@EnableTransactionManagement
static class ServletConfig implements WebMvcConfigurer, ApplicationContextAware { // WebMvcConfigurer를 상속했기 때문에 반환받을 타입을 설정 가능
// MessageConverters가 반환하는 형식 설정 가능
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
var messageConverter = new MarshallingHttpMessageConverter();
var xStreamMarshaller = new XStreamMarshaller();
messageConverter.setMarshaller(xStreamMarshaller);
messageConverter.setUnmarshaller(xStreamMarshaller); // 자바 객체로 인스턴스화
converters.add(0, messageConverter); // 인덱스를 줘서 맨 앞에 추가
// 메세지 컨버터 추가 : CreateAt을 ISO_DATE_TIME 포맷 으로 바꾸기 위한 코드
var javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ISO_DATE_TIME));
var modules = Jackson2ObjectMapperBuilder.json().modules(javaTimeModule); // 자바 오브젝트(OR class)를 JSON으로 바꿀 때 Jackson2ObjectMapperBuilder 사용
converters.add(1, new MappingJackson2HttpMessageConverter(modules.build()));
}
Http Client 도구를 사용하여 요청을 통해 받을 컨텐츠 타입을 지정해줄 수 있다.
오류해결
intellij origin 서버가 대상 리소스를 위한 현재의 representation을 찾지 못했거나, 그것이 존재하는지를 밝히려 하지 않습니다.
강의와 내 어플리케이션 네임이 달랐다. ^^;
http://localhost:8080/어플리케이션네임/api/v1/customers서블릿 [test]을(를) 위한 Servlet.init() 호출이 예외를 발생시켰습니다.
해결 : pom.xml에 추가한 의존성을 Project structure의 available element에서 직접 추가해주지 않아서Jackson2ObjectMapperBuilder
가 동작하지 않고 createdAt필드가 계속 이상하게 출력됨 -> 해결못함
새로 알게된 용어
- DTO(Data Transfer Ovject) == VO
계층(컨트롤러, 뷰, 비즈니스, 퍼시스턴스 등) 간 데이터 전달에 사용하는 데이터 객체
참고자료 : https://genesis8.tistory.com/214- Attribute
개체가 가지는 속성- Payload
전송의 근본적인 목적이 되는 데이터의 일부분으로 그 데이터와 함께 전송되는 헤더와 메타데이터와 같은 데이터는 제외한다.
출처 :https://ko.wikipedia.org/wiki/%ED%8E%98%EC%9D%B4%EB%A1%9C%EB%93%9C_(%EC%BB%B4%ED%93%A8%ED%8C%85)- Endpoint
- API가 서버의 특정 리소스에 대한 요청을 받는 위치
- 일반적으로 API에서 Endpoint는 API가 서버에서 자원(resource)에 접근할 수 있도록 하는 URL
- SOAP API
- 이 API는 단순 객체 접근 프로토콜을 사용한다.
- 클라이언트와 서버는
XML
을 사용하여 메시지를 교환한다.- 과거에 더 많이 사용되었으며 유연성이 떨어지는 API.
TIP
- IoC 컨테이너의 역할은 초기에 Bean 객체를 생성하고 DI한 후 최초로 애플리케이션을 기동할 Bean을 제공해주는 것까지이다.
더 공부하면 좋을 포스팅
Rf
- 심드류 카네기 님의 [Spring] ContextLoaderListener 란? RootApplicationContext과 WebApplicationContext란?
- bruteforce 님의 Spring Web MVC에서 사용하는 context들
- KoB 님의 [Spring] ApplicationContext와 WebApplicationContext
- ykkkk님의 ServletContext, ApplicationContext, WebApplicationContext
- yshjft 님의 2022년 4월 20일 TIL
- 망나니 개발자님의 [Spring] 애플리케이션 컨텍스트(Application Context)와 스프링의 싱글톤(Singleton)
- AWS 공식 사이트 - API란 무엇인가요?
- [Web] API 그리고 EndPoint
- What Is an API Endpoint? (And Why Are They So Important?)
- taeha7b 님의 API, REST API, RESTful API 개념정리
- DongGeon Lee 님의 REST API Content-Type 설정