개발자입니다
[Java] 예제 소스 정리 - Spring WebMVC 본문
네이버클라우드 AIaaS 개발자 양성과정 1기/Spring Framework, Spring Boot
[Java] 예제 소스 정리 - Spring WebMVC
끈기JK 2023. 3. 3. 09:33eomcs-spring-webmvc
예제 소스 정리
spring webmvc
eomcs-spring-webmvc/src-09
web.xml 이나 애노테이션이 아닌 다른 방법으로 서블릿을 등록하기
package bitcamp;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
// web.xml 이나 애노테이션이 아닌 다른 방법으로 서블릿을 등록하기
// => Servlet API의 표준 기술 활용!
// 서블릿 컨테이너가 시작될 때 보고 받는 객체
// 구동 순서:
// 1) 서블릿 컨테이너를 시작한다.
// 2) 서블릿 컨테이너는 /WEB-INF/lib/*.jar 파일을 뒤진다.
// 3) /META-INF/services/javax.servlet.SerlvetContainerInitializer 파일을 찾는다.
// 4) 그 파일에서 ServletContainerInitializer 구현체를 알아낸다.
// 5) 해당 구현체의 인스턴스를 생성한 후 onStartup()을 호출한다.
// 6) 만약 그 구현체가 보고 받고자 하는 타입이 있다면 해당 클래스를 찾아
// onStartup()을 호출할 때 파라미터로 넘겨준다.
//
// WebApplicationInitializer 호출 과정
// 1) 서블릿 컨테이너(예: 톰캣 서버)를 시작한다.
// 2) spring-web-*.jar 파일에서
// /META-INF/service/javax.servlet.SerlvetContainerInitializer 파일을 읽는다.
// 3) 이 파일에 등록된 클래스의 인스턴스를 생성한다.
// => SpringServletContainerInitializer 인스턴스 생성
// 4) SpringServletContainerInitializer 객체에 대해 onStartup()을 호출한다.
// => 호출할 때 WebApplicationInitializer를 구현한 클래스 목록을 넘겨준다.
// 5) SpringServletContainerInitializer는
// WebApplicationInitializer 구현체의 인스턴스를 만들고,
// onStartup()을 호출한다.
//
public class WebApplicationInitializerImpl implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("WebApplicationInitializerImpl.onStartup()...호출됨!");
// DispatcherServlet 에서 사용할 스프링 IoC 컨테이너를 준비한다.
AnnotationConfigWebApplicationContext iocContainer =
new AnnotationConfigWebApplicationContext();
// => IoC 컨테이너의 설정 정보를 갖고 있는 Java Config 클래스를 직접 지정하기
// iocContainer.register(AppConfig.class);
// => Java Config 클래스가 있는 패키지를 지정하기
// 단 이럴 경우 Java Config 클래스에는 @Configuration 애노테이션이 선언되어 있어야 한다.
iocContainer.scan("bitcamp");
iocContainer.refresh();
// DispatcherServlet 인스턴스를 생성한다.
DispatcherServlet servlet = new DispatcherServlet(iocContainer);
// 웹 애플리케이션에 DispatcherServlet을 등록한다.
Dynamic registration = servletContext.addServlet("app", servlet);
// 웹 애플리케이션에 등록된 DispatcherServlet을 설정한다.
registration.setLoadOnStartup(1);
// DispatcherServlet에 URL 패턴을 지정한다.
registration.addMapping("/app/*");
}
}
package bitcamp;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("bitcamp")
public class AppConfig {
}
eomcs-spring-webmvc/src-10
WebApplicationInitializer 구현체를 통해 DispatcherServlet을 등록하는 두 번째 방법
package bitcamp;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
// WebApplicationInitializer 구현체를 통해 DispatcherServlet을 등록하는 두 번째 방법
// => 인터페이스를 직접 구현하는 대신에 그 인터페이스를 미리 구현한
// AbstractAnnotationConfigDispatcherServletInitializer 클래스를 상속 받기
// - 이 클래스는 미리 AnnotationConfigWebApplicationContext IoC 컨테이너를 준비했다.
// - 따라서 IoC 컨테이너를 따로 설정할 필요가 없다.
// - 또한 DispatcherServlet을 등록하는 코드가 이미 작성되어 있기 때문에
// 따로 등록할 필요가 없다.
// - 즉 인터페이스를 직접 구현하는 것 보다 편하다.
//
public class WebApplicationInitializerImpl
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
System.out.println("==> getRootConfigClasses()");
// ContextLoaderListener의 IoC 컨테이너가 사용할 Java Config 클래스를 리턴한다.
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
System.out.println("==> getServletConfigClasses()");
// DispatcherServlet의 IoC 컨테이너가 사용할 Java config 클래스를 리턴한다.
return new Class<?>[] {AppConfig.class};
}
@Override
protected String[] getServletMappings() {
System.out.println("==> getServletMappings()");
// DispatcherServlet을 등록할 때 이 메서드를 호출한다.
// 이 메서드의 리턴 값이 리턴 값이 URL 패턴으로 사용된다.
return new String[] {"/app/*"};
}
@Override
protected String getServletName() {
System.out.println("==> getServletName()");
// DispatcherServlet을 등록할 때 이 메서드를 호출한다.
// 이 메서드의 리턴 값이 서블릿의 이름으로 사용된다.
// 이 메서드를 오버라이딩 하지 않으면 기본 이름("dispatcher")이 사용된다.
// => 한 개만 등록할 것이라면 오버라이딩 하지 않아도 되지만,
// 여러 개의 DispatcherServlet을 등록할 것이라면 오버라이딩 하여
// 반드시 이름을 다르게 해야 한다.
return "app";
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 이 메서드가 호출될 때 간단한 메시지를 출력하기 위해 오버라이딩 하였다.
// 따라서 원래의 메서드를 반드시 호출해줘야 한다.
System.out.println("WebApplicationInitializerImpl.onStartup()...호출됨2!");
super.onStartup(servletContext);
}
}
WebApplicationInitializer 구현체를 통해 DispatcherServlet을 등록하는 세 번째 방법
package bitcamp;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
// WebApplicationInitializer 구현체를 통해 DispatcherServlet을 등록하는 세 번째 방법
// => 인터페이스를 직접 구현하는 대신에 그 인터페이스를 구현한
// AbstractDispatcherServletInitializer 클래스를 상속 받기
//
//
public class WebApplicationInitializerImpl
extends AbstractDispatcherServletInitializer {
// AbstractDispatcherServletInitializer 클래스에서
// 이미 DispatcherServlet 객체를 생성하여 등록했다.
// 따라서 이 클래스를 상속 받는 서브 클래스에서 해야 할 일은
// 1) ContextLoaderListener 가 사용할 IoC 컨테이너를 설정한다.
// => createRootApplicationContext() 메서드 오버라이딩
// 2) DispatcherServlet이 사용할 IoC 컨테이너를 설정한다.
// => createServletApplicationContext() 메서드 오버라이딩
// 3) DispatcherServlet에 적용할 URL 매핑을 설정한다.
// => getServletMappings() 메서드 오버라이딩
// 다음 메서드들은 수퍼 클래스에서 상속 받은 onStartup()에서 호출한다.
// 즉 onStartup()은 DispatcherServlet 을 준비할 때 다음 메서드의 리턴 값을 참조한다.
@Override
protected WebApplicationContext createRootApplicationContext() {
// ContextLoaderListener 가 사용할 IoC 컨테이너를 리턴한다.
// 만약 null을 리턴한다면,
// 스프링 WebMVC 프레임워크는 ContextLoaderListener를 생성하지 않을 것이다.
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
// DispatcherServlet이 사용할 IoC 컨테이너를 리턴한다.
// 스프링 WebMVC 프레임워크는 DispatcherServlet을 만들 때
// 이 메서드가 리턴한 IoC 컨테이너를 DispatcherServlet에 주입할 것이다.
//1) XML 기반 IoC 컨테이너를 사용할 경우,
// XmlWebApplicationContext iocContainer = new XmlWebApplicationContext();
// iocContainer.setConfigLocation("/WEB-INF/app-servlet.xml");
//2) Java Config 기반 IoC 컨테이너를 사용할 경우,
AnnotationConfigWebApplicationContext iocContainer =
new AnnotationConfigWebApplicationContext();
iocContainer.register(AppConfig.class);
return iocContainer;
}
@Override
protected String[] getServletMappings() {
return new String[] {"/app/*"};
}
@Override
protected String getServletName() {
return "app";
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 이 메서드가 호출될 때 간단한 메시지를 출력하기 위해 오버라이딩 하였다.
// 따라서 원래의 메서드를 반드시 호출해줘야 한다.
System.out.println("WebApplicationInitializerImpl.onStartup()...호출됨!");
super.onStartup(servletContext);
}
}
eomcs-spring-webmvc/src-12
package bitcamp.web;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
public class AdminDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
// ContextLoaderListener 가 사용할 IoC 컨테이너를 리턴한다.
// AppDispatcherServletInitializer가 이미 리턴했기 때문에
// 이 객체는 그냥 null을 리턴한다.
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
// DispatcherServlet이 사용할 IoC 컨테이너를 리턴한다.
XmlWebApplicationContext iocContainer = new XmlWebApplicationContext();
iocContainer.setConfigLocation("/WEB-INF/admin-servlet.xml");
return iocContainer;
}
@Override
protected String[] getServletMappings() {
return new String[] {"/admin/*"};
}
@Override
protected String getServletName() {
return "admin";
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 프론트 컨트롤러가 사용할 웹 관련 객체가 들어 있는 패키지를 지정한다. -->
<context:component-scan base-package="bitcamp.web">
<context:exclude-filter type="regex" expression="bitcamp.web.app.*"/>
</context:component-scan>
</beans>
package bitcamp.web;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
public class AppDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
// ContextLoaderListener 가 사용할 IoC 컨테이너를 리턴한다.
XmlWebApplicationContext iocContainer = new XmlWebApplicationContext();
iocContainer.setConfigLocation("/WEB-INF/config/app-context.xml");
return iocContainer;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
// DispatcherServlet이 사용할 IoC 컨테이너를 리턴한다.
XmlWebApplicationContext iocContainer = new XmlWebApplicationContext();
iocContainer.setConfigLocation("/WEB-INF/app-servlet.xml");
return iocContainer;
}
@Override
protected String[] getServletMappings() {
return new String[] {"/app/*"};
}
@Override
protected String getServletName() {
return "app";
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 프론트 컨트롤러가 사용할 웹 관련 객체가 들어 있는 패키지를 지정한다. -->
<context:component-scan base-package="bitcamp.web">
<context:exclude-filter type="regex" expression="bitcamp.web.admin.*"/>
</context:component-scan>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- ContextLoaderListener의 IoC 컨테이너가 관리할 객체가 들어 있는 패키지를 지정한다.
=> 보통 Service, DAO, 트랜잭션 관련 객체 등을 관리한다.
-->
<context:component-scan base-package="bitcamp">
<context:exclude-filter type="regex" expression="bitcamp.web.*"/>
</context:component-scan>
</beans>
package bitcamp.web.admin;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import bitcamp.dao.BoardDao;
import bitcamp.vo.Board;
// 페이지 컨트롤러는 보통 DispatcherServlet의 IoC 컨테이너에서 관리한다.
// 주로 다음 애노테이션으로 표시한다.
@Controller
public class BoardController {
@Autowired
BoardDao boardDao; // 이 객체는 ContextLoaderListener에서 관리하는 객체이다.
@RequestMapping(path = "/board/list", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String list() throws Exception {
List<Board> boards = boardDao.findAll();
StringBuffer sb = new StringBuffer();
for (Board board : boards) {
sb.append(board.toString() + "\n");
}
return sb.toString();
}
}
package bitcamp.vo;
public class Board {
private int no;
private String contents;
public Board() {
}
public Board(int no, String contents) {
this.no = no;
this.contents = contents;
}
@Override
public String toString() {
return "Board [no=" + no + ", contents=" + contents + "]";
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getContents() {
return contents;
}
public void setContents(String contents) {
this.contents = contents;
}
}
package bitcamp.dao;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Repository;
import bitcamp.vo.Board;
// 데이터의 입출력을 다루는 객체(퍼시스턴스 객체; persistence)인 경우 다음 애노테이션을 붙인다.
@Repository
public class BoardDao {
ArrayList<Board> boards = new ArrayList<>();
public BoardDao() {
boards.add(new Board(1, "게시물입니다.111"));
boards.add(new Board(2, "게시물입니다.222"));
boards.add(new Board(3, "게시물입니다.333"));
boards.add(new Board(4, "게시물입니다.444"));
boards.add(new Board(5, "게시물입니다.555"));
}
public List<Board> findAll() {
return boards;
}
public void insert(Board board) {
boards.add(board);
}
public Board findBy(int no) {
for (Board board : boards) {
if (board.getNo() == no)
return board;
}
return null;
}
}
eomcs-spring-webmvc/src-13
package bitcamp.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class AppDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
// ContextLoaderListener 의 IoC 컨테이너가 사용할 Java Config 클래스를 리턴한다.
return new Class<?>[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
// DispatcherServlet 의 IoC 컨테이너가 사용할 Java Config 클래스를 리턴한다.
return new Class<?>[] {AppConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/app/*"};
}
@Override
protected String getServletName() {
return "app";
}
}
package bitcamp.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
@ComponentScan(
value="bitcamp",
excludeFilters = {
@Filter(type = FilterType.REGEX, pattern = "bitcamp.web.*")
})
public class RootConfig {
}
package bitcamp.config;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("bitcamp.web.app")
public class AppConfig {
}
package bitcamp.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class AdminDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
// ContextLoaderListener 의 IoC 컨테이너가 사용할 Java Config 클래스를 리턴한다.
// AppDispatcherServletInitializer 에서 이미 설정했기 때문에
// 여기서는 null을 리턴한다.
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
// DispatcherServlet 의 IoC 컨테이너가 사용할 Java Config 클래스를 리턴한다.
return new Class<?>[] {AdminConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/admin/*"};
}
@Override
protected String getServletName() {
return "admin";
}
}
package bitcamp.config;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("bitcamp.web.admin")
public class AdminConfig {
}
eomcs-spring-webmvc/src-14/~/app1
package bitcamp.config;
import java.io.File;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class App1WebApplicationInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
String uploadTmpDir;
public App1WebApplicationInitializer() {
uploadTmpDir = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
System.out.println("업로드 임시 폴더: " + uploadTmpDir);
}
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {App1Config.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/app1/*"};
}
@Override
protected String getServletName() {
return "app1";
}
@Override
protected void customizeRegistration(
javax.servlet.ServletRegistration.Dynamic registration) {
// Servlet 3.0 API의 파일 업로드를 사용하려면 다음과 같이
// DispatcherServlet에 설정을 추가해야 한다.
// => 즉 멀티파트 데이터를 Part 객체로 받을 때는
// 이 설정을 추가해야 한다.
//
// 만약 Spring의 방식으로 파일 업로드를 처리하고 싶다면,
// AppConfig.java의 MultipartResolver를 추가해야 한다.
// => 즉 멀티파트 데이터를 MultipartFile 객체로 받으려면
// AppConfig에 별도로 MultipartResolver를 추가해야 한다.
// => 단 이 설정을 추가하면 Servlet 3.0 API의 멀티파트 처리 기능이 동작되지 않는다.
//
// DispatcherServlet 이 multipart/form-data 으로 전송된 데이터를 처리하려면
// 해당 콤포넌트를 등록해야 한다.
// registration.setMultipartConfig(
// new MultipartConfigElement(
// uploadTmpDir, // 업로드 한 파일을 임시 보관할 위치
// 10000000, // 최대 업로드할 수 있는 파일들의 총 크기
// 15000000, // 요청 전체 데이터의 크기
// 2000000 // 업로드 되고 있는 파일을 메모리에 임시 임시 보관하는 크기
// ));
}
}
package bitcamp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
@ComponentScan("bitcamp.app1")
public class App1Config {
@Bean
public MultipartResolver multipartResolver() {
// 스프링 WebMVC에서 파일 업로드를 처리하고 싶다면,
// 이 메서드를 통해 MultipartResolver 구현체를 등록해야 한다.
// 그래야 request handler는 MultipartFile 객체를 받을 수 있다.
//
CommonsMultipartResolver mr = new CommonsMultipartResolver();
mr.setMaxUploadSize(10000000);
mr.setMaxInMemorySize(2000000);
mr.setMaxUploadSizePerFile(5000000);
return mr;
}
}
페이지 컨트롤러 만드는 방법
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller // 이 애노테이션을 붙인다.
@RequestMapping("/c01_1") // 컨트롤러에 URL을 매핑한다.
public class Controller01_1 {
//@RequestMapping // 이 애노테이션을 붙여서 요청이 들어왔을 때 호출될 메서드임을 표시한다.
@ResponseBody // 메서드의 리턴 값이 클라이언트에게 출력할 내용임을 표시한다.
public String handler() {
return "c01_1 -> handler()";
}
// URL 한 개 당 한 개의 핸들러만 연결할 수 있다.
// 같은 URL에 대해 다른 메서드를 또 정의하면 실행 오류가 발생한다.
@RequestMapping
@ResponseBody
public String handler2() {
return "c01_1 -> handler2()";
}
}
페이지 컨트롤러 만드는 방법 - 한 개의 페이지 컨트롤러에 여러 개의 요청 핸들러 두기
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class Controller01_2 {
// 요청이 들어 왔을 때 호출되는 메서드를 "request handler" 라 부른다.
@RequestMapping("/c01_2_h1") // 핸들러에서 URL을 지정한다.
@ResponseBody
public String handler() {
return "c01_2_h1";
}
@RequestMapping("/c01_2_h2") // 핸들러에서 URL을 지정한다.
@ResponseBody
public String handler2() {
return "c01_2_h2";
}
@RequestMapping("/c01_2/h3") // URL을 지정할 때 디렉토리 형식으로 지정할 수 있다.
@ResponseBody
public String handler3() {
return "/c01_2/h3";
}
@RequestMapping("/c01_2/h4") // URL을 지정할 때 디렉토리 형식으로 지정할 수 있다.
@ResponseBody
public String handler4() {
return "/c01_2/h4";
}
// 한 개의 request handler에 여러 개의 URL을 매핑할 수 있다.
@RequestMapping({"/c01_2/h5", "/c01_2/h6", "/c01_2/h7"})
@ResponseBody
public String handler5() {
return "/c01_2/h5,h6,h7";
}
}
페이지 컨트롤러 만드는 방법 - 기본 URL과 상세 URL을 분리하여 설정하기
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c01_3") // 핸들러에 적용될 기본 URL을 지정한다.
public class Controller01_3 {
@RequestMapping("h1") // 기본 URL에 뒤에 붙는 상세 URL. 예) /c01_3/h1
@ResponseBody
public String handler() {
return "h1";
}
@RequestMapping("/h2") // 앞에 /를 붙여도 되고 생략해도 된다. 예) /c01_3/h2
@ResponseBody
public String handler2() {
return "h2";
}
@RequestMapping("h3")
@ResponseBody
public String handler3() {
return "h3";
}
@RequestMapping("h4")
@ResponseBody
public String handler4() {
return "h4";
}
@RequestMapping({"h5", "h6", "h7"})
@ResponseBody
public String handler5() {
return "h5,h6,h7";
}
}
GET, POST 구분하기
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c02_1")
public class Controller02_1 {
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/html/app1/c02_1.html
@RequestMapping(method = RequestMethod.GET) // GET 요청일 때만 호출된다.
@ResponseBody
public String handler1() {
return "get";
}
@RequestMapping(method = RequestMethod.POST) // POST 요청일 때만 호출된다.
@ResponseBody
public String handler2() {
return "post";
}
}
GET, POST 구분하기 II
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c02_2")
public class Controller02_2 {
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/html/app1/c02_2.html
@GetMapping // GET 요청일 때만 호출된다.
@ResponseBody
public String handler1() {
return "get";
}
@PostMapping // POST 요청일 때만 호출된다.
@ResponseBody
public String handler2() {
return "post";
}
}
request handler를 구분하는 방법 - 파라미터 이름으로 구분하기
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c03_1")
public class Controller03_1 {
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1?name=kim
@GetMapping(params = "name")
@ResponseBody
public String handler1() {
return "handler1";
}
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1?age=20
@GetMapping(params = "age")
@ResponseBody
public String handler2() {
return "handler2";
}
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1?name=kim&age=20
@GetMapping(params = {"age", "name"})
@ResponseBody
public String handler3() {
return "handler3";
}
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/app1/c03_1
@GetMapping
@ResponseBody
public String handler4() {
return "handler4";
}
}
request handler를 구분하는 방법 - 요청 헤더 이름으로 구분하기
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c03_2")
public class Controller03_2 {
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/html/app1/c03_2.html
// => 요청 헤더 중에서 특정 이름을 갖는 헤더가 있을 때 호출될 메서드를 지정할 수 있다.
// => 웹 페이지에서 링크를 클릭하거나 입력 폼에 값을 넣고 등록 버튼을 누르는
// 일반적인 상황에서는 요청헤더에 임의의 헤더를 추가할 수 없다.
// => 자바스크립트 등의 프로그래밍으로 임의의 HTTP 요청을 할 때
// HTTP 프로토콜에 표준이 아닌 헤더를 추가할 수 있다.
// 그 헤더를 처리하는 메서드를 정의할 때 사용한다.
// => 보통 Open API를 개발하는 서비스 회사에서 많이 사용한다.
// @GetMapping(headers="name")
@RequestMapping(method = RequestMethod.GET, headers = "name")
@ResponseBody
public String handler1() {
return "handler1";
}
// @GetMapping(headers="age")
@RequestMapping(method = RequestMethod.GET, headers = "age")
@ResponseBody
public String handler2() {
return "handler2";
}
@GetMapping(headers = {"age", "name"})
@ResponseBody
public String handler3() {
return "handler3";
}
@GetMapping
@ResponseBody
public String handler4() {
return "handler4";
}
}
request handler를 구분하는 방법 - Accept 요청 헤더의 값에 따라 구분하기
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c03_3")
public class Controller03_3 {
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/html/app1/c03_3.html
// => 요청 헤더 중에서 Accept의 값에 따라 구분할 때 사용한다.
//
// Accept 헤더?
// => HTTP 클라이언트(웹 브라우저)에서 서버에 요청할 때
// 받고자 하는 콘텐트의 타입을 알려준다.
@GetMapping(produces = "text/plain")
@ResponseBody
public String handler1() {
return "handler1";
}
@GetMapping(produces = "text/html")
@ResponseBody
public String handler2() {
return "handler2";
}
@GetMapping(produces = "application/json")
@ResponseBody
public String handler3() {
return "handler3";
}
@GetMapping
@ResponseBody
public String handler4() {
return "handler4";
}
}
request handler를 구분하는 방법 - Content-Type 헤더의 값에 따라 구분하기
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c03_4")
public class Controller03_4 {
// Content-Type 요청 헤더
// => HTTP 클라이언트가 보내는 데이터의 콘텐트 타입이다.
// => 프론트 컨트롤러는 보내는 데이터의 타입에 따라 처리를 구분할 수 있다.
// 테스트 방법:
// => http://localhost:9999/eomcs-spring-webmvc/html/app1/c03_4.html
// => 클라이언트가 POST 요청으로 데이터를 보낼 때 기본 형식은 다음과 같다.
// application/x-www-form-urlencoded
// => <form> 태그에서 enctype 속성에 "mulpart/form-data"를 지정하면
// 해당 형식으로 서버에 값을 보낸다.
// => 자바스크립트를 사용하여 개발자가 임의의 형식으로 값을 보낼 수 있다.
//
// 클라이언트가 POST로 요청할 때 보내는 데이터의 유형에 따라 호출될 메서드를 구분할 때 사용한다.
// 다음 메서드는 application/x-www-form-urlencoded 형식의 데이터를 소비한다.
// => 즉 클라이언트의 HTTP 요청에서 Content-Type 헤더의 값이 위와 같을 때
// 이 메서드를 호출하라는 의미다.
@PostMapping(consumes = "application/x-www-form-urlencoded")
@ResponseBody
public String handler1() {
return "handler1";
}
// 다음 메서드는 multipart/form-data 형식의 데이터를 소비한다.
@PostMapping(consumes = "multipart/form-data")
@ResponseBody
public String handler2() {
return "handler2";
}
// 다음 메서드는 text/csv 형식의 데이터를 소비한다.
@PostMapping(consumes = "text/csv")
@ResponseBody
public String handler3() {
return "handler3";
}
// 다음 메서드는 application/json 형식의 데이터를 소비한다.
@PostMapping(consumes = "application/json")
@ResponseBody
public String handler4() {
return "handler4";
}
// 다음 메서드는 Content-Type 헤더가 없을 때 호출된다.
@RequestMapping
@ResponseBody
public String handler5() {
return "handler5";
}
}
요청 핸들러의 아규먼트 - 프론트 컨트롤러로부터 받을 수 있는 파라미터 값
package bitcamp.app1;
import java.io.PrintWriter;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_1")
public class Controller04_1 {
// 프론트 컨트롤러(DispatcherServlet)로부터 받고 싶은 값이 있다면
// 요청 핸들러를 정의할 때 받고 싶은 타입의 파라미터를 선언하라!
// 그러면 프론트 컨트롤러가 메서드를 호출할 때 해당 타입의 값을 넘겨줄 것이다.
// ServletContext는 의존 객체로 주입 받아야 한다.
// 요청 핸들러에서 파라미터로 받을 수 없다.
@Autowired
ServletContext sc;
// 요청 핸들러에서 받을 수 있는 타입의 아규먼트를 선언해 보자!
@GetMapping("h1")
@ResponseBody
public void handler1(
// ServletContext sc,
// ServletContext는 파라미터로 받을 수 없다. 예외 발생!
// 의존 객체로 주입 받아야 한다.
ServletRequest request,
ServletResponse response,
HttpServletRequest request2,
HttpServletResponse response2,
HttpSession session,
Map<String, Object> map, // JSP에 전달할 값을 담는 임시 보관소
Model model, // Map과 같다. 둘 중 한 개만 받으면 된다.
PrintWriter out // 클라이언트에게 콘텐트를 보낼 때 사용할 출력 스트림
) {
out.printf("ServletContext: %b\n", sc != null);
out.printf("ServletRequest: %b\n", request != null);
out.printf("ServletResponse: %b\n", response != null);
out.printf("HttpServletRequest: %b\n", request2 != null);
out.printf("HttpServletResponse: %b\n", response2 != null);
out.printf("HttpSession: %b\n", session != null);
out.printf("Map: %b\n", map != null);
out.printf("Model: %b\n", model != null);
out.printf("ServletRequest == HttpServletRequest : %b\n", request == request2);
out.printf("ServletResponse == HttpServletResponse : %b\n", response == response2);
}
}
요청 핸들러의 아규먼트 - @RequestParam
package bitcamp.app1;
import java.io.PrintWriter;
import javax.servlet.ServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_2")
public class Controller04_2 {
// 클라이언트가 보낸 파라미터 값을 바로 받을 수 있다.
// => 요청 핸들러의 파라미터로 선언하면 된다.
// 단 파라미터 앞에 @RequestParam 애노테이션을 붙인다.
// 그리고 클라이언트가 보낸 파라미터 이름을 지정한다.
// 테스트:
// => http://localhost:9999/eomcs-spring-webmvc/app1/c04_2/h1?name=kim
@GetMapping("h1")
@ResponseBody
public void handler1(
PrintWriter out,
ServletRequest request,
@RequestParam(value = "name") String name1,
@RequestParam(name = "name") String name2, // value와 name은 같은 일을 한다.
@RequestParam("name") String name3, // value 이름을 생략할 수 있다.
/* @RequestParam("name") */ String name // 요청 파라미터 이름과 메서드 파라미터(아규먼트)의 이름이 같다면
// 애노테이션을 생략해도 된다.
) {
out.printf("name=%s\n", request.getParameter("name"));
out.printf("name=%s\n", name1);
out.printf("name=%s\n", name2);
out.printf("name=%s\n", name3);
out.printf("name=%s\n", name);
}
// 테스트:
// http://.../app1/c04_2/h2?name1=kim&name2=park
@GetMapping("h2")
@ResponseBody
public void handler2(
PrintWriter out,
@RequestParam("name1") String name1, // 애노테이션을 붙이면 필수 항목으로 간주한다.
// 따라서 파라미터 값이 없으면 예외가 발생한다.
String name2, // 애노테이션을 붙이지 않으면 선택 항목으로 간주한다.
// 따라서 파라미터 값이 없으면 null을 받는다.
@RequestParam(value = "name3", required = false) String name3,
// required 프로퍼티를 false로 설정하면 선택 항목으로 간주한다.
@RequestParam(value = "name4", defaultValue = "ohora") String name4
// 기본 값을 지정하면 파라미터 값이 없어도 된다.
) {
out.printf("name1=%s\n", name1);
out.printf("name2=%s\n", name2);
out.printf("name3=%s\n", name3);
out.printf("name4=%s\n", name4);
}
}
요청 핸들러의 아규먼트 - 도메인 객체(값 객체; Value Object)로 요청 파라미터 값 받기
package bitcamp.app1;
import java.io.PrintWriter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_3")
public class Controller04_3 {
// 클라이언트가 보낸 요청 파라미터 값을 값 객체에 받을 수 있다.
// => 요청 핸들러의 아규먼트가 값 객체라면,
// 프론트 컨트롤러는 메서드를 호출할 때 값 객체의 인스턴스를 생성한 후
// 요청 파라미터와 일치하는 프로퍼티에 대해 값을 저장한다.
// 그리고 호출할 때 넘겨준다.
//
// 테스트:
// => http://.../c04_3/h1?model=sonata&maker=hyundai&capacity=5&auto=true&engine.model=ok&engine.cc=1980&engine.valve=16
@GetMapping("h1")
@ResponseBody
public void handler1(
PrintWriter out,
String model,
String maker,
@RequestParam(defaultValue = "100") int capacity, // 프론트 컨트롤러가 String 값을 int로 변환해 준다.
// 단 변환할 수 없을 경우 예외가 발생한다.
boolean auto,
// 프론트 컨트롤러가 String 값을 boolean으로 변환해 준다.
// 단 변환할 수 없을 경우 예외가 발생한다.
// "true", "false"는 대소문자 구분없이 true, false로 변환해 준다.
// 1 ==> true, 0 ==> false 로 변환해 준다. 그 외 숫자는 예외 발생!
Car car
// 아규먼트가 값 객체이면 요청 파라미터 중에서 값 객체의 프로퍼티 이름과 일치하는
// 항목에 대해 값을 넣어준다.
// 값 객체 안에 또 값 객체가 있을 때는 OGNL 방식으로 요청 파라미터 값을
// 지정하면 된다.
// 예) ...&engine.model=ok&engine.cc=1980&engine.valve=16
) {
out.printf("model=%s\n", model);
out.printf("maker=%s\n", maker);
out.printf("capacity=%s\n", capacity);
out.printf("auto=%s\n", auto);
out.printf("car=%s\n", car);
}
}
요청 핸들러의 아규먼트 - 프로퍼티 에디터 사용하기
package bitcamp.app1;
import java.beans.PropertyEditorSupport;
import java.io.PrintWriter;
import java.util.Date;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_4")
public class Controller04_4 {
// 클라이언트가 보낸 요청 파라미터 값(String 타입)을
// request handler의 아규먼트 타입(String, int, boolean 등)의 값으로 바꿀 때
// primitive type에 대해서만 자동으로 변환해 준다.
// 그 외의 타입에 대해서는 프로퍼티 에디터(타입 변환기)가 없으면 예외를 발생시킨다.
// 테스트:
// http://.../c04_4/h1?model=sonata&capacity=5&auto=true&createdDate=2019-4-19
@GetMapping("h1")
@ResponseBody
public void handler1(
PrintWriter out,
String model,
@RequestParam(defaultValue = "5") int capacity, // String ===> int : Integer.parseInt(String)
boolean auto, // String ===> boolean : Boolean.parseBoolean(String)
Date createdDate // 프로퍼티 에디터를 설정하지 않으면 변환 오류 발생
) {
out.printf("model=%s\n", model);
out.printf("capacity=%s\n", capacity);
out.printf("auto=%s\n", auto);
out.printf("createdDate=%s\n", createdDate);
}
// 테스트:
// http://.../c04_4/h2?car=sonata,5,true,2019-4-19
@GetMapping("h2")
@ResponseBody
public void handler2(PrintWriter out,
// 콤마(,)로 구분된 문자열을 Car 객체로 변환하기?
// => String ===> Car 프로퍼티 에디터를 등록하면 된다.
@RequestParam("car") Car car) {
out.println(car);
}
// 테스트:
// http://.../c04_4/h3?engine=bitengine,3500,16
@GetMapping("h3")
@ResponseBody
public void handler3(PrintWriter out,
// 콤마(,)로 구분된 문자열을 Engine 객체로 변환하기?
// => String ===> Engine 프로퍼티 에디터를 등록하면 된다.
@RequestParam("engine") Engine engine) {
out.println(engine);
}
// 이 페이지 컨트롤러에서 사용할 프로퍼티 에디터 설정하는 방법
// => 프론트 컨트롤러는 request handler를 호출하기 전에
// 그 메서드가 원하는 아규먼트 값을 준비해야 한다.
// 각 아규먼트 값을 준비할 때
// @InitBinder가 표시된 메서드(request handler를 실행할 때 사용할 도구를 준비하는 메서드)
// 를 호출하여 프로퍼티 에디터(변환기)를 준비시킨다.
// 그리고 이 준비된 값 변환기(프로퍼티 에디터)를 이용하여 파라미터 값을
// request handler의 아규먼트가 원하는 타입의 값을 바꾼다.
// request handler의 아규먼트 개수 만큼 이 메서드를 호출한다.
// => 따라서 프로퍼티 에디터를 적용하기에
// @InitBinder가 표시된 메서드가 적절한 지점이다.
// 즉 이 메서드에 프로퍼티 에디터를 등록하는 코드를 둔다.
//
@InitBinder
// => 메서드 이름은 마음대로.
// => 작업하는데 필요한 값이 있다면 파라미터로 선언하라.
public void initBinder(WebDataBinder binder) {
System.out.println("Controller04_4.initBinder()...");
// 프로퍼티 에디터를 등록하려면 그 일을 수행할 객체(WebDataBinder)가 필요하다.
// request handler 처럼 아규먼트를 선언하여
// 프론트 컨트롤러에게 달라고 요청하라.
//String ===> java.util.Date 프로퍼티 에디터 준비
DatePropertyEditor propEditor = new DatePropertyEditor();
// WebDataBinder에 프로퍼티 에디터 등록하기
binder.registerCustomEditor(
java.util.Date.class, // String을 Date 타입으로 바꾸는 에디터임을 지정한다.
propEditor // 바꿔주는 일을 하는 프로퍼티 에디터를 등록한다.
);
// WebDataBinder에 프로퍼티 에디터 등록하기
binder.registerCustomEditor(
Car.class, // String을 Car 타입으로 바꾸는 에디터임을 지정한다.
new CarPropertyEditor() // 바꿔주는 일을 하는 프로퍼티 에디터를 등록한다.
);
// WebDataBinder에 프로퍼티 에디터 등록하기
binder.registerCustomEditor(Engine.class, // String을 Engine 타입으로 바꾸는 에디터임을 지정한다.
new EnginePropertyEditor() // 바꿔주는 일을 하는 프로퍼티 에디터를 등록한다.
);
}
// PropertyEditor 만들기
// => 문자열을 특정 타입의 프로퍼터의 값으로 변환시킬 때 사용하는 에디터이다.
// => java.beans.PropertyEditor 인터페이스를 구현해야 한다.
// => PropertyEditor를 직접 구현하면 너무 많은 메서드를 오버라이딩 해야 하기 때문에
// 자바에서는 도우미 클래스인 PropertyEditorSupport 클래스를 제공한다.
// 이 클래스는 PropertyEditor를 미리 구현하였다.
// 따라서 이 클래스를 상속 받은 것 더 낫다.
class DatePropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
System.out.println("DatePropertyEditor.setAsText()");
// 프로퍼티 에디터를 사용하는 측(예: 프론트 컨트롤러)에서
// 문자열을 Date 객체로 바꾸기 위해 이 메서드를 호출할 것이다.
// 그러면 이 메서드에서 문자열을 프로퍼티가 원하는 타입으로 변환한 후 저장하면 된다.
try {
// 1) String ==> java.util.Date
// SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
// Date date = format.parse(text); // String ===> java.util.Date
// setValue(date); // 내부에 저장
// 2) String ==> java.sql.Date
setValue(java.sql.Date.valueOf(text));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
@Override
public Object getValue() {
System.out.println("DatePropertyEditor.getValue()");
// 이 메서드는 프로퍼티 에디터를 사용하는 측(예: 프론트 컨트롤러)에서
// 변환된 값을 꺼낼 때 호출된다.
// 이 메서드를 오버라이딩 하는 이유는 이 메서드가 호출된 것을
// 확인하기 위함이다. 원래는 오버라이딩 해야 할 이유가 없다.
return super.getValue();
}
}
// String ===> Car 프로퍼티 에디터 만들기
class CarPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] values = text.split(",");
Car car = new Car();
car.setModel(values[0]);
car.setCapacity(Integer.parseInt(values[1]));
car.setAuto(Boolean.parseBoolean(values[2]));
car.setCreatedDate(java.sql.Date.valueOf(values[3]));
setValue(car);
}
}
class EnginePropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] values = text.split(",");
Engine engine = new Engine();
engine.setModel(values[0]);
engine.setCc(Integer.parseInt(values[1]));
engine.setValve(Integer.parseInt(values[2]));
setValue(engine);
}
}
}
요청 핸들러의 아규먼트 - 글로벌 프로퍼티 에디터 적용하기
package bitcamp.app1;
import java.io.PrintWriter;
import java.util.Date;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_5")
public class Controller04_5 {
// 다른 페이지 컨트롤러에서 등록한 프로퍼티 에디터는 사용할 수 없다.
// 각 페이지 컨트롤러 마다 자신이 사용할 프로퍼티 에디터를 등록해야 한다.
// 따라서 만약 여러 페이지 컨트롤러에서 공통으로 사용하는 프로퍼티 에디터라면
// 글로벌 프로퍼티 에디터로 등록하는 것이 편하다.
// 글로벌 프로퍼티 에디터 등록하기
// @ControllerAdvice 애노테이션이 붙은 클래스에서
// @InitBinder 메서드를 정의하면 된다.
// => GlobalControllerAdvice 클래스를 참고하라!
// 테스트:
// http://.../c04_5/h1?model=sonata&capacity=5&auto=true&createdDate=2019-4-19
@GetMapping("h1")
@ResponseBody
public void handler1(
PrintWriter out,
String model,
int capacity,
boolean auto,
Date createdDate
) {
out.printf("model=%s\n", model);
out.printf("capacity=%s\n", capacity);
out.printf("auto=%s\n", auto);
out.printf("createdDate=%s\n", createdDate);
}
//테스트:
// http://.../c04_5/h2?car=sonata,5,true,2019-4-19
@GetMapping("h2")
@ResponseBody
public void handler2(
PrintWriter out,
@RequestParam("car") Car car) {
out.println(car);
}
//테스트:
// http://.../c04_5/h3?engine=bitengine,3500,16
@GetMapping("h3")
@ResponseBody
public void handler3(
PrintWriter out,
@RequestParam("engine") Engine engine) {
out.println(engine);
}
}
package bitcamp.app1;
import java.beans.PropertyEditorSupport;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
// @ControllerAdvice
// => 이름에 이미 역할에 대한 정보가 담겨있다.
// => 페이지 컨트롤러를 실행할 때 충고하는 역할을 수행한다.
// 즉 프론트 컨트롤러가 페이지 컨트롤러의 request handler를 호출하기 전에
// 이 애노테이션이 붙은 클래스를 참고하여 적절한 메서드를 호출한다.
//
@ControllerAdvice
public class GlobalControllerAdvice {
// 이 클래스에 프로퍼티 에디터를 등록하는 @InitBinder 메서드를 정의한다.
@InitBinder
public void initBinder(WebDataBinder binder) {
DatePropertyEditor propEditor = new DatePropertyEditor();
binder.registerCustomEditor(java.util.Date.class, propEditor);
binder.registerCustomEditor(Car.class, new CarPropertyEditor());
binder.registerCustomEditor(Engine.class, new EnginePropertyEditor());
}
class DatePropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
// String ==> java.util.Date
// SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
// Date date = format.parse(text);
// setValue(date);
// String ==> java.sql.Date
setValue(java.sql.Date.valueOf(text));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
class CarPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] values = text.split(",");
Car car = new Car();
car.setModel(values[0]);
car.setCapacity(Integer.parseInt(values[1]));
car.setAuto(Boolean.parseBoolean(values[2]));
car.setCreatedDate(java.sql.Date.valueOf(values[3]));
setValue(car);
}
}
class EnginePropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
String[] values = text.split(",");
Engine engine = new Engine();
engine.setModel(values[0]);
engine.setCc(Integer.parseInt(values[1]));
engine.setValve(Integer.parseInt(values[1]));
setValue(engine);
}
}
}
요청 핸들러의 아규먼트 - @RequestHeader
package bitcamp.app1;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_6")
public class Controller04_6 {
// 클라이언트의 HTTP 요청 헤더를 받고 싶으면
// request handler의 아규먼트 앞에 @RequestHeader(헤더명) 애노테이션을 붙여라!
// 테스트:
// http://.../c04_6/h1
@GetMapping("h1")
@ResponseBody
public void handler1(
PrintWriter out,
@RequestHeader("Accept") String accept,
@RequestHeader("User-Agent") String userAgent) {
out.printf("Accept=%s\n", accept);
out.printf("User-Agent=%s\n", userAgent);
if (userAgent.matches(".*Edg.*")) {
out.println("Edge");
} else if (userAgent.matches(".*Chrome.*")) {
out.println("chrome");
} else if (userAgent.matches(".*Safari.*")) {
out.println("safari");
} else if (userAgent.matches(".*Firefox.*")) {
out.println("firefox");
} else {
out.println("etc");
}
}
public static void main(String[] args) {
String str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36";
// String str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML,
// like Gecko) Version/12.1 Safari/605.1.15";
// String str = "AA BB Aa Ba $12,000";
// 정규 표현식으로 패턴을 정의한다.
// String regex = "Chrome";
// String regex = "Chrome.*Safari";
String regex = "[^(Chrome.*)]Safari";
Pattern pattern = Pattern.compile(regex);
// 주어진 문자열에서 패턴과 일치하는 정보를 찾는다.
Matcher matcher = pattern.matcher(str);
// 일치 여부를 확인한다.
if (matcher.find()) {
System.out.println("OK!");
// for (int i = 1; i < matcher.groupCount(); i++) {
// System.out.println(matcher.group(1));
// }
} else {
System.out.println("NO!");
}
}
}
요청 핸들러의 아규먼트 - @Cookie
package bitcamp.app1;
import java.io.PrintWriter;
import java.net.URLEncoder;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_7")
public class Controller04_7 {
// 클라이언트가 보낸 쿠키 꺼내기
// => @CookieValue(쿠키명) 애노테이션을 request handler의 아규먼트 앞에 붙인다.
// 테스트:
// http://.../c04_7/h1
@GetMapping("h1")
@ResponseBody
public void handler1(
PrintWriter out,
HttpServletResponse response
) {
// 이 메서드에서 쿠키를 클라이언트로 보낸다.
try {
// 쿠키의 값이 ASCII가 아니라면 URL 인코딩 해야만 데이터가 깨지지 않는다.
// URL 인코딩을 하지 않으면 ? 문자로 변환된다.
response.addCookie(new Cookie("name1", "AB가각"));
response.addCookie(new Cookie("name2", URLEncoder.encode("AB가각", "UTF-8")));
response.addCookie(new Cookie("name3", "HongKildong"));
response.addCookie(new Cookie("age", "30"));
} catch (Exception e) {
e.printStackTrace();
}
out.println("send cookie!");
}
// 테스트:
// http://.../c04_7/h2
@GetMapping(value = "h2", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String handler2(
@CookieValue(value = "name1", required = false) String name1,
@CookieValue(value = "name2", defaultValue = "") String name2,
@CookieValue(value = "name3", defaultValue = "홍길동") String name3,
@CookieValue(value = "age", defaultValue = "0") int age // String ===> int 자동 변환
) throws Exception {
//
// 1) URLEncoder.encode("AB가각", "UTF-8")
// ==> JVM 문자열은 UTF-16 바이트 배열이다.
// 0041 0042 ac00 ac01
// ==> UTF-8 바이트로 변환한다.
// 41 42 ea b0 80 ea b0 81
// ==> 8비트 데이터가 짤리지 않도록 URL 인코딩으로 7비트화 시킨다.
// "AB%EA%B0%80%EA%B0%81"
// 41 42 25 45 41 25 42 30 25 38 30 25 45 41 25 42 30 25 38 31
// ==> 웹 브라우저에서는 받은 값을 그대로 저장
//
// 2) 쿠키를 다시 서버로 보내기
// ==> 웹 브라우저는 저장된 값을 그대로 전송
// "AB%EA%B0%80%EA%B0%81"
// 41 42 25 45 41 25 42 30 25 38 30 25 45 41 25 42 30 25 38 31
// ==> 프론트 컨트롤러가 쿠키 값을 꺼낼 때 자동으로 URL 디코딩을 수행한다.
// 즉 7비트 문자화된 코드를 값을 원래의 8비트 코드로 복원한다.
// 41 42 ea b0 80 ea b0 81
// ==> 디코딩 하여 나온 바이트 배열을 UTF-16으로 만든다.
// 문제는 바이트 배열을 ISO-8859-1로 간주한다는 것이다.
// 그래서 UTF-16으로 만들 때 무조건 앞에 00 1바이트를 붙인다.
// 0041 0042 00ea 00b0 0080 00ea 00b0 0081
// 그래서 한글이 깨진 것이다.
//
// 해결책:
// => UTF-16을 ISO-8859-1 바이트 배열로 변경한다.
// 41 42 ea b0 80 ea b0 81
byte[] originBytes = name2.getBytes("ISO-8859-1");
// => 다시 바이트 배열을 UTF-16으로 바꾼다.
// 이때 바이트 배열이 UTF-8로 인코딩된 값임을 알려줘야 한다.
// 0041 0042 ac00 ac01
String namex = new String(originBytes, "UTF-8");
return String.format(//
"name1=%s\n name2=%s\n name2=%s\n name3=%s\n age=%d\n", //
name1, name2, namex, name3, age);
}
}
요청 핸들러의 아규먼트 - multipart/form-data 형식의 파라미터 값 받기
package bitcamp.app1;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.http.Part;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("/c04_8")
public class Controller04_8 {
// ServletContext는 메서드의 아규먼트로 받을 수 없다.
// 의존 객체로 주입 받아야 한다.
@Autowired
ServletContext sc;
// 클라이언트가 멀티파트 형식으로 전송한 데이터를 꺼내기
// => Servlet API에서 제공하는 Part를 사용하거나
// 또는 Spring에서 제공하는 MultipartFile 타입의 아규먼트를 선언하면 된다.
//
// 주의!
// => DispatcherServlet을 web.xml을 통해 배치했다면,
// <multipart-config/> 태그를 추가해야 한다.
// => WebApplicationInitializer를 통해 DispatcherServlet을 배치했다면,
// App1WebApplicationInitializer 클래스를 참고하라!
//
// 테스트:
// http://.../html/app1/c04_8.html
@PostMapping(value = "h1", produces = "text/html;charset=UTF-8")
@ResponseBody
public String handler1(
String name,
int age,
Part photo // Servlet API의 객체
) throws Exception {
String filename = null;
if (photo.getSize() > 0) {
filename = UUID.randomUUID().toString();
String path = sc.getRealPath("/html/app1/" + filename);
photo.write(path);
}
return "<html><head><title>c04_8/h1</title></head><body>" + "<h1>업로드 결과</h1>" + "<p>이름:" + name
+ "</p>" + "<p>나이:" + age + "</p>" +
// 현재 URL이 다음과 같기 때문에 업로드 이미지의 URL을 이 경로를 기준으로 계산해야 한다.
// http://localhost:8080/java-spring-webmvc/app1/c04_8/h1
//
(filename != null ? "<p><img src='../../html/app1/" + filename + "'></p>" : "")
+ "</body></html>";
}
// MultipartFile로 멀티파트 데이터를 받으려면,
// Spring WebMVC 설정에서 MultipartResolver 객체를 등록해야 한다.
//
// 테스트:
// http://.../html/app1/c04_8.html
@PostMapping(value = "h2", produces = "text/html;charset=UTF-8")
@ResponseBody
public String handler2(//
String name, //
@RequestParam(defaultValue = "0") int age, //
MultipartFile photo // Spring API의 객체
) throws Exception {
String filename = null;
if (!photo.isEmpty()) {
filename = UUID.randomUUID().toString();
String path = sc.getRealPath("/html/app1/" + filename);
photo.transferTo(new File(path));
}
return "<html><head><title>c04_8/h2</title></head><body>" + "<h1>업로드 결과</h1>" + "<p>이름:" + name
+ "</p>" + "<p>나이:" + age + "</p>" +
// 현재 URL이 다음과 같기 때문에 업로드 이미지의 URL을 이 경로를 기준으로 계산해야 한다.
// http://localhost:8080/java-spring-webmvc/app1/c04_8/h2
//
(filename != null ? "<p><img src='../../html/app1/" + filename + "'></p>" : "")
+ "</body></html>";
}
// 테스트:
// http://.../html/app1/c04_8.html
@PostMapping(value = "h3", produces = "text/html;charset=UTF-8")
@ResponseBody
public String handler3(//
String name, //
int age, //
// 같은 이름으로 전송된 여러 개의 파일은 배열로 받으면 된다.
MultipartFile[] photo //
) throws Exception {
StringWriter out0 = new StringWriter();
PrintWriter out = new PrintWriter(out0);
out.println("<html><head><title>c04_8/h3</title></head><body>");
out.println("<h1>업로드 결과</h1>");
out.printf("<p>이름:%s</p>\n", name);
out.printf("<p>나이:%s</p>\n", age);
for (MultipartFile f : photo) {
if (!f.isEmpty()) {
String filename = UUID.randomUUID().toString();
String path = sc.getRealPath("/html/app1/" + filename);
f.transferTo(new File(path));
out.printf("<p><img src='../../html/app1/%s'></p>\n", filename);
}
}
out.println("</body></html>");
return out0.toString();
}
// 테스트:
// http://.../html/app1/c04_8.html
@PostMapping(value = "h4", produces = "text/html;charset=UTF-8")
@ResponseBody
public String handler4(
String name,
int age,
// 같은 이름으로 전송된 여러 개의 파일은 배열로 받으면 된다.
MultipartFile[] photo
) throws Exception {
StringWriter out0 = new StringWriter();
PrintWriter out = new PrintWriter(out0);
out.println("<html><head><title>c04_8/h3</title></head><body>");
out.println("<h1>업로드 결과</h1>");
out.printf("<p>이름:%s</p>\n", name);
out.printf("<p>나이:%s</p>\n", age);
for (MultipartFile f : photo) {
String filename = UUID.randomUUID().toString();
String path = sc.getRealPath("/html/app1/" + filename);
f.transferTo(new File(path));
out.printf("<p><img src='../../html/app1/%s'></p>\n", filename);
}
out.println("</body></html>");
return out0.toString();
}
}
요청 핸들러의 아규먼트 - @RequestBody : 클라이언트가 보낸 데이터를 한 덩어리로 받기
package bitcamp.app1;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c04_9")
public class Controller04_9 {
// 클라이언트가 보낸 데이터를 통째로 받기
// => request handler의 아규먼트 앞에 @RequestBody를 붙이면 된다.
// 테스트:
// http://.../html/app1/c04_9.html
@PostMapping(value="h1", produces="text/html;charset=UTF-8")
@ResponseBody
public String handler1(
String name,
int age,
// 클라이언트가 보낸 데이터를 통째로 받으려면 @RequestBody 애노테이션을 붙인다.
@RequestBody String data
) throws Exception {
StringWriter out0 = new StringWriter();
PrintWriter out = new PrintWriter(out0);
out.println("<html><head><title>c04_9/h1</title></head><body>");
out.println("<h1>결과</h1>");
out.printf("<p>이름:%s</p>\n", name);
out.printf("<p>나이:%s</p>\n", age);
out.printf("<p>통데이터:%s</p>\n", data);
out.println("</body></html>");
return out0.toString();
}
}
요청 핸들러의 리턴 값 - 콘텐트를 직접 리턴하기
package bitcamp.app1;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c05_1")
public class Controller05_1 {
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_1/h1
@GetMapping("h1")
@ResponseBody
public String handler1() {
// 리턴 값이 클라이언트에게 보내는 콘텐트라면
// 메서드 선언부에 @ResponseBody를 붙인다.
// => 붙이지 않으면 프론트 컨트롤러는 view URL로 인식한다.
// => 출력 콘텐트는 브라우저에서 기본으로 HTML로 간주한다.
// 단 한글은 ISO-8859-1 문자표에 정의된 코드가 아니기 때문에
// 클라이언트로 보낼 때 '?' 문자로 바꿔 보낸다.
return "<html><body><h1>abc가각간</h1></body></html>";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_1/h2
// => 리턴되는 콘텐트의 MIME 타입과 charset을 지정하고 싶다면
// 애노테이션의 produces 프로퍼티에 설정하라.
@GetMapping(value = "h2", produces = "text/html;charset=UTF-8")
@ResponseBody
public String handler2() {
return "<html><body><h1>abc가각간<h1></body></html>";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_1/h3
@GetMapping("h3")
@ResponseBody
public String handler3(HttpServletResponse response) {
// HttpServletResponse에 대해 다음과 같이 콘텐트 타입을 설정해봐야 소용없다.
response.setContentType("text/html;charset=UTF-8");
return "<html><body><h1>abc가각간<h1></body></html>";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_1/h4
@GetMapping("h4")
public HttpEntity<String> handler4(HttpServletResponse response) {
// HttpEntity 객체에 콘텐트를 담아 리턴할 수 있다.
// 이 경우에는 리턴 타입으로 콘텐트임을 알 수 있기 때문에
// @ResponseBody 애노테이션을 붙이지 않아도 된다.
HttpEntity<String> entity = new HttpEntity<>(
"<html><body><h1>abc가각간<h1></body></html>");
// 이 경우에는 출력할 때 ISO-8859-1 문자표의 코드로 변환하여 출력한다.
// 그래서 한글은 ? 문자로 변환된다.
return entity;
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_1/h5
@GetMapping(value = "h5", produces = "text/html;charset=UTF-8")
public HttpEntity<String> handler5(HttpServletResponse response) {
// 한글을 제대로 출력하고 싶으면 위 애노테이션의 produces 속성에 콘텐트 타입을 지정한다.
HttpEntity<String> entity = new HttpEntity<>(
"<html><body><h1>abc가각간<h1></body></html>");
return entity;
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_1/h6
@GetMapping("h6")
public HttpEntity<String> handler6(HttpServletResponse response) {
// 한글을 제대로 출력하고 싶으면,
// 응답 헤더에 직접 Content-Type을 설정할 수 있다.
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/html;charset=UTF-8");
HttpEntity<String> entity = new HttpEntity<>(
"<html><body><h1>abc가각간<h1></body></html>",
headers);
return entity;
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_1/h7
@GetMapping("h7")
public ResponseEntity<String> handler7(HttpServletResponse response) {
// HttpEntity 대신에 ResponseEntity 객체를 리턴 할 수 있다.
// 이 클래스의 경우 응답 상태 코드를 추가하기 편하다.
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/html;charset=UTF-8");
// 이렇게 응답 헤더를 따로 설정하는 방법이 존재하는 이유는
// 다음과 같이 임의의 응답 헤더를 추가하는 경우가 있기 때문이다.
headers.add("BIT-OK", "ohora");
ResponseEntity<String> entity = new ResponseEntity<>(
"<html><body><h1>abc가각간<h1></body></html>",
headers,
HttpStatus.OK // 응답 상태 코드를 설정할 수 있다.
);
return entity;
}
}
요청 핸들러의 리턴 값 - view URL 리턴하기, 리다이렉트, forward/include
package bitcamp.app1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.JstlView;
@Controller
@RequestMapping("/c05_2")
public class Controller05_2 {
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_2/h1
@GetMapping("h1")
public String handler1() {
// 메서드 선언부에 @ResponseBody를 붙이지 않으면
// 프론트 컨트롤러는 view URL로 간주한다.
// => 리턴 URL의 '/'는 웹 애플리케이션 루트를 의미한다.
return "/jsp/c05_2.jsp";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_2/h2
@GetMapping("h2")
public String handler2() {
// MVC 패턴에서는 항상 Controller에 의해 View가 통제되어야 한다.
// Controller를 경유하지 않고 View를 실행하게 해서는 안된다.
// 그래야 View에 대해 일관성 있는 제어가 가능하다.
// 문제는 jsp 파일을 웹 애플리케이션의 일반 폴더에 두게 되면
// 다음과 같이 클라이언트에서 직접 실행을 요청할 수 있다.
// http://localhost:9999/eomcs-spring-webmvc/jsp/c05_2.jsp
//
// 이것을 막으려면, 다음과 같이 WEB-INF 폴더 밑에 JSP 파일을 두어라.
// /WEB-INF 폴더에 있는 파일은 클라이언트에서 직접 실행을 요청할 수 없다.
return "/WEB-INF/jsp/c05_2.jsp";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_2/h3
@GetMapping("h3")
public View handler3() {
return new JstlView("/WEB-INF/jsp/c05_2.jsp");
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_2/h4
@GetMapping("h4")
public ModelAndView handler4() {
System.out.println("===> /app1/c05_2/h4");
ModelAndView mv = new ModelAndView();
mv.setViewName("/WEB-INF/jsp/c05_2.jsp");
return mv;
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_2/h5
@GetMapping("h5")
public String handler5() {
// 리다이렉트를 지정할 때는 URL 앞에 "redirect:" 접두어를 붙인다.
// 즉 HTTP 응답이 다음과 같다.
// HTTP/1.1 302
// Location: h4
// Content-Language: ko-KR
// Content-Length: 0
// Date: Fri, 19 Apr 2019 07:57:00 GMT
return "redirect:h4";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_2/h6
@GetMapping("h6")
public String handler6() {
// 포워드를 지정할 때는 URL 앞에 "forward:" 접두어를 붙인다.
// 인클루드를 지정할 때는 URL 앞에 "include:" 접두어를 붙인다.
return "forward:h4";
}
}
요청 핸들러에서 view 컴포넌트(JSP) 쪽에 데이터 전달하기
package bitcamp.app1;
import java.util.Map;
import javax.servlet.ServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/c05_3")
public class Controller05_3 {
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_3/h1
@GetMapping("h1")
public String handler1(
ServletRequest request) {
// JSP가 꺼내 쓸 수 있도록 ServletRequest 객체에 직접 담는다.
request.setAttribute("name", "홍길동");
request.setAttribute("age", 20); // auto-boxing: int ===> Integer 객체
request.setAttribute("working", true); // auto-boxing: boolean ===> Boolean 객체
return "/WEB-INF/jsp/c05_3.jsp";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_3/h2
@GetMapping("h2")
public String handler2(Map<String,Object> map) {
// 아규먼트에 Map 타입의 변수를 선언하면
// 프론트 컨트롤러는 빈 맵 객체를 만들어 넘겨준다.
// 이 맵 객체의 용도는 JSP에 전달할 값을 담는 용이다.
// 맵 객체에 값을 담아 놓으면 프론트 컨트롤러가 JSP를 실행하기 전에
// ServletRequest로 복사한다.
// 따라서 ServletRequest에 값을 담는 것과 같다.
//
map.put("name", "홍길동");
map.put("age", 20); // auto-boxing
map.put("working", true); // auto-boxing
return "/WEB-INF/jsp/c05_3.jsp";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_3/h3
@GetMapping("h3")
public String handler3(Model model) {
// 아규먼트에 Model 타입의 변수를 선언하면
// 프론트 컨트롤러는 모델 객체를 만들어 넘겨준다.
// 이 객체의 용도는 Map 객체와 같다.
//
model.addAttribute("name", "홍길동");
model.addAttribute("age", 20); // auto-boxing
model.addAttribute("working", true); // auto-boxing
return "/WEB-INF/jsp/c05_3.jsp";
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app1/c05_3/h4
@GetMapping("h4")
public ModelAndView handler4() {
// request handler에서 ModelAndView 객체를 만들어 리턴한다.
// => 이 객체의 용도는 Model과 view URL을 함께 리턴하는 것이다.
//
ModelAndView mv = new ModelAndView();
// JSP가 사용할 데이터를 담고
mv.addObject("name", "홍길동");
mv.addObject("age", 20); // auto-boxing
mv.addObject("working", true); // auto-boxing
// JSP 주소도 담는다.
mv.setViewName("/WEB-INF/jsp/c05_3.jsp");
return mv;
}
}
eomcs-spring-webmvc/src-14/~/app2
package bitcamp.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class App2WebApplicationInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {App2Config.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/app2/*"};
}
@Override
protected String getServletName() {
return "app2";
}
}
helper.setRemoveSemicolonContent(false);
// DispatcherServlet의 MVC Path 관련 설정을 변경한다.
configurer.setUrlPathHelper(helper);
}
// 이 설정을 사용하는 프론트 컨트롤러에 적용할 인터셉터 설정하기
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 1) 모든 요청에 대해 실행할 인터셉터 등록하기
// => 인터셉터를 적용할 URL을 지정하지 않으면
// 현재 프론트 컨트롤러의 모든 요청에 대해 적용된다.
//
registry.addInterceptor(new Controller04_1_Interceptor1());
// 2) 특정 요청에 대해 실행할 인터셉터 등록하기
// => 패턴: /c04_1/*
// 적용 가능:
// /c04_1/x
// /c04_1/y
// 적용 불가:
// /x
// /c03_1/x
// /c04_1/a/x
// /c04_1/a/b/x
// 즉, /c04_1/ 바로 밑의 있는 자원에 대해서만 인터셉터를 적용한다.
//
registry
.addInterceptor(new Controller04_1_Interceptor2())
.addPathPatterns("/c04_1/*");
// 3) 특정 요청에 대해 실행할 인터셉터 등록하기
// => 패턴: /c04_1/**
// 적용 가능:
// /c04_1/x
// /c04_1/y
// /c04_1/a/x
// /c04_1/a/b/x
// 적용 불가:
// /x
// /c03_1/x
// 즉, /c04_1/ 의 모든 하위 경로에 있는 자원에 대해서만 인터셉터를 적용한다.
//
registry
.addInterceptor(new Controller04_1_Interceptor3())
.addPathPatterns("/c04_1/**");
// 4) 특정 요청에 대해 인터셉터 적용을 제외하기
// => 패턴: /c04_1/** (include), /c04_1/a/** (exclude)
// 적용 가능:
// /c04_1/x
// /c04_1/y
// /c04_1/b/x
// /c04_1/b/c/x
// 적용 불가:
// /x
// /c03_1/x
// /c04_1/a/x
// /c04_1/a/b/x
// 즉, /c04_1/ 의 모든 하위 경로에 있는 자원에 대해서만 인터셉터를 적용한다.
// 단 /c04_1/a/ 의 모든 하위 경로에 있는 자원은 제외한다.
registry
.addInterceptor(new Controller04_1_Interceptor4())
.addPathPatterns("/c04_1/**")
.excludePathPatterns("/c04_1/a/**");
}
}
기본 View Resolver 사용하기
package bitcamp.app2;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/c01_1")
public class Controller01_1 {
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_1/h1
@GetMapping("h1")
// @ResponseBody // 뷰 이름을 리턴 할 때는 이 애노테이션을 붙이면 안된다.
public String handler1(Model model) {
model.addAttribute("name", "홍길동");
model.addAttribute("age", 20);
return "/jsp/c01_1.jsp";
// 기본 ViewResolver는 리턴 값으로 URL을 받아
// 웹 애플리케이션 디렉토리에서 JSP를 찾는다.
// 웹 애플리케이션이 경로가 /eomcs-spring-webmvc 라면,
// JSP 경로는 다음과 같다.
// ==> /eomcs-spring-webmvc/jsp/c01_1.jsp
//
// InternalResourceViewResolver로 교체한 다음의 JSP URL은?
// => /WEB-INF/jsp2//jsp/c01_1.jsp.jsp
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_1/h2
@GetMapping("h2")
public void handler2(Model model) {
model.addAttribute("name", "홍길동2");
model.addAttribute("age", 30);
// 기본 ViewRosolver를 사용할 때는
// 뷰 이름을 리턴하지 않으면 오류 발생!
//
// InternalResourceViewResolver로 교체한 다음은?
// => 리턴 값이 없으면 요청 URL(/c01_1/h2)을 리턴 값으로 사용한다.
// => 따라서 ViewResolver가 계산한 최종 URL은
// /WEB-INF/jsp2/c01_1/h2.jsp
//
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_1/h3
@GetMapping("h3")
public String handler3(Map<String, Object> map) {
map.put("name", "홍길동3");
map.put("age", 40);
return "/WEB-INF/jsp/c01_1.jsp";
// MVC 모델에서는 JSP는 뷰 콤포넌트로서 출력이라는 역할을 담당한다.
// 출력할 데이터를 준비하는 일은 페이지 컨트롤러가 담당한다.
// 그래서 JSP를 실행할 때는 항상 페이지 컨트롤러를 통해 실행해야 한다.
// 페이지 컨트롤러가 하는 일이 없어도 프로그래밍의 일관성을 위해
// 직접 JSP을 요청하지 않고, 페이지 컨트롤러를 통해 요청해야 한다.
//
// 그런데 웹 디렉토리에 JSP를 두면 클라이언트에서 JSP를 직접 요청할 수 있다.
// 페이지 컨트롤러를 경유하지 않은 상태에서 실행해봐야 소용없지만,
// 그래도 요청은 할 수 있다.
// 이런 의미 없는 요청을 막는 방법으로,
// JSP 파일을 /WEB-INF 폴더 아래에 두는 것을 권장한다.
//
// 웹 브라우저에서 다음 URL의 JSP를 요청해보라!
// 1) http://localhost:8888/bitcamp-java-spring-webmvc/jsp/c01_1.jsp
// => 클라이언트가 요청할 수 있다.
// 2) http://localhost:8888/bitcamp-java-spring-webmvc/WEB-INF/jsp/c01_1.jsp
// => 클라이언트가 요청할 수 없다.
// => /WEB-INF 폴더에 있는 자원들은 클라이언트에서 직접 요청할 수 없다.
// => 그래서 잘못된 요청을 막을 수 있다.
// 실무에서는 이 방법을 사용한다.
}
}
기본 View Resolver 교체 - InternalResourceViewResolver 사용하기
package bitcamp.app2;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/c01_2")
public class Controller01_2 {
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_2/h1
@GetMapping("h1")
public String handler1(Map<String, Object> map) {
map.put("name", "홍길동");
map.put("age", 20);
//
// ViewResolver?
// => 실행할 뷰를 찾는 일을 한다.
// => 페이지 컨트롤러가 리턴한 뷰 이름에 해당하는 뷰 콤포넌트를 찾는 역할.
// => ResourceBundleViewResolver
// - *.properties 에서 뷰 이름에 해당하는 콤포넌트의 URL을 찾는다.
// => InternalResouceViewResolver
// - 미리 지정된 접두사, 접미사를 사용하여 뷰이름으로 콤포넌트의 URL을 완성한다.
//
// View?
// => 뷰를 실행하는 일을 한다.
// => 템플릿 엔진을 실행하여 실제 클라이언트로 보낼 콘텐트를 생성한다.
// => Thymeleaf, FreeMarker, JSP/JSTL, Tiles, RSS/Atom, PDF, Excel 등의
// 엔진을 이용하여 콘텐트를 생성하는 뷰가 있다.
//
// ViewResolver 교체
// => InternalResourceViewResolver를 사용하여
// JSP URL의 접두어와 접미사를 미리 설정해 둘 수 있어 URL을 지정하기 편리하다.
// => 교체 방법은 XML에서 설정하는 방법과 Java Config로 설정하는 방법이 있다.
// 자세한 것은 App2Config 클래스를 참고하라!
//
// ViewResolver 실행 과정?
// => 페이지 컨트롤러는 클라이언트가 요청한 작업을 실행한 후
// 그 결과를 출력할 뷰의 이름을 리턴한다.
// => 프론트 컨트롤러는 request handler가 리턴한 URL을
// view resolver에게 전달한다.
// => view resolver는 자신의 정책에 맞춰서 뷰 URL을 준비한다.
// => InternalResourceViewResolver의 경우
// request handler가 리턴한 URL 앞, 뒤에
// 접두사와 접미사를 붙여 JSP를 찾는다.
// 예를 들어 다음 URL을 리턴하면,
// "c01_1/h1"
// 최종 JSP URL은,
// "/WEB-INF/jsp2/c01_2/h1.jsp"
// 이다.
// => 프론트 컨트롤러는 ViewResolver가 준비한 URL을 가지고
// View 객체를 통해 해당 URL의 자원을 실행한다.
//
return "c01_2/h1"; // => /WEB-INF/jsp2/c01_1/h1.jsp
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_1/h2
@GetMapping("h2")
public void handler2(Model model) {
model.addAttribute("name", "홍길동2");
model.addAttribute("age", 30);
// InternalResourceViewResolver를 사용하는 경우,
// request handler가 뷰 이름을 리턴하지 않으면
// request handler의 URL을 뷰 이름으로 사용한다.
// 즉 이 핸들러의 URL은 "/c01_2/h2" 이기 때문에 뷰 이름도 "/c01_2/h2"가 된다.
// InternalResourceViewResolver는 바로 이 URL을 사용하여 다음과 같이 최종 URL을 만든다.
// => "/WEB-INF/jsp2/" + "/c01_2/h2" + ".jsp"
// => "/WEB-INF/jsp2/c01_2/h2.jsp"
//
// 실무에서는 이 방법을 많이 사용한다.
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_2/h3
@GetMapping("h3")
public Map<String, Object> handler3() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "홍길동3");
map.put("age", 40);
// Map 객체에 값을 담아 리턴하면
// 프론트 컨트롤러는 Map 객체에 보관되어 있는 값들을 ServletRequest 보관소로 옮긴다.
// 그리고 view URL은 request handler의 URL을 사용한다.
// => "/WEB-INF/jsp2" + "/c01_2/h3" + ".jsp" = "/WEB-INF/jsp2/c01_2/h3.jsp"
return map;
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_2/h4
@GetMapping("h4")
public ModelAndView handler4() {
ModelAndView mv = new ModelAndView();
mv.addObject("name", "홍길동3");
mv.addObject("age", 40);
mv.setViewName("c01_2/h4");
// ModelAndView 객체에 값과 뷰 이름을 담아 리턴하면
// 프론트 컨트롤러는 ModelAndView 객체에 보관되어 있는 값들을
// ServletRequest 보관소로 옮기고,
// 설정된 뷰 이름을 ViewResolver에게 넘긴다.
//
return mv;
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c01_2/h5
@GetMapping("h5")
public ModelAndView handler5() {
ModelAndView mv = new ModelAndView();
mv.addObject("name", "홍길동3");
mv.addObject("age", 40);
// ModelAndView 객체에 값과 뷰 이름을 담아 리턴하면
// 프론트 컨트롤러는 ModelAndView 객체에 보관되어 있는 값들을
// ServletRequest 보관소로 옮기고,
// 뷰 이름을 지정하지 않으면 request handler의 path를 ViewResolver에게 넘긴다.
// => /c01_2/h5
// InternalResourceViewResolver 는 위 URL을 다음과 같이 바꾼다.
// => /WEB-INF/jsp2/c01_2/h5.jsp
// 그런 후에 해당 URL의 JSP를 실행한다.
return mv;
}
}
URL 에서 값 추출하기 - @PathVariable
package bitcamp.app2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c02_1")
public class Controller02_1 {
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c02_1?name=kim&age=20
@GetMapping
@ResponseBody
public String handler1(String name, int age) {
// 클라이언트로부터 값을 받는 일반적인 방법
// => Query String 으로 받는다.
// => 즉 URL 다음에 "?변수=값&변수=값" 형태로 값을 받는다.
// => Query String의 값을 request handler에서 받으려면
// 아규먼트를 선언하면 된다.
// 아규먼트 앞에 @RequestParam을 붙여도 되고
// 아규먼트이 이름이 요청 파라미터의 이름과 같다면 @RequestParam을 생략해도 된다.
return String.format("name=%s, age=%d", name, age);
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c02_1/kim/20
@GetMapping("{name}/{age}")
@ResponseBody
public String handler2(
/*
@PathVariable("name") String name,
@PathVariable("age") int age
*/
// URL의 변수 이름을 생략하면 아규먼트 이름을 사용한다.
@PathVariable String name,
@PathVariable int age
) {
// URL path에 값을 포함하여 전달할 수 있고, 그 값을 아규먼트로 받을 수 있다.
// URL path에 포함된 값을 받으려면 request handler의 URL을 설정할 때
// 다음의 문법으로 선언해야 한다.
// => .../{변수명}/{변수명}
// 이렇게 선언된 변수 값을 받으려면 다음과 같이 아규먼트를 선언해야 한다.
// => @PathVariable(변수명) String 아규먼트
// 변수명과 아규먼트의 이름이 같다면, 다음과 같이 변수명을 생략할 수 있다.
// => @PathVariable String 아규먼트
//
return String.format("name=%s, age=%d", name, age);
}
// 테스트:
// http://localhost:9999/eomcs-spring-webmvc/app2/c02_1/kim_20
@GetMapping("{name}_{age}")
@ResponseBody
public String handler3(
@PathVariable String name,
@PathVariable int age
) {
return String.format("name=%s, age=%d", name, age);
}
}
URL 에서 값 추출하기 - @MatrixVariable
package bitcamp.app2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c02_2")
public class Controller02_2 {
// 테스트:
// http://.../app2/c02_2?name=kim&age=20
@GetMapping
@ResponseBody
public String handler1(String name, int age) {
// Query String 으로 값 받기
return String.format("name=%s, age=%d", name, age);
}
// 테스트:
// http://.../app2/c02_2/name=kim;age=20
@GetMapping(value = "{value}", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String handler2(//
@PathVariable("value") String value,
// value 값 중에서 name 항목의 값을 받고 싶을 때 @MatrixVariable 을 사용한다.
// 단 value의 형식은 "이름=값;이름=값;이름=값" 형태여야 한다.
// @MatrixVariable("name") String name,
// @MatrixVariable("age") int age
// 매트릭스 변수명을 생략하면 아규먼트의 이름을 사용한다.
@MatrixVariable String name,
@MatrixVariable int age
) {
// @MatrixVariable 애노테이션을 사용하려면
// IoC 컨테이너에서 이 애노테이션을 활성화시키는 설정을 추가해야 한다.
// 1) XML 설정
// => <mvc:annotation-driven enable-matrix-variables="true"/>
// 2) Java Config 설정
// => @EnableWebMvc 애노테이션을 활성화시킨다.
// => WebMvcConfigurer 구현체를 정의한다.
// => UrlPathHelper 객체의 removeSemicolonContent 프로퍼티 값을 false로 설정한다.
// 테스트1
// http://.../app2/c02_2/name=kim;age=20
// => @PathVariable("value") : name=kim <== 첫 번째 세미콜론의 값만 가져온다.
// => @MatrixVariable("name") : kim
// => @MatrixVariable("age") : 20
//
// 테스트2
// http://.../app2/c02_2/user;name=kim;age=20
// => @PathVariable("value") : user <== 첫 번째 세미콜론의 값만 가져온다.
// => @MatrixVariable("name") : kim
// => @MatrixVariable("age") : 20
return String.format("value:%s \n name:%s, age:%d", value, name, age);
}
// 테스트:
// http://.../app2/c02_2/name=teamA;qty=5/title=work1;state=1
@GetMapping(value = "{team}/{task}", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String handler3(
// 여러 개의 패스 변수가 있을 때 값을 꺼내는 방법
@MatrixVariable String name,
@MatrixVariable int qty,
@MatrixVariable String title,
@MatrixVariable int state
) {
return String.format("team: %s(%d)\n task: %s, %d", name, qty, title, state);
}
// http://.../app2/c02_2/h4/name=teamA;qty=5/name=work1;qty=1
@GetMapping(value = "h4/{team}/{task}", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String handler4(
// 여러 개의 패스 변수가 있을 때 값을 꺼내는 방법
// => 만약 항목의 이름이 같다면?
@MatrixVariable(name = "name", pathVar = "team") String name1,
@MatrixVariable(name = "qty", pathVar = "team") int qty1,
@MatrixVariable(name = "name", pathVar = "task") String name2,
@MatrixVariable(name = "qty", pathVar = "task") int qty2) {
return String.format("team: %s(%d)\n task: %s, %d", name1, qty1, name2, qty2);
}
}
URL 에서 값 추출하기 - 정규표현식으로 URL 다루기
package bitcamp.app2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c02_3")
public class Controller02_3 {
// 테스트:
// http://.../app2/c02_3/h1/hongkildong/010-1111-2222/man
@GetMapping(
value="h1/{name}/{tel}/{gender}",
produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler1(
@PathVariable String name,
@PathVariable String tel,
@PathVariable String gender) {
return String.format("name: %s\n tel: %s \n gender: %s",
name, tel, gender);
}
// 테스트:
// http://.../app2/c02_3/h1/hongkildong/010-1111-2222/man
@GetMapping(
value="h2/{name:[a-zA-Z0-9]+}/{tel:[0-9]+-[0-9]+-[0-9]+}/{gender:man|woman}",
produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler2(
@PathVariable String name,
@PathVariable String tel,
@PathVariable String gender) {
// 패스 변수를 사용할 때,
// 패스 변수의 값 규칙을 정규표현식으로 정의할 수 있다.
// 정규 표현식에 어긋간 URL의 경우 예외가 발생할 것이다.
//
return String.format("name: %s\n tel: %s \n gender: %s",
name, tel, gender);
}
}
세션 다루기 - HttpSession 직접 사용하기
package bitcamp.app2;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/c03_1")
public class Controller03_1 {
// 테스트:
// http://.../app2/c03_1/h1
@GetMapping(value="h1", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler1(HttpSession session) {
// HttpSession 객체를 사용하려면 아규먼트로 받아야 한다.
//
session.setAttribute("name", "홍길동");
session.setAttribute("age", "20");
return "세션에 값을 보관했음!";
}
// 테스트:
// http://.../app2/c03_1/h2
@GetMapping(value="h2", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler2(HttpSession session) {
// HttpSession 객체를 사용하려면 아규먼트로 받아야 한다.
//
return String.format("name=%s, age=%s",
session.getAttribute("name"),
session.getAttribute("age"));
}
// 테스트:
// http://.../app2/c03_1/h3
@GetMapping(value="h3", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler3(HttpSession session) {
// HttpSession 객체를 사용하려면 아규먼트로 받아야 한다.
//
// 세션을 무효화시키기
session.invalidate();
return "세션을 무효화시켰음!";
}
@GetMapping(value="h4", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler4(
// 현재 페이지 컨트롤러의 @SessionAttributes 에 지정된 이름이 아니라면,
// 프론트 컨트롤러는 요청 파라미터에서 해당 이름의 값을 찾아 넘겨준다.
// 요청 파라미터에 해당 이름의 값이 없다면,
// 프론트 컨트롤러는 빈 문자열을 넘겨준다.
@ModelAttribute("name") String name,
@ModelAttribute("age") String age,
@ModelAttribute("name2") String name2,
@ModelAttribute("age2") String age2) {
return String.format("name=%s, age=%s, name2=%s, age2=%s",
name, age, name2, age2);
}
}
세션 다루기 - @SessionAttributes, @ModelAttribute
package bitcamp.app2;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@RequestMapping("/c03_2")
// request handler 가 뷰 컴포넌트(jsp)에 전달하는 값 중에서
// 세션에 보관할 값의 이름을 지정하면
// 프론트 컨트롤러는 그 값을 HttpSession 객체에도 보관해 둔다.
// 또한 @ModelAttribute에서 지정한 이름의 값을 세션에서 꺼낼 때도 사용한다.
// 즉 @SessionAttributes 에 이름을 지정해 두지 않으면
// 세션에 해당 값이 들어 있어도
// @ModelAttribute가 붙은 아규먼트에 값을 넣어주지 않는다.
@SessionAttributes({"name2","age2"})
public class Controller03_2 {
// 테스트:
// http://.../app2/c03_2/h1
@GetMapping(value="h1", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler1(Model model) {
// Model 객체에 값을 담으면 프론트 컨트롤러는 ServletRequest 보관소에 값을 옮긴다.
// 만약 @SessionAttributes 에서 지정한 이름의 값이라면
// HttpSession 객체에도 보관된다.
model.addAttribute("name2", "임꺽정");
model.addAttribute("age2", "30");
// @SessionAttributes에 등록되지 않은 이름의 값은 세션에 보관되지 않는다.
model.addAttribute("tel2", "1111-2222");
return "세션에 값 보관했음!";
}
// 테스트:
// http://.../app2/c03_2/h2
@GetMapping(value="h2", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler2(HttpSession session) {
return String.format("name2=%s, age2=%s, tel2=%s",
session.getAttribute("name2"),
session.getAttribute("age2"),
session.getAttribute("tel2"));
}
// 테스트:
// http://.../app2/c03_2/h3
@GetMapping(value="h3", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler3(
// @ModelAttribute 에 지정된 이름이 @SessionAttributes에 있는 경우
// => 세션에 해당 값이 있으면 아규먼트에 넣어 준다.
// => 세션에 해당 값이 없으면 예외가 발생한다.
@ModelAttribute("name2") String name2,
@ModelAttribute("age2") String age2,
// @ModelAttribute 에 지정된 이름이 @SessionAttributes에 없는 경우
// => 요청 파라미터에 tel2 값이 있다면 그 값을 넣어준다.
// => 요청 파라미터에 값이 없으면 아규먼트에 빈 문자열을 넣어 준다.
// => @SessionAttributes에 등록한 이름이 아니기 때문에 세션에서 값을 꺼내지 않는다.
@ModelAttribute("tel2") String tel2) {
return String.format("name2=%s, age2=%s, tel2=%s",
name2, age2, tel2);
}
// 테스트:
// 1) http://.../app2/c03_1/h1 을 실행하여 name과 age 값을 세션에 보관한다.
// 2) http://.../app2/c03_2/h1 을 실행하여 name2와 age2 값을 세션에 보관한다.
// 3) http://.../app2/c03_2/h4 을 실행하여 세션에 보관된 값을 꺼낸다.
@GetMapping(value="h4", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler4(
// c03_1/h1 에서 세션에 저장한 값 꺼내기
// => 꺼내지 못한다. name과 age에는 빈 문자열이 저장된다.
// => 왜?
// @ModelAttribute는 현재 페이지 컨트롤러의 @SessionAttributes에
// 지정된 이름에 한해서만 세션에서 값을 꺼내기 때문이다.
@ModelAttribute("name") String name,
@ModelAttribute("age") String age,
// 현재 컨트롤러에서 세션에 저장한 값 꺼내기
@ModelAttribute("name2") String name2,
@ModelAttribute("age2") String age2) {
return String.format("name=%s, age=%s, name2=%s, age2=%s",
name, age, name2, age2);
}
}
세션 다루기 - 세션의 값을 무효화시키는 방법
package bitcamp.app2;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
@Controller
@RequestMapping("/c03_3")
// 세션에 보관된 값 중에서 현재 페이지 컨트롤러에서 사용할 값을 지정한다.
// 또한 세션에 보관할 값이기도 하다.
@SessionAttributes({"name", "name2"})
public class Controller03_3 {
// 테스트:
// http://.../app2/c03_3/h1
@GetMapping(value = "h1", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String handler1(HttpSession session) {
return String.format("name=%s, age=%s, name2=%s, age2=%s",
session.getAttribute("name"),
session.getAttribute("age"),
session.getAttribute("name2"),
session.getAttribute("age2"));
}
// 테스트:
// http://.../app2/c03_3/h2
@GetMapping(value = "h2", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String handler2(HttpSession session) {
// 세션을 완전히 무효화시키기
session.invalidate();
// 용도:
// => 페이지 컨트롤러에 상관없이 모든 세션 값을 삭제할 때 사용하라.
// 세션 자체를 무효화시켜 다음에 요청이 들어 왔을 때 새로 세션을 만들게 한다.
//
return "session.invalidate()";
}
// 테스트:
// http://.../app2/c03_3/h3
@GetMapping(value = "h3", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String handler3(SessionStatus status) {
// 현재 페이지 컨트롤러의 @SessionAttributes 에 지정된 값만 무효화시키기
status.setComplete();
// 용도:
// => 보통 페이지 컨트롤러는 서로 관련된 작업을 처리하는 요청 핸들러를 정의한다.
// 예) BoardController : add(), detail(), list(), update(), delete()
// => 또는 트랜잭션과 관련된 작업을 처리하는 요청 핸들러를 두기도 한다.
// 예) BookOrderController: 장바구니담기(), 주문하기(), 결제하기()
// => 이렇게 특정 작업에 관계된 요청 핸들러가 작업하는 동안
// 공유할 데이터가 있다면 세션에 보관하면 편할 것이다.
// 작업이 완료되면 그 작업을 처리하는 동안 세션에 보관했던 데이터는 삭제해야 한다.
// 세션의 모든 데이터가 아니라
// 현재 페이지 컨트롤러가 보관한 데이터만 삭제하고 싶을 때
// 바로 이 방식으로 처리하면 된다.
// => 즉 세션을 그대로 유지한채로 이 페이지 컨트롤러에서
// @SessionAttributes로 지정한 값만 무효화시킬 때 사용한다.
return "status.setComplete()";
}
@GetMapping(value="h4", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler4(
@ModelAttribute("name") String name,
@ModelAttribute("age") String age,
@ModelAttribute("name2") String name2,
@ModelAttribute("age2") String age2) {
return String.format("name=%s, age=%s, name2=%s, age2=%s",
name, age, name2, age2);
}
}
스프링 인터셉터 다루기
package bitcamp.app2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/c04_1")
public class Controller04_1 {
// 테스트:
// http://.../app2/c04_1/h1
@GetMapping("h1")
public String handler1() {
System.out.println("c04_1.handler1() 호출");
return "c04_1";
}
// 테스트:
// http://.../app2/c04_1/a/h2
@GetMapping("a/h2")
public String handler2() {
System.out.println("c04_1.handler2() 호출");
return "c04_1";
}
// 테스트:
// http://.../app2/c04_1/b/h3
@GetMapping("b/h3")
public String handler3() {
System.out.println("c04_1.handler3() 호출");
return "c04_1";
}
}
package bitcamp.app2;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
// 인터셉터 만들기
// => 프론트 컨트롤러와 페이지 컨트롤러 사이에 코드를 삽입하는 기술
// => 프론트 컨트롤러와 뷰 컴포넌트 사이에 코드를 삽입하는 기술
//
// 인터셉터를 배치하기
// => 프론트 컨트롤러의 IoC 설정 파일에 배치 정보를 추가한다.
//
public class Controller04_1_Interceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 페이지 컨트롤러의 핸들러를 호출하기 전에 이 메서드가 먼저 호출된다.
System.out.println("Interceptor1.preHandle()");
// 다음 인터셉터나 페이지 컨트롤러를 계속 실행하고 싶다면 true를 리턴한다.
// 여기서 요청 처리를 완료하고 싶다면 false를 리턴한다.
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 페이지 컨트롤러의 핸들러가 리턴한 후 이 메서드가 호출된다.
System.out.println("Interceptor1.postHandle()");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// JSP를 실행한 후 이 메서드가 호출된다.
System.out.println("Interceptor1.afterCompletion()");
}
}
JSON 콘텐트 출력하기
AJAX 요청 테스트:
// http://.../html/app2/c05_1.html
//
@GetMapping("h1")
public void handler1(Model model) {
model.addAttribute("list", this.list);
}
// 2) Google Gson 라이브러리를 사용하여 JSON 형식의 콘텐트 출력하기
// => mvnrepository.com에서 gson 검색하여 라이브러리 정보를 가져온다.
// => build.gradle 파일에 추가한다.
// => '$gradle eclipse' 실행한다.
// => 이클립스에서 프로젝트를 리프래시 한다.
// 테스트:
// http://.../app2/c05_1/h2
@GetMapping(value="h2", produces="text/plain;charset=UTF-8")
@ResponseBody
public String handler2() {
return new Gson().toJson(this.list);
}
// 3) Google Gson 라이브러리를 사용하여 JSON 형식의 콘텐트 출력하기 II
// => 페이지 컨트롤러의 리턴 값이 String이 아니면
// 프론트 컨트롤러는
// Google의 Gson 라이브러리나 Jackson 라이브러리를 사용하여
// 자동으로 JSON 형식의 문자열로 만들어 클라이언트로 출력한다.
// => 단 Gson 또는 Jackson 라이브러리가 있어야 한다.
// 둘 다 있다면 Jackson 라이브러리가 기본으로 사용된다.
// => build.gradle 파일에서 gson 또는 jackson 라이브러리를 추가하는 부분의
// 주석을 참고하라!
// 테스트:
// http://.../app2/c05_1/h3
@GetMapping("h3")
@ResponseBody
public Object handler3() {
return this.list; // JSON 형식의 문자열은 자동으로 UTF-8로 인코딩 된다.
}
}
JSON 콘텐트 출력하기 - @RestController
package bitcamp.app2;
import java.sql.Date;
import java.util.ArrayList;
import java.util.HashMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
// => 페이지 컨트롤러를 @RestController로 선언하면,
// 리턴 값은 HttpMessageConverter에 의해 자동으로 변환된다.
// => @ResponseBody를 붙일 필요가 없다.
//
@RequestMapping("/c05_2")
public class Controller05_2 {
ArrayList<Board> list = new ArrayList<>();
public Controller05_2() {
list.add(new Board(1, "제목입니다1", "내용", "홍길동", 10, Date.valueOf("2019-5-1")));
list.add(new Board(2, "제목입니다2", "내용", "홍길동2", 11, Date.valueOf("2019-5-2")));
list.add(new Board(3, "제목입니다3", "내용", "홍길동3", 12, Date.valueOf("2019-5-3")));
list.add(new Board(4, "제목입니다4", "내용", "홍길동4", 13, Date.valueOf("2019-5-4")));
list.add(new Board(5, "제목입니다5", "내용", "홍길동5", 14, Date.valueOf("2019-5-5")));
list.add(new Board(6, "제목입니다6", "내용", "홍길동6", 15, Date.valueOf("2019-6-1")));
list.add(new Board(7, "제목입니다7", "내용", "홍길동7", 16, Date.valueOf("2019-6-1")));
list.add(new Board(8, "제목입니다8", "내용", "홍길동8", 17, Date.valueOf("2019-6-1")));
list.add(new Board(9, "제목입니다9", "내용", "홍길동9", 18, Date.valueOf("2019-6-1")));
list.add(new Board(10, "제목입니다10", "내용", "홍길동10", 19, Date.valueOf("2019-7-1")));
list.add(new Board(11, "제목입니다11", "내용", "홍길동11", 11, Date.valueOf("2019-8-1")));
list.add(new Board(12, "제목입니다12", "내용", "홍길동12", 12, Date.valueOf("2019-9-1")));
list.add(new Board(13, "제목입니다13", "내용", "홍길동13", 13, Date.valueOf("2019-10-1")));
}
// 테스트:
// http://.../app2/c05_2/h1
@GetMapping("h1")
public Object handler1() {
return this.list; // JSON 형식의 문자열은 자동으로 UTF-8로 인코딩 된다.
}
// 테스트:
// http://.../app2/c05_2/h2
@GetMapping(value="h2", produces="text/plain;charset=UTF-8")
public String handler2() {
return "안녕하세요!"; // String 타입은 그대로 출력한다.
// 단 출력 문자열의 인코딩을 지정해야 한글이 깨지지 않는다.
}
// 테스트:
// http://.../app2/c05_2/h3
@GetMapping("h3")
public int handler3() {
return 100; // primitive 타입의 값도 그대로 출력한다.
}
// 테스트:
// http://.../app2/c05_2/h4
@GetMapping("h4")
public Object handler4() {
// primitive 타입이나 String 타입의 값을 JSON 형식으로 출력하려면
// 도메인 객체나 맵 객체 담아 리턴하라!
HashMap<String,Object> content = new HashMap<>();
content.put("v1", 100);
content.put("v2", "Hello");
return content;
}
}
JSON 콘텐트 입력받기 - @RestController
HttpMessageConverter 구현체(예:MappingJackson2HttpMessageConverter)가
// 클라이언트가 보낸 데이터를 Map 객체에 담아준다.
// => 이 기능을 쓰고 싶다면 Gson 또는 Jackson 라이브러리를 반드시 포함해야 한다.
// 그래야 스프링의 DispatcherServlet에서 찾는다.
// 테스트
// http://.../html/app2/c05_3.html
@RequestMapping(value="h4", produces="text/plain;charset=UTF-8")
public Object handler4(@RequestBody Map<String,Object> content) throws Exception {
System.out.println(content);
return "OK!";
}
// 5) JSON 형식의 요청 파라미터 값을 도메인 객체로 받기
// => HttpMessageConverter 구현체(예: MappingJackson2HttpMessageConverter)가
// 클라이언트가 보낸 데이터를 도메인 객체(예: Board, Member, Lesson 등)에 담아준다.
// => Json 데이터의 프로퍼티 명과 도메인 객체의 프로퍼티 명이 일치해야 한다.
//
// 테스트
// http://.../html/app2/c05_3.html
@RequestMapping(value="h5", produces="text/plain;charset=UTF-8")
public Object handler5(@RequestBody Board content) throws Exception {
System.out.println(content);
// 주의!
// => 클라이언트에서 보낸 날짜 데이터의 문자열 형식이 yyyy-MM-dd 형태여야 한다.
// 그래야 java.util.Date 타입의 값으로 변환해 준다.
// 예) 2019-05-01 ===> java.util.Date 객체 변환 성공!
// => 만약 이 형태가 아니면 변환할 수 없어 실행 오류가 발생한다.
// 예) 05/01/2020 또는 2020-5-1 ===> 변환 오류!
//
// @JsonFormat 애노테이션 사용
// => 이 애노테이션은 MappingJackson2HttpMessageConverter를 위한 것이다.
// GsonHttpMessageConverter는 이 애노테이션을 인식하지 않는다.
// => 도메인 객체의 프로퍼티에 이 애노테이션을 붙이면
// 2019-05-01 이나 2019-5-1 모두 처리할 수 있다.
// => 뿐만 아니라, 도메인 객체를 JSON 문자열로 변환할 때도
// 해당 형식으로 변환된다.
//
return "OK!";
}
}
package bitcamp.app2;
import java.sql.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
public class Board {
protected int no;
protected String title;
protected String content;
protected String writer;
protected int viewCount;
// Jackson 라이브러리를 사용한다면
// Date 필드의 JSON 출력 형식을 지정할 수 있다.
// 다음 애노테이션을 setter나 field에 붙이면 된다.
@JsonFormat(
shape=JsonFormat.Shape.STRING,
pattern="yyyy-MM-dd")
protected Date createdDate;
public Board() {
}
public Board(
int no, String title, String content, String writer,
int viewCount, Date createdDate) {
this.no = no;
this.title = title;
this.content = content;
this.writer = writer;
this.viewCount = viewCount;
this.createdDate = createdDate;
}
@Override
public String toString() {
return "Board [no=" + no + ", title=" + title + ", content=" + content + ", writer=" + writer
+ ", viewCount=" + viewCount + ", createdDate=" + createdDate + "]";
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public int getViewCount() {
return viewCount;
}
public void setViewCount(int viewCount) {
this.viewCount = viewCount;
}
public Date getCreatedDate() {
return createdDate;
}
//@JsonFormat(shape=JsonFormat.Shape.STRING,pattern="yyyy-MM-dd")
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
}
예외 다루기
package bitcamp.app2;
import java.io.IOException;
import java.sql.SQLException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/c06_1")
public class Controller06_1 {
// 테스트:
// http://.../app2/c06_1/get
@GetMapping("get")
public void get() {}
// 테스트:
// http://.../app2/c06_1/post
@PostMapping("post")
public void post() {}
// 테스트:
// http://.../app2/c06_1/error1
@GetMapping("error1")
public void error1() throws Exception {
throw new Exception("request handler 오류 발생!");
// Request Handler에서 예외를 던졌을 때 처리 절차!
// 1) 페이지 컨트롤러 안에 예외 처리기가 있다면,
// => 해당 메서드를 호출한다.
// 2) @ControllerAdvice 객체에 예외 처리기가 있다면,
// => 해당 메서드를 호출한다.
// 3) web.xml 에 지정된 오류 처리 기본 페이지가 설정되어 있다면,
// => 해당 페이지를 실행한다.
// 4) 서블릿 컨테이너의 기본 오류 처리 페이지를 실행한다.
//
}
// 테스트:
// http://.../app2/c06_1/error2
@GetMapping("error2")
public void error() throws Exception {
throw new IOException("request handler 오류 발생!");
}
// 테스트:
// http://.../app2/c06_1/error3
@GetMapping("error3")
public void error3() throws Exception {
throw new SQLException("request handler 오류 발생!");
}
@ExceptionHandler
public ModelAndView exceptionHandler(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex);
mv.setViewName("error6");
return mv;
}
}
예외 다루기
package bitcamp.app2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/c06_2")
public class Controller06_2 {
// 테스트:
// http://.../app2/c06_2/error1
@GetMapping("error1")
public void error1() throws Exception {
throw new Exception("request handler 오류 발생!");
// Request Handler에서 예외를 던졌을 때 처리 절차!
// 1) 페이지 컨트롤러 안에 예외 처리기가 있다면,
// => 해당 메서드를 호출한다.
// 2) @ControllerAdvice 객체에 예외 처리기가 있다면,
// => 해당 메서드를 호출한다.
// 3) web.xml 에 지정된 오류 처리 기본 페이지가 설정되어 있다면,
// => 해당 페이지를 실행한다.
// 4) 서블릿 컨테이너의 기본 오류 처리 페이지를 실행한다.
//
}
}
// 예외 다루기
package bitcamp.app2;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ErrorController {
// 테스트:
// http://.../app2/error
@RequestMapping("/error")
public ModelAndView error(HttpServletRequest request) {
ModelAndView mv = new ModelAndView();
mv.addObject("status", request.getAttribute("javax.servlet.error.status_code"));
mv.addObject("reason", request.getAttribute("javax.servlet.error.message"));
mv.setViewName("error2");
return mv;
}
}
// 모든 페이지 컨트롤러에 적용할 작업을 설정한다.
package bitcamp.app2;
import java.io.IOException;
import java.sql.SQLException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class GlobalControllerAdvice {
// DispatcherServlet은
// request handler가 던지는 예외에 따라
// 그 예외를 받을 수 있는 메서드를 찾아 호출한다.
// 단, @ExceptionHandler 가 선언된 메서드여야 한다.
//
@ExceptionHandler
public ModelAndView exceptionHandler(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex);
mv.setViewName("error3");
return mv;
}
@ExceptionHandler
public ModelAndView ioExceptionHandler(IOException ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex);
mv.setViewName("error4");
return mv;
}
@ExceptionHandler
public ModelAndView sqlExceptionHandler(SQLException ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex);
mv.setViewName("error5");
return mv;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0" metadata-complete="false">
<description>
스프링 Web MVC 프레임워크 예제 테스트
</description>
<display-name>java-spring-webmvc</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>default.htm</welcome-file>
</welcome-file-list>
<!--
서블릿 컨테이너에서 요청을 처리하는 중에 오류가 발생했을 때
오류 내용을 출력하는 기본 페이지를 교체하기
-->
<!-- 1)오류가 발생했을 때 직접 JSP를 실행 -->
<!--
<error-page>
<location>/WEB-INF/jsp2/error1.jsp</location>
</error-page>
-->
<!-- 2)오류가 발생했을 때 페이지 컨트롤러를 경유하여 JSP 실행 -->
<error-page>
<location>/app2/error</location>
</error-page>
</web-app>
build.gradle
build-01.gradle
plugins {
id 'java'
id 'eclipse-wtp'
id 'war'
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8';
}
// 이클립스에서 출력할 프로젝트 이름을 설정한다.
eclipse {
project {
name = "eomcs-spring-webmvc"
}
}
war {
archiveBaseName = "spring"
}
repositories {
mavenCentral()
}
dependencies {
// compileOnly?
// - 프로그래밍(컴파일) 하는 동안에만 사용한다.
// - 배포 파일(.jar, .war, .ear)에는 포함하지 않는다.
// - 프로그램이 배치되는 런타입 서버(예: 실행 중인 톰캣 서버)에서
// 라이브러리를 제공하는 경우 굳이 배포할 필요가 없기 때문에
// 이 옵션을 사용한다.
// => Servlet API 라이브러리
//compileOnly 'javax.servlet:javax.servlet-api:4.0.1'
// providedCompile?
// - compileOnly 처럼 컴파일 할 때만 사용한다.
// - 배포 파일에는 포함하지 않는다.
// - 단 이 옵션은 'war' 플러그인을 사용할 때만 설정할 수 있다.
providedCompile 'javax.servlet:javax.servlet-api:4.0.1'
// implementation?
// - 컴파일 할 때 사용한다.
// - 배포 파일에도 포함한다.
// => JSTL 명세를 구현한 라이브러리
implementation 'javax.servlet:jstl:1.2'
implementation 'com.google.guava:guava:28.2-jre'
// testImplementation?
// - 단위 테스트를 수행할 때만 사용한다.
// - 배포 파일(.jar, .war, ear)에 포함되지 않는다.
testImplementation 'junit:junit:4.12'
}
build-08.gradle
plugins {
id 'java'
id 'eclipse-wtp'
id 'war'
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8';
}
// 이클립스에서 출력할 프로젝트 이름을 설정한다.
eclipse {
project {
name = "eomcs-spring-webmvc"
}
}
war {
archiveBaseName = "spring"
}
repositories {
mavenCentral()
}
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'javax.servlet:jstl:1.2'
implementation 'org.springframework:spring-webmvc:5.3.7'
implementation 'org.apache.logging.log4j:log4j-core:2.14.1'
// gradle을 통해 자동으로 다운로드 받은 라이브러리가 아니라,
// 별도로 다운로드 받은 라이브러리 파일을 프로젝트 포함시키는 방법.
// implementation fileTree(
// dir: '현재 프로젝트 폴더를 기준으로 한 상대 경로 또는 절대 경로',
// include: '프로젝트에 포함한 라이브러리 파일들'
//
implementation fileTree(dir: 'lib', include: '*.jar')
implementation 'com.google.guava:guava:28.2-jre'
testImplementation 'junit:junit:4.12'
}
build-14.gradle
plugins {
id 'java'
id 'eclipse-wtp'
id 'war'
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8';
}
// 이클립스에서 출력할 프로젝트 이름을 설정한다.
eclipse {
project {
name = "eomcs-spring-webmvc"
}
}
war {
archiveBaseName = "spring"
}
repositories {
mavenCentral()
}
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'javax.servlet:jstl:1.2'
implementation 'org.springframework:spring-webmvc:5.3.7'
implementation 'org.apache.logging.log4j:log4j-core:2.14.1'
// 파일 업로드 처리
implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.4'
// JSON 형식을 다루는 라이브러리
// @Controller가 붙은 일반적인 페이지 컨트롤러의 요청 핸들러를 실행할 때
// 요청 파라미터의 문자열을 int나 boolean 등으로 바꾸기 위해
// 기본으로 장착된 변환기를 사용한다.
// 그 변환기는 HttpMessageConverter 규칙에 따라 만든 변환기이다.
//
// 또한 요청 핸들러가 리턴한 값을 문자열로 만들어 클라이언트로 출력할 때도
// 이 HttpMessageConverter를 사용한다.
// 즉 클라인트가 보낸 파라미터 값을 핸들러의 아규먼트 타입으로 바꿀 때도 이 변환기를 사용하고
// 핸들러의 리턴 값을 클라이언트로 보내기 위해 문자열로 바꿀 때도 이 변환기를 사용한다.
//
// 스프링이 사용하는 기본 데이터 변환기는 MappingJackson2HttpMessageConverter 이다.
// 만약 이 변환기가 없다면 Google의 Gson 변환기를 사용한다.
// 구글의 Gson 변환기 마저 없다면 컨버터가 없다는 예외를 발생시킨다.
// 컨버터가 하는 일은 JSON 데이터로 변환하는 것이다.
// 클라이언트가 보낸 JSON 요청 파라미터 ===> 자바 객체
// 핸들러가 리턴하는 자바 객체 ===> JSON 형식의 문자열
//
// MappingJackson2HttpMessageConverter?
// => 요청 파라미터로 JSON 문자열을 받으면 요청 핸들러를 호출할 때 자바 객체로 변환시킨다.
// => 요청 핸들러가 자바 객체를 리턴할 때 JSON 문자열로 변환한다.
//
// 주의!
// => MappingJackson2HttpMessageConverter를 사용하려면
// 다음과 같이 의존하는 라이브러리를 추가해야 한다.
//
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
// => 그런데 JSON 데이터를 처리할 때
// MappingJackson2HttpMessageConverter 대신 GsonHttpMessageConverter 를 사용할 수 있다.
// 단 GsonHttpMessageConverter를 사용하려면
// 다음과 같이 이 클래스가 들어있는 의존 라이브러리를 추가해야 한다.
// => 만약 동시에 추가한다면 기본으로 Jackson 라이브러리를 사용한다.
//
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
implementation 'com.google.guava:guava:28.2-jre'
testImplementation 'junit:junit:4.12'
}
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Spring Framework, Spring Boot' 카테고리의 다른 글
[비트캠프] 83일차(18주차1일) - Spring Framework(Thymeleaf), myapp-62 (0) | 2023.03.06 |
---|---|
[비트캠프] 82일차(17주차4일) - Spring Framework(Spring WebMVC 아키텍처, Tiles), myapp-60-3, 61, 프로젝트 절차(전체 과정) (0) | 2023.03.03 |
[비트캠프] 81일차(17주차3일) - Spring Framework: myapp-60-1, 2 (0) | 2023.03.02 |
[Java] 예제 소스 정리 - Spring IoC (0) | 2023.03.01 |
[비트캠프] 80일차(17주차2일) - Spring Framework(Spring IoC 컨테이너) (0) | 2023.02.28 |