1. Front Controller : 230220
1) 이전 시간 복습
-
서블릿을 통한 입출력은 request와 response 밖에 없다.
-
이전 시간에는 서블릿을 통해서 입력 받는 방법을 배움.
- 앞으로는 기존 컨트롤러에서 입력부분과 Dispatcher 부분이 잘려나가고(서블릿 부분이 잘려나간다.) 순수 자바코드인 POJO만 남는다.
- 잘려진 부분은 클라이언트 쪽으로 보낸다.
2) POST 전송 방식(GET 요청과 비교!)
- 중요! :
- GET 요청 : 클라이언트가 서버에게 데이터를 달라고 요청하는 것(데이터를 조회하는 것과 같다. = 검색할 때, 이용)
- POST 요청 : 클라이언트가 서버에게 이러한 데이터를 줄테니까 등록해달라는 것이다.(= 회원 등록할 때, 이용)
- 하지만, 애매한 조회는 POST 요청이 가능하다.(JSON 타입으로 데이터를 조회하는 경우)
- POST 요청 구분 :
- 데이터가 클 때, 한 번에 많은 데이터를 전송할 때, POST 방식으로 보낸다. 기입할 때, form 방식으로 제출하고 제출하는데 이 때, POST 요청으로 보낸다.
- e.g. 검색은 GET 요청이다. // 회원 가입은 값을 달라는 것이 아니라 기입만 하는 것이라서 POST 방식이다.
- 애매하게 값을 달라고하면서 기입하는 데이터가 많은 경우에 POST 전송으로 보낸다.
- GET 요청과 구분하기!
- POST 전송 특징 :
- 크롬에서 키보드 중 F12를 눌러 개발자 모드에서 요청 header가 아니라 요청 body에 데이터가 들어가서 URL에서 데이터가 보여지지 않는다.
<form action="/webprj2/input" method="post">
<fieldset>
<legend>쿼리 값</legend>
<label>page:</label>
<input type= "text" name="p"><br>
<label>검색어1:</label>
<input type= "text" name="q1"><br>
<label>검색어2:</label>
<input type= "text" name="q2"><br>
<label>검색어3:</label>
<input type= "text" name="q3"><br>
<label>size:</label>
<input type= "text" name="s"><br>
</fieldset>
<fieldset>
<legend>취미</legend>
<input type="checkbox" name="hb" value="1">
<label>탁구</label>
<input type="checkbox" name="hb" value="2">
<label>독서</label>
<input type="checkbox" name="hb" value="3">
<label>술</label>
</fieldset>
<input type= "submit" value="제출">
</form>
-
쿼리스트링은 사용자가 선택하는 값이다.
-
사용자가 입력하는 경우, form을 이용한 쿼리스트링 입력이다. GET 방식
-
Post 방식은 form을 이용할 때, 사용자가 입력한 값이 매우 클 경우, article 같은 경우! POST를 사용한다.(보안과는 별개다)
3) 쿠키(Cookie), 세션
-
서버가 사용자에게 주는 데이터이다. 새로운 사용자로 인식하지 않게 한다. 기존 사용자로 인식하게 해준다.
-
쿠키는 상태값을 저장하는 경우에 사용한다.
- 웹의 저장소 : Request, Session, Application
- Session, Application은 서블릿이 사라져도 데이터가 남아 있다. 서버에 저장된다.
- forward 관계에서 종료되면 데이터는 같이 사라진다.
- 3-1. 세션
- 서버에 사용자 데이터를 저장하는 공간(기간을 말한다.)
- 세션은 사전적인 의미로 허용된 사용 기간을 말한다.
- 웹에서는 커넥션이 없어서 연결 유지가 안 될때, 세션을 사용한다.
- 세션 기간은 기본 30분으로 설정되어 있다.
- 세션 타임아웃되기 전에 그 회원에게 세션 저장소(객체)에 세션 키를 저장해놓는다.
- 첫 번째 서버 요청에는 세션키가 없다. 2번째 서버 요청 이후부터 세션키가 발급된다.
- html은 정적인 파일이라서 세션을 부여해줄 필요가 없다.
- 세션 정리** : 사용자가 누구인지 상관하지 않는다. 허락을 하지 않아도 된다.(쿠키와 다르다.)
- 3-2. 세션 객체
- 아래 코드의 ListController4에서 서블릿 세션을 부여하고 ListController2에서 서블릿이 그 세션을 공유하여 출력한다.
- 서블릿들이 공유하여 사용한다.
- 요청자들에 준하여 세션을 공유한다.
- 요청자가 다르면, 세션 공간을 다르게 공유한다.
- 실습 코드 :
- ListController4.java
package com.newlecture.web.controller.menu;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import com.newlecture.web.entity.Menu;
import com.newlecture.web.service.DefaultMenuService;
import com.newlecture.web.service.MenuService;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@WebServlet("/menu/list4") //존재하지않는 리소스 요청시 404에러
public class ListController4 extends HttpServlet{
private MenuService service;
public ListController4() {
service= new DefaultMenuService();
}
@Override // doGet 뿐만 아니라 doDelete, doPut, doPost, doHead 등이 있다.
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
req.getParameter("c");
PrintWriter out = resp.getWriter();
// List<Menu> menus = service.getList();
List<Menu> menus = new ArrayList<>();
//세션으로 보내기
HttpSession session = req.getSession();
session.setAttribute("haha", "hoho");
// 내용을 전달
req.setAttribute("menus", menus);
// 포워딩 방식
req
.getRequestDispatcher("/WEB-INF/view/menu/list.jsp")
.forward(req, resp);
}
}
- ListController2.java
package com.newlecture.web.controller.menu;
import java.io.IOException;
import java.io.PrintWriter;
import com.newlecture.web.service.MenuService;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@WebServlet("/menu/list2")
public class ListController2 extends HttpServlet {
private MenuService service;
@Override // doGet 뿐만 아니라 doDelete, doPut, doPost, doHead 등이 있다.
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
resp.setContentType("text/html; charset=utf-8");
// 서블릿은 메인함수 직접 작성하지 않고입출력 도구가 달라짐!
HttpSession session = req.getSession();
String haha = (String) session.getAttribute("haha");
System.out.println(haha);
req.getRequestDispatcher("/WEB-INF/view/menu/list.jsp").forward(req, resp);
}
}
- 3-3. 쿠키
- 클라이언트에 저장하는 사용자 데이터
- 서버에 부담도 줄여준다.
- 쿠키 특징 :
- 1) 쿠키는 기간도 설정 가능 : 브라우저를 닫아도 쿠키를 기록할 수 있고 쿠키를 가져갈 수 있다.(1년 or 10년 기간 설정 가능)
- setMaxAge : 분 단위로 쿠키 시간 설정 가능
- 2) 원하는 경로 설정 가능
- setPath(“/”) : 경로 설정 가능!
- 추가 : 쿠키는 문자열만 저장할 수 있다. 그 외의 값들은 JSON이라는 형태로 바꿔서 url로 사용할 수 있는 방식으로 인코딩해서 넣어줘야 한다.
- 실습 코드 :
- ListController4.java
package com.newlecture.web.controller.menu;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import com.newlecture.web.entity.Menu;
import com.newlecture.web.service.DefaultMenuService;
import com.newlecture.web.service.MenuService;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@WebServlet("/menu/list4") //존재하지않는 리소스 요청시 404에러
public class ListController4 extends HttpServlet{
private MenuService service;
public ListController4() {
service= new DefaultMenuService();
}
@Override // doGet 뿐만 아니라 doDelete, doPut, doPost, doHead 등이 있다.
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
req.getParameter("c");
PrintWriter out = resp.getWriter();
// List<Menu> menus = service.getList();
List<Menu> menus = new ArrayList<>();
//세션으로 보내기
// HttpSession session = req.getSession();
// session.setAttribute("haha", "hoho");
//쿠키 만들어 보내기
Cookie cookie = new Cookie("haha", "hoho");
cookie.setPath("/");
cookie.setMaxAge(30);
resp.addCookie(cookie);
// 내용을 전달
req.setAttribute("menus", menus);
// 포워딩 방식
req
.getRequestDispatcher("/WEB-INF/view/menu/list.jsp")
.forward(req, resp);
}
}
2. 쿠키, Front-Controller, 리플렉션 : 230221
1) 쿠키 : 원하는 쿠키를 사용
- 원하는 쿠키를 사용하기 위해서 조건 처리를 해줘야 한다.
-
원래, 쿠키가 있는지 없는지 for-each 구문으로 파악해서 쿠키가 있을 때만 쿠키를 출력해줘야 한다**
- 코드 요약!
ListController2.java
package com.newlecture.web.controller.menu;
import java.io.IOException;
import java.io.PrintWriter;
import com.newlecture.web.service.MenuService;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@WebServlet("/menu/list2")
public class ListController2 extends HttpServlet {
private MenuService service;
@Override // doGet 뿐만 아니라 doDelete, doPut, doPost, doHead 등이 있다.
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//제네릭
//GList<Menu> menus = new GList<Menu>();
//제일 많이 쓰는 패턴
PrintWriter out = resp.getWriter();
// List<Menu> menus = new ArrayList<>();
// 한글로 출력하고싶을때. 근데도 깨진다?
// resp.setCharacterEncoding("UTF-8");
// 그럼 이렇게 해주면됨
resp.setContentType("text/html; charset=utf-8");
// 서블릿은 메인함수 직접 작성하지 않고입출력 도구가 달라짐!
HttpSession session = req.getSession();
String haha = (String) session.getAttribute("haha");
System.out.println(haha);
// null 출력! list4에서 넘겨 받은 session이 없다.
// 나중에는 쿠키 조건 검사를 스프링이 알아서 해줄 것이다.
Cookie[] cookies = req.getCookies();
for(Cookie cookie :cookies)
if(cookie.getName().equals("haha")) {
System.out.println(cookie.getValue()); // hoho 출력
break;
}
//내용을 전달
// req.setAttribute("menus", menus);
//리디렉션 방식
//resp.sendRedirect("listview");
//포워딩 방식
req.getRequestDispatcher("/WEB-INF/view/menu/list.jsp").forward(req, resp);
}
}
2-1) Front-Controller(Dispatcher)
-
하는일이 Dispatch라서 Dispatcher라고도 부른다.
-
코드 요약 :
초기 JSPDispatcherServlet.java 코드
package com.newlecture.web.controller;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/*")
public class JSPDispatcherServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.write("Hello Front");
}
}
2-2) FrontController에서 앞으로 할 것 :
-
/menu/list
요청이 오면 Listcontroller의 requestHandler()를 호출하고 -
/menu/detail
요청이 오면 DetailController의 requestHandle()를 호출한다.
초기 JSPDispatcherServlet.java 코드
package com.newlecture.web.controller;
import java.io.IOException;
import java.io.PrintWriter;
import com.newlecture.web.controller.menu.DetailPOJOController;
import com.newlecture.web.controller.menu.ListPOJOController5;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
// 바꿔주기
@WebServlet("/")
public class JSPDispatcherServlet extends HttpServlet{
// 반복하기위해서 배열에 넣어준다.
String[] urls = {"/webprj2/menu/list", "/webprj2/menu/detail"};
String[] controllers = {
"com.newlecture.web.controller.menu.ListPOJOController5",
"com.newlecture.web.controller.menu.DetailPOJOController"
};
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
String uri = request.getRequestURI();
String url = request.getRequestURL().toString();
String viewSrc = "/WEB-INF/view/notfound.jsp";
out.printf("uri:%s\n", uri);
out.printf("url:%s\n", url);
// 방법 2: 컴퓨터가 반복하는 코드
while(uri.equals("/webprj2/menu/list"))
viewSrc = new ListPOJOController5().requestHandler();
for(int i=0; i<urls.length; i++) {
?? controller = null;
if(uri.equals(urls[i])) {
controller = (??) Class.forName(controllers[i]).newInstance();
controller.requestHandler(); // 우리는 controller를 포함하는 가시 형식 필요하는 공통 형식이 필요하다!
// 개체 정보를 다 찾아서 함수 호출을 찾아서 다 꺼내서 사용할 수 있다.
// 자바 '리플렉션'이라는 개념이 필요하다!
}
}
// 방법 1:
// 디스패처가 해당 url과 일치하면, 해당 POJO 컨트롤러를 가져온다.
// 컨텍스트 path가 있는 경우, 위에서 출력된 uri 경로를 여기에 그대로 넣어줘서 비교한다!!**
// uri는 url에서 localhost를 뺀 나머지의 경로이다.
if(uri.equals("/webprj2/menu/list"))
viewSrc = new ListPOJOController5().requestHandler();
if(uri.equals("/webprj2/menu/detail"))
viewSrc = new DetailPOJOController().requestHandler();
out.write("Hello Front");
request.getRequestDispatcher(viewSrc)
.forward(request, response);
// 앞으로 할 것!
// /menu/list 요청이 오면 Listcontroller의 requestHandler()를 호출하고
// /menu/detail 요청이 오면 DetailController의 requestHandle()를 호출한다.
}
}
ListPOJOController5.java
package com.newlecture.web.controller.menu;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import com.newlecture.web.entity.Menu;
import com.newlecture.web.service.DefaultMenuService;
import com.newlecture.web.service.MenuService;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
public class ListPOJOController5{
private MenuService service;
public ListPOJOController5() {
service= new DefaultMenuService();
}
// 순수 컨트롤러 역할을 해준다.
public String requestHandler(String id, String query){
return "/WEB-INF/view/menu/list.jsp";
}
}
DetailPOJOController.java
package com.newlecture.web.controller.menu;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import com.newlecture.web.entity.Menu;
import com.newlecture.web.service.DefaultMenuService;
import com.newlecture.web.service.MenuService;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
// /menu/detail 에서 url이 열린다.
public class DetailPOJOController{
private MenuService service;
public DetailPOJOController() {
service= new DefaultMenuService();
}
// 순수 컨트롤러역할을 해준다.
public String requestHandler(){
return "/WEB-INF/view/menu/detail.jsp";
}
}
3-1) 자바 리플렉션 개념 & 스프링에서 리플렉션이 필요한 이유**
a. 개념 1 : 알 수 없는 객체의 클래스 정보를 접근할 수 있는 메서드와 속성을 제공한다.(중요!!)**
- 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
- 자바에서 제공하는 리플렉션(Reflection)은 C, C++과 같은 언어를 비롯한 다른 언어에서는 볼 수 없는 기능이다. 이미 로딩이 완료된 클래스에서 또 다른 클래스를 동적으로 로딩(Dynamic Loading)하여 생성자(Constructor), 멤버 필드(Member Variables) 그리고 멤버 메서드(Member Method) 등을 사용할 수 있도록 한다.
- java Reflection이 가져올 수 없는 정보 중 하나가 바로 생성자의 인자 정보들이다. 따라서 기본 생성자 없이 파라미터가 있는 생성자만 존재한다면 java Reflection이 객체를 생성할 수 없게 되는 것이다.**
a-1) 의문점 1** :
- 여기서
구체적인 클래스 타입을 알지 못해도
가 무슨 말인지 이해할 수 없었다. ‘자신이 작성한 코드인데 어떻게 클래스 타입을 모를수가 있을까 ?’라고 생각했기 때문이다. - 우리가 이렇게 music이라는 객체를 생성했지만 Music클래스에서 제공하는 getTitle()과 getSinger() 메소드를 사용할 수 없다.
public class Reflection {
public static void main(String[] args) {
Object music = new Music( singer: "IU", title: "YOU AND ME");
music.~~
}
}
- 자바는 정적 언어로 컴파일 시점에 타입을 결정한다. 따라서 컴파일을 진행할때 music이라는 객체의 타입을 Object로 결정한 것이다.
- 컴파일 시점에 타입을 결정하는 정적 언어 : Java, C, C++ 등등
- 런타임 시점에 타입을 결정하는 동적 언어 : JavaScript, Ruby 등등
컴파일 타임 : 작성한 코드를 기계어로 변환하여 실행가능한 프로그램으로 변환시키는 과정을 컴파일이라 하며, 이 과정동안 일어나는 시간을 컴파일타임 이라고 한다.
런타임 : 컴파일 과정을 마친 프로그램은 사용자에 의해 실행되어 지며, 이러한 응용프로그램이 동작되는 때를 런타임(Runtime) 이라고 부른다.
- 의문점의 결과 :
- 즉 music이라는 객체는 자신이 Object타입이라는 사실만 알 뿐, Music 클래스에 대해서는 전혀 알지 못한다.
- 이제 위에서 설명한 구체적인 클래스 타입을 알지 못해도 의 의미를 어느정도 파악할 수 있게 되었다.
- 이제 Java Reflection API를 이용해서 구체적인 클래스 타입을 알지 못하지만 그 클래스의 메소드에 접근을 해보자.
b. 개념 2** :
- 그래서, 아래 실습 코드에서도
ListPOJOController5
클래스에서 리플렉션 접근할 때도 기본 생성자의 모양은 있었지만 생성자에 인자가 없었기 때문에 완전한 기본 생성자는 없었다. 하지만, 원래 리플렉션의 기본 스펙에 따라서, 기본 생성자는 개발자가 꼭 만들었어야만 했다.**
public class ListPOJOController5{
private int x = 1;
private int y = 3;
private MenuService service;
public ListPOJOController5() {
service= new DefaultMenuService();
}
// 순수 컨트롤러 역할을 해준다.
// 일반적인 함수로 호출할 것이다. 사용자 정보가 있거나 없거나?에 따라 인자 개수가 달라진다.
// 사용자가 나중에 입력할 새로운 파라미터 인자 추가!
public String requestHandler(String id, String query){
return "/WEB-INF/view/menu/list.jsp";
}
public int add(int x, int y) {
return 20+x;
}
}
- 의문점 : 그래서 기본 생성자가 꼭 필요한다. 그런데, 생성자의 인자 정보를 못 가져오면 인자가 있는 생성자로 객체 생성을 어떻게 하지?**
- Spring Data JPA 에서 Entity에 기본 생성자가 필요한 이유도 동적으로 객체 생성 시 Reflection API를 활용하기 때문이다. Reflection API로 가져올 수 없는 정보 중 하나가 생성자의 인자 정보이다. 그래서 기본 생성자가 반드시 있어야 객체를 생성할 수 있는 것이다. 기본 생성자로 객체를 생성만 하면 필드 값 등은 Reflection API로 넣어줄 수 있다.**
- 결론 : 기본 생성자로 객체를 생성하고, 만들어진 기본 생성자로 클래스의 필드 정보를 가져올 수 있으니까 객체 생성 후에 필드 값을 할당할 수 있다.**
b-1) 의문점 2 :
JPA는 Entity로 사용할 객체에 반드시 기본 생성자가 있어야 합니다.
의 이유를 정리할 차례다.- java Reflection이 가져올 수 없는 정보 중 하나가 바로 생성자의 인자 정보들 이다. 따라서, 기본 생성자 없이 파라미터가 있는 생성자만 존재한다면 java Reflection이 객체를 생성할 수 없게 되는 것이다.
- 문제점 : JPA 이용 시, 기본 생성자가 없어도 코드가 동작하는 경우도 있었다.**
@Getter @Setter
@Entity
public class Member {
@Id
private Long id;
private String name;
public Member(Long id, String name){
this.id = id;
this.name = name;
}
}
- 기본 생성자가 없어도 코드가 동작하는 이유 : JPA의 기본 스펙은 엔티티에 기본 생성자가 필수로 있어야 합니다. 그런데 하이버네이트 같은 구현체들은 조금 더 유연하게 바이트코드를 조작하는 라이브러리등을 통해서 이런 문제를 회피합니다. 하지만 이런 것은 완벽한 해결책이 아 니므로, 상황에 따라 될 때도 있고 되지 않을 때도 있습니다. 따라서 JPA 스펙에서 가이드한 것 처럼 기본생성자를 꼭 넣어주세요.(김영한님 답변)
b-2) 의문점 해결 :
- 하이버네이트는 내부적으로 Class.newInstance()라는 리플렉션을 이용해 해당 Entity의 기본 생성자를 호출해서 객체를 생성하게 되는데, 이 리플렉션은 생성자의 매개변수를 읽을 수가 없어서 반드시 기본 생성자를 정의해 줘야 합니다.
- 항상 기본 생성자를 명시적으로 정의해야 하는 것은 아니고 만약 아래 예시처럼 인자가 있는 생성자를 정의했을 때만 기본 생성자를 생성해주면 됩니다.**
// 이렇게 생성자에 인자가 있는 경우 생성자를 생성해주어야 한다.
public User(String name) {
this.name = name;
}
- 중요!! : 만약 인자가 있는 생성자를 정의하지 않았다면 자바에서 자동으로 기본 생성자를 생성하기 때문에 따로 명시적으로 기본 생성자를 정의하지 않아도 됩니다.**
c. 개념 3** :
- 불변성 보장 테스트 :
ObjectMapper를 위한 기본 생성자를 사용할 것인지
에 대한 문제에서 인수 테스트까지 생각하면 아무래도 불변성 보장은 힘들 것 같다. 왜? Reflection은 기본 생성자가 필요하기 때문이다! 따라서, private로 빈 객체 생성를 막는 게 최선이라 생각한다. 왜? Reflection은 접근 제어자와 상관 없기 때문이다!
d. 추가 개념 정리** :
- 보통 리플렉션은 스프링의 빈 객체을 이용하거나 JPA에서 기본 생성자가 필요한 이유에서 리플렉션 개념이 사용한다. 추가로 사용자로부터 입력받은 데이터를 파라미터 인자로 인지해서 그 해당 객체의 메서드로 정보를 받아 들여서 메서드 정보(반환 타입, 접근자), 파라미터 정보나 생성자 정보를 디버깅이나 테스트 프레임워크에 이용한다.
e. 리플렉션 사용 방법 :
- 자바 리플렉션은 주로 디버깅, JUnit과 같은 테스트 프레임워크에서 런타임에 알 수 없는 객체의 동작 과정을 분석하기 위해서 사용한다.
f. 리플렉션의 사용도 :
- 이렇게 복잡한 Java Reflection을 우리가 코딩을 하면서 직접 사용하는 일은 극히 드물다. 우리가 작성한 코드의 구체적인 클래스를 모를 일이 거의 없기 때문이다.
- 대부분 프레임워크나 라이브러리 등 사용하는 사람이 어떤 클래스를 만들지 모르는 경우 리플렉션을 통해 동적으로 이 문제를 해결하기 위해 많이 사용된다.
- 또한, Java Reflection은 몇가지 큰 단점들을 가지고 있다.
g. 리플렉션 단점 :
- 성능 오버헤드(Performance Overhead): Reflection은 유용한 기능입니다. 하지만 런타임에 Reflection API를 사용하여 알 수 없는 객체를 처리하는 과정은 성능적으로 좋지 않습니다. 따라서 Reflection을 사용하지 않는 방향으로 개발해야 합니다.
- 보안 제한(Security Restrictions): Reflection은 컴파일 타임이 아닌 런타임 기능이므로 런타임 권한이 필요할 수 있습니다. 따라서 보안 때문에 Reflection를 사용한 소스코드가 실행할 수 없는 경우 무용지물이 될 수 있습니다.
- 내부 노출(Exposure of Internals): Reflection을 사용하여 클래스의 메서드와 필드를 접근할 수 있습니다. 코드를 이식하지 않아도 되는 장점은 객체 지향 프로그래밍의 특징인 추상화를 위반합니다.
h. 결론 :
- Reflection API는 런타임에 알 수 없는 객체를 처리할 수 있다는 장점이 있습니다. 하지만 장점에 비해 치명적인 문제들이 존재하므로 웬만하면 Reflection API를 사용하지 않는 것이 좋습니다.
i. 코드 요약 :
ReflectProgram.java
package com.newlecture.web;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import com.newlecture.web.controller.menu.ListPOJOController5;
public class ReflectProgram {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException {
Object controller = Class.forName("com.newlecture.web.controller.menu.ListPOJOController5")
.getDeclaredConstructor()
.newInstance();
// 아래 3가지 방식은 클래스 정보 얻는 방법이 모두 같다.
// 개체명(문자열)에서 클래스 정보 얻기
Class clsInfo = Class.forName("com.newlecture.web.controller.menu.ListPOJOController5");
// 개체(class)에서 클래스 정보 얻기 : 멤버가 누구인지, 어노테이션이 무엇인지
Class clsInfo1 = ListPOJOController5.class;
// 객체에서 클래스 정보 얻기
Class clsInfo2 = controller.getClass();
// 메서드 정보 가져오기
// 접근자가 상관없이 가져온다.
Method[] methods = clsInfo.getDeclaredMethods();
Method method = null;
for(Method m: methods) {
// System.out.println(m.getName()); // Method 객체는 만들어져 있는 메서드 정보를 얻을 수 있다.
// 리플렉션은 라이브러리를 만드는 사람이 자주 쓴다.
if(m.getName().equals("add"))
method = m;
}
// method.getParameterCount();
Parameter[] params = method.getParameters();
for(Parameter p : params)
System.out.printf("params: %s\n", p.getType().toString());
// if(params.length>2)
// params[2].getType(); // 해당 조건을 만족하는 params가 있다면, invoke인자에 추가로 호출될 수 있다.
Object[] objs = null;
// if(파라미터가 1개가 있다면)
// objs = 그 정수를 값에 대입해서 넘겨준다.
// if(파라미터가 2개가 있다면 = 메서드의 정보를 얻어와서 파라미터가 몇 개인지 인지한다)
// objs = 그 정수를 값에 대입해서 넘겨준다.
// 객체가 method에 들어간다. 리플렉션은 어떠한 객체의 정보를 꺼내서 쓸 수 있다.
// 원래는 메서드의 접근자가 무엇이든 상관이 없지만, invoke로 실제 불러올 때는 접근자를 public으로 해주어야 한다.
int result = (int) method.invoke(controller, 3, 4);
System.out.printf("result: %d\n", result);
// 바로 위의 코드에서 덧셈이 되는 이유? 바로 아래 코드처럼 오토박싱이 이유가 된다.
// Object x = 3;
// int y = (int) x;
}
}
j. 리플렉션 참고 사이트 모음 :
- 리플렉션 참고 사이트1 : 객체 타입을 왜 모르는지?, 리플렉션이 사용되는 JPA
- 리플렉션 참고 사이트2 : 리플렉션 활용 시, 기본 생성자가 필요한 이유
- 리플렉션 참고 사이트3 : 자바의 원래 개념
3-2) 컨트롤러에서 자바 리플렉션 실습!!
JSPDispatcherServlet.java
package com.newlecture.web.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import com.newlecture.web.controller.menu.DetailPOJOController;
import com.newlecture.web.controller.menu.ListPOJOController5;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
// 바꿔주기
@WebServlet("/")
public class JSPDispatcherServlet extends HttpServlet{
// 반복하기위해서 배열에 넣어준다.
String[] urls = {"/webprj2/menu/list", "/webprj2/menu/detail"};
String[] controllers = {
"com.newlecture.web.controller.menu.ListPOJOController5",
"com.newlecture.web.controller.menu.DetailPOJOController"
};
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
String uri = request.getRequestURI();
// String url = request.getRequestURL().toString();
String viewSrc = "/WEB-INF/view/notfound.jsp";
try {
Object controller = Class.forName("com.newlecture.web.controller.menu.ListPOJOController5")
.getDeclaredConstructor()
.newInstance();
// 아래 3가지 방식은 클래스 정보 얻는 방법이 모두 같다.
// 개체명(문자열)에서 클래스 정보 얻기
Class clsInfo = Class.forName("com.newlecture.web.controller.menu.ListPOJOController5");
// 개체(class)에서 클래스 정보 얻기 : 멤버가 누구인지, 어노테이션이 무엇인지
Class clsInfo1 = ListPOJOController5.class;
// 객체에서 클래스 정보 얻기
Class clsInfo2 = controller.getClass();
// 메서드 정보 가져오기
// 접근자가 상관없이 가져온다.
Method[] methods = clsInfo.getDeclaredMethods();
Method method = null;
for(Method m: methods) {
if(m.getName().equals("requestHandler")) // requesthandler의 정보를 가져온다.
method = m;
}
// 호출하기 위해서는 Object 객체를만들어줘서 argument를 채우는 작업을 시작한다!!
Object[] args = new Object[2];
// 사용자로부터 파라미터 값이 2개 오면, 어느 순서로 Object 객체에 담아 주어야 하는가?
Parameter[] params = method.getParameters();
for(Parameter p : params) {
System.out.printf("param : %s\n", p.getType().toString());
System.out.printf("name : %s\n", p.getName());
}
// request에 파라미터를 매핑시켜준다.
request.getParameterMap();
viewSrc= (String) method.invoke(controller, args);
// 파라미터 개수
int paramSize = method.getParameterCount();
System.out.printf("param size: %d\n", paramSize);
// 보통 쿼리스트링을 사용자가 입력해서 넘겨진 데이터를 확인하려고 사용한다.
// 사용자가 파라미터를 몇개를 원하는지 확인하려고 사용한다!
// Parameter[] params = method.getParameters();
//
// for(Parameter p : params)
// System.out.printf("params: %s\n", p.getType().toString());
//
// Object[] objs = null;
//
// int result = (int) method.invoke(controller, 3, 4);
// System.out.printf("result: %d\n", result);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
ListPOJOController.java
package com.newlecture.web.controller.menu;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import com.newlecture.web.entity.Menu;
import com.newlecture.web.service.DefaultMenuService;
import com.newlecture.web.service.MenuService;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
public class ListPOJOController5{
private int x = 1;
private int y = 3;
private MenuService service;
public ListPOJOController5() {
service= new DefaultMenuService();
}
// 순수 컨트롤러 역할을 해준다.
// 일반적인 함수로 호출할 것이다. 사용자 정보가 있거나 없거나?에 따라 인자 개수가 달라진다.
// 사용자가 나중에 입력할 새로운 파라미터 인자 추가!
public String requestHandler(String id, String query){
return "/WEB-INF/view/menu/list.jsp";
}
public int add(int x, int y) {
return 20+x;
}
}
3-3) 컨트롤러에서 리플렉션 실습 결과
param : class java.lang.String
name : arg0
param : class java.lang.String
name : arg1
param size: 2
3. 스프링 강의 : 230222
- 이클립스의 마켓플레이스는 스프링의 도구만 제공해주고 라이브러리는 아니다. 그래서 maven repository에서 다운받는다.
1) Maven :
Maven
은 빌드 도구의 일종이다.이클립스
는 메이븐보다 더 큰 개념인 IDE이다.Maven
은 모든 IDE에서 사용할 수 있는Maven 프로젝트
이다.- 추가로 Gradle이 있다. 안드로이드 앱 만들 때만 이용한다.
- 2차 프로젝트에서
Gradle
를 쓸 예정이다. - 형상관리 도구는
Git
라는 도구가 있다. - 테스트 도구는
JUnit
이 있다.
a. Maven 특징
- Maven은 라이브러리 관리가 가능하다.
- 남이 만들어 놓은 프로젝트를 이어서 만들 수 있다.
- archetype, groupId, artifactId, archetypeArtifacteId 설정
b. Maven 수동 생성 및 실행
- 만들고 싶은 폴더에서 maven 프로젝트 생성해주기
- 메이븐 프로젝트를 수동으로 만들 때, 스냅샷 버전을 물어보면 엔터를 쳐서 버전 이상 없다고 알려주기
- 스냅샷은 버전이 확정되지 않은 상태라서 아직 개발중인 프로젝트를 의미한다.
-
mvn compile : 메이븐 프로젝트 컴파일 해주기
-
mvn test : 나중에 단위 테스트 진행(target이라는 폴더가 새로 생긴다.)
-
mvn package : 배포하려고 해당 명령어를 사용하고 사용하면, 배포 파일인 jar 파일이 생김.
c. Maven 라이프 사이클 :
- Phase를 바꿔낄 수 있게 할 떄, 라이프 사이클이 필요하다.
- 모든 phase를 거쳐서 메이븐 프로젝트가 만들어진다. 하지만, 보통 필요한 부분만 만들어져서 사용한다.
- 서블릿에서도 라이프 사이클이 존재했었다.
- 따라서, 우리는 jar, war를 이용한다.
- Plug-in : 보통 꽂아 넣을 때는 플러그인에 꽂아서 사용한다.(하나의 객체이다.)
- Goal : 컴파일을 갖고 있는 기능과 테스트컴파일 기능을 갖고 있는 기능을 각각 Goal이라고 한다.
- 정리** : 우리는 이러한 기능(Goal)에 알맞은 Plug-in들을 꽂아서 전체적으로는 Phase단계로 실행한다.
d. Maven 자동 생성 및 실행(이클립스에서 생성)
- 이클립스에서 메이븐 프로젝트로 이전에 만든 프로젝트 가져오기. 또는, 이클립스에서 메이븐프로젝트로 바로 만들 수 있다!
- 메이븐 프로젝트에서는 pom.xml이 중요하다. jar를 war로 바꿔서 웹프로젝트로 바꿔준다.
- 또한, 또 다른, plugin을 추가해주기
- dependencies 태그 안에 dependency 태그에 spring 라이브러리를 넣어주고 관련된 라이브러리를 모두 다운받자.
- jdk 버전도 properties에 의해서 올려주기
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.newlecture</groupId>
<artifactId>javaprj</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>javaprj</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
<!-- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin> -->
</plugins>
</build>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
2) 스프링 부트 이용하기
- 우리는 이제 개발하면서 여러가지 라이브러리가 필요해서 우리는 스프링 부트를 이용한다.
4. 스프링부트 시작 : 230223
1) 스프링부트 개념
-
스프링 부트는 메인 함수가 있다. 메인 함수의 run 메서드가 내장된 톰캣을 실행시켜준다.
-
static, public, webapp 폴더가 서버의 root가 된다.
- 폴더 정리 방법 :
- 우리는 public 폴더에 퍼블리싱된 파일을 넣고 css,이미지는 static에 넣는다. jsp는 webapp 폴더에 넣고 thymeleaf는 templates 폴더에 넣는다.
- 원래는 public이라는 폴더는 잘 안쓰고 우리가 퍼블릭이라는 폴더에 넣는다.
- 우리가 위의 폴더들이 충돌이 안나는 이유는 순서가 있기 때문이다.
- 우리는 public에서 vscode를 통해서 퍼블리싱 파일을 만든다.
2) Front-Controller
-
우리는 스프링 부트에서 POJO 클래스인 Front-Controller를 바로 이용할 수 있다.
-
어노테이션으로 url에 함수가 연결되어서 실제 클래스가 함수가 되어버린다.
-
url도 여러 개의 url에 바인딩이 된다
@RestController
public class Asdfadf {
@RequestMapping("/hello")
public String asdfsadf() {
return "Hello Spring";
}
@RequestMapping("/aaa")
public String aaa() {
return "aaa";
}
}
- 따라서, 우리는 하나의 클래스에 수십, 수백개의 컨트롤러가 들어간다.
-
그래서 이것을 정리해주어야 한다.
- 실습 코드 :
- HomeController.java
package kr.co.rland.web.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// FrontController(POJO 클래스)를 만드는 방법!
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@RestController
public class HomeController {
// 함수 이름은 보통 url이 된다.
@RequestMapping("/index")
public String index() {
return "Hello Spring";
}
}
- MenuController.java
package kr.co.rland.web.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// FrontController(POJO 클래스)를 만드는 방법!
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@RestController
public class MenuController {
// 함수 이름은 보통 url이 된다.
@RequestMapping("/menu/list")
public String list() {
return "/menu/list";
}
@RequestMapping("/menu/detail")
public String detail() {
return "/menu/detail";
}
// @RequestMapping("/admin/menu/list")
// public String list() {
//
// return "/admin/menu/list";
// }
}
a. @RestController
- 컨트롤러 이름이 겹칠 때 사용한다. 특화된 컨트롤에 이름을 붙여준다.
- Rest라는 것은 웹에서 데이터를 전송하고 처리하는 방법이며 즉, 반환하는 것은 상대방에게 데이터를 바로 전해준다.
- REST(Representational State Transfer) : 상태를 전달해준다! REST라는 데이터 형식이다.
- REST 5대 원칙 :
- 서버는 데이터를 제공하고 클라이언트는 데이터를 소비한다
- 웹 기반이 되어야 한다.
- 상태가 유지될 필요 없이 전송이 되어야 한다.
- Restful 형태 웹서버인가 그 전의 Web Server를 이용하는가?
- Restful API는 모든 데이터를 자바스크립트를 이용해서 만든다. 서버는 데이터만 제공해준다. 이것은 CSR 서버이다.
- 서버에서 데이터를 다 구성해서 만들어서 만드는 웹서버는 SSR이다.
- 결론 :
return "Hello JSP"
를 반환하면Hello JSP
라는 사용자가 문자열을 반환해준다.
b. @Controller
-
컨트롤러 이름이 겹칠 때 사용한다. 특화된 컨트롤에 이름을 붙여준다.
-
또한,
@Controller
는 데이터가 아니라 view단을 반환해준다.return "/WEB-INF/view/index.jsp"
라는 파일 경로를 입력해주어야 한다.
c. @RequestMapping
- url 경로가 겹치는 것이 있다면, 클래스명에 어노테이션으로 붙여준다 .
d. 실습 코드 :
package kr.co.rland.web.controller.admin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController("adminMenuController")
@RequestMapping("/admin/menu/list")
public class MenuController {
@RequestMapping("list")
public String list() {
return "/admin/menu/list";
}
@RequestMapping("list")
public String detail() {
return "/admin/menu/detail";
}
}
e. 스프링 부트 서버 자동 재시작 방법
- 프로젝트 우클릭 후 Spring 탭에서 dev tools 다운 받기
3) 스프링 부트에서 jsp 파일 사용
http://localhost:8080/admin/menu/reg.jsp
를 요청하면, jsp파일이 다운받아지는데 이것은 요청이 된다. 요청이 되지만 스프링부트가 처리할 수 없으면 파일이 직접 다운받아진다.
- 이것을 해결하기 위해서는 처리기를 넣어줘야 한다.
- jsp파일을 처리하기 위한 라이브러리를 추가해주어야 한다.
- 메이븐 repo에서 tomcat-embed 가져오기
- 추가적으로 WEB-INF/view 폴더 안에 admin 폴더를 넣어주자!
package kr.co.rland.web.controller.admin;
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.RestController;
@RestController("adminMenuController")
@RequestMapping("/admin/menu/")
public class MenuController {
@RequestMapping("list")
public String list() {
return "/admin/menu/list";
}
@RequestMapping("detail")
public String detail() {
return "/admin/menu/detail";
}
// 등록폼을 주세요.
// @RequestMapping("reg")
// -> service() 함수와 같다. : Get/Post를 내가 다 처리하는 매핑
@GetMapping("reg")
public String reg() {
return "등록 페이지 제공";
}
// 폼입력해서 제출이요.
@PostMapping("reg")
public String reg(String title) {
// 등록하고나서!
return "리디렉션: 목록 페이지 ";
}
}
4) reg에서 list로 이동하기
@Controller
는 view단을 찾으므로 반환값에 url을 직접 입력해줘야 한다. 하지만 현재 경로로서 경로가 더 이상 없다면 현재 경로에서 찾는다.- redirection은 reg라는 페이지에서 클라이언트가 list라는 url로 가라고 한다.
- Post 요청이라서! 그 요청은 jsp 파일의 form 태그에서 method를 post로 설정해줘야 한다.
- MenuContoller.java
package kr.co.rland.web.controller.admin;
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;
@Controller("adminMenuController")
@RequestMapping("/admin/menu/")
public class MenuController {
@RequestMapping("list")
public String list() {
return "/WEB-INF/view/admin/menu/list.jsp";
}
@RequestMapping("detail")
public String detail() {
return "/admin/menu/detail";
}
// 등록폼을 주세요.
// @RequestMapping("reg")
// -> service() 함수와 같다. : Get/Post를 내가 다 처리하는 매핑
@GetMapping("reg")
public String reg() {
return "/WEB-INF/view/admin/menu/reg.jsp";
}
// 폼입력해서 제출이요.
@PostMapping("reg")
public String reg(String title) {
// 등록하고나서!
System.out.println("메뉴 등록 완료");
return "redirect:list";
// @Controller는 view단을 찾으므로 url을 입력해줘야한다.
// redirection은 reg라는 페이지에서 클라이언트가 list라는 url로 가라고 한다.
// 경로가 더 이상 없다면 현재 경로에서 찾는다.
// 그 요청은 jsp파일의 form 태그에서 method post를 설정해줘야 한다. Post 요청이라서!
}
}
- reg.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<section>
<h1>메뉴 등록 페이지 </h1>
<form method="post">
<fieldset>
<legend>메뉴 입력 필드</legend>
<div>
<label>제목 : </label>
<input type="text" name="title">
</div>
<div>
<input type="submit" value="등록">
</div>
</fieldset>
</form>
</section>
</body>
</html>
5. Spring MVC : 230224
- 보통, Spring MVC는 Servlet-Dispatcher를 사용한다.
- 스프링 부트도 같다.
1) Front Controller (in Spring-Boot)
- 프론트 컨트롤러와 데이터를 공유한다.
- output parameter : 컨트롤러가 직접 프론트 컨트롤러에 있는 빈 그릇에 데이터를 담는다. 그리고 반환값을 받기 위한 값.
- 하지만, 우리는 데이터를 request를 공유하지 않는 이유는? 서블릿을 드러내기 위해서 사용하며 별도의 서블릿 아닌 그릇에 담아준다. 역할에 대한 분배가 잘 되어야 한다.
- 실습 코드 :
- HomeController.java
package kr.co.rland.web.controller;
import java.net.URLEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
// FrontController(POJO 클래스)를 만드는 방법!
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@Controller
public class HomeController {
// 함수 이름은 보통 url이 된다.
@RequestMapping("/index") // 이렇게 하면, Front-controller가 가지고 있는 response를 가져온다.
public String index(Model model, HttpServletResponse response) {
String data = "Hello Data";
//model에는 addAttribute이다.
model.addAttribute("data",data);
return "/WEB-INF/view/index.jsp";
}
}
- index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>welcome ${data}</h1>
</body>
</html>
2) JSTL 라이브러리 추가 방법
- jstl을 사용하기 위해서 pom.xml에 라이브러리를 추가해주자!
- 기존 톰캣 버전이 10버전이라서 jakarta 버전으로 라이브러리를 JSTL 추가해주어야 한다.
3) view단에서 url의 쿼리스트링 받기
- EL 태그를 사용하기 위해서는 JSTL의 taglib 지시자 블럭이 필요하다. 주의하기!
- list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>admin 메뉴 목록 페이지</h1>
<nav>
<hl>페이저</h1>
<form>
<label>size: </label>
<!-- <input name="s" value="10"> -->
<select>
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
</select>
<input type="submit" value="변경">
</form>
<ul>
<c:forEach var="n" begin="1" end="5">
<li><a href="list?p=${n}&q=${m}">${n}</a></li>
<!-- 여기에 ${m}은 출력할 수 없다. 따로, c:set으로 var 변수를 설정해주어야 한다. -->
</c:forEach>
</ul>
</nav>
</body>
</html>
4) view단의 파라미터 받아서 매핑 및 기본 설정
- MenuController.java
package kr.co.rland.web.controller.admin;
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.RequestParam;
@Controller("adminMenuController")
@RequestMapping("/admin/menu/")
public class MenuController {
// 400 에러는 데이터가 파라미터 인자가 없는 경우이다.
// 403 에러는 권한 관련 에러이다.
// 405 에러는 처리메서드가 post요청인데 get요청만 있는 경우
@RequestMapping("list")
public String list(
@RequestParam(name = "p", defaultValue = "1") int page,
//@RequestParam("q") String query
// @Requestparam같은 경우는 무조건 파라미터가 필요하다.
// 무조건을 없애주기 위해서 @RequestParam(name = "q", required=false)
@RequestParam(name="q", defaultValue = "hello") String query
) {
System.out.println(page);
System.out.println(query);
return "/WEB-INF/view/admin/menu/list.jsp";
}
}
5) Front-Controller에서 쿠키 사용법
- 쿠키는 서블릿이지만 스프링 부트에서도 다른 방법이 없어서 어쩔 수 없이 response 인자를 사용한다.
- 쿠키가 저장된 것은 스프링에서 간단히 출력하여 확인할 수 있지만 쿠키를 저장하는 방법은 다른 방법이 없어서 기존에 썼던 방식을 써야 한다.
- 모든 url에서 쿠키를 심기 위해서는 쿠키를 add할 때 쿠키 저장 경로를 설정해주어야 한다 :
cookie.setPath("/");
- 하지만 처음 웹에 들어올 때, 쿠키를 심어주기 위해서는 필터에 쿠키 심는 코드를 넣어주어야 한다.
- 그 이유는 지금 ‘index’ url에 쿠키를 심었기 때문에 setPath 메서드가 ‘index’ url 보다 앞에 있어야 한다.
- 쿠키 domain 설정! :
cookie.setDomain("localhost");
- 쿠키 저장 경로 설정 :
cookie.setPath("/");
- 해당 domain의 모든 url에서 쿠키가 저장된다.(여기서 심어 준다.)
- 쿠키 유효기간 설정 :
cookie.setMaxAge(30*60);
- 해당 코드는 쿠키가 30초간 저장(ms 단위)
- 쿠키 보안 설정 :
cookie.setSecure(true);
package kr.co.rland.web.controller;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
// FrontController(POJO 클래스)를 만드는 방법!
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@Controller
public class HomeController {
// 함수 이름은 보통 url이 된다.
@RequestMapping("/index")
public String index(Model model, HttpServletResponse response) throws UnsupportedEncodingException { // 이렇게 하면, Front-controller가 가지고 있는 response를 가져온다.
// String data = "Hello Data";
// 쿠키도 서블릿이라서 어떻게 해야하지? 방법이 없어서 어쩔 수 없이 response를 사용한다.
// 쿠키는 한글을 출력 못해서 url 인코더로 인코딩을 해주어야 한다. ㅜ
String data = URLEncoder.encode("cookie 지륭~", "utf-8");
System.out.println(data);
Cookie cookie = new Cookie("my", data);
// 쿠키 domain 설정!
cookie.setDomain("localhost");
// 쿠키 저장 경로 설정 : 해당 domain의 모든 url에서 쿠키가 저장된다.(여기서 심어 준다.)
cookie.setPath("/");
// 쿠키 유효기간 설정 : 30초간 저장(ms 단위)
cookie.setMaxAge(30*60);
// 쿠키 보안 설정!
cookie.setSecure(true);
response.addCookie(cookie);
// 쿠키는 모든 url에서 열어 볼 수 있다. 이러한 url은 클라이언트가 요청한 url이다.("/"에서 시작)
//model에는 addAttribute이다.
model.addAttribute("data", data);
return "/WEB-INF/view/index.jsp";
}
}
6) 과거 방식의 쿠키를 가져오는 방법
- 수동으로 가져와서 조건 처리
package kr.co.rland.web.controller.admin;
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.RequestParam;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
@Controller("adminMenuController")
@RequestMapping("/admin/menu/")
public class MenuController {
// 400 에러는 데이터가 파라미터 인자가 없는 경우이다.
// 403 에러는 권한 관련 에러이다.
// 405 에러는 처리메서드가 post요청인데 get요청만 있는 경우
@RequestMapping("list")
public String list(
@RequestParam(name = "p", defaultValue = "1") int page,
//@RequestParam("q") String query
// @Requestparam같은 경우는 무조건 파라미터가 필요하다.
// 무조건을 없애주기 위해서 @RequestParam(name = "q", required=false)
@RequestParam(name="q", defaultValue = "hello") String query,
// 스프링에서도 쿠키를 받아오기 위해서 request를 써야 한다.
HttpServletRequest request
)
{
String myCookie = "";
Cookie[] cookies = request.getCookies();
for(Cookie cookie: cookies)
if(cookie.getName().equals("my")) {
myCookie = cookie.getValue();
break;
}
System.out.println(myCookie);
System.out.printf("page: %d, query: %s",page, query);
return "/WEB-INF/view/admin/menu/list.jsp";
}
@RequestMapping("detail")
public String detail() {
return "/admin/menu/detail";
}
// 등록폼을 주세요.
// @RequestMapping("reg")
// -> service() 함수와 같다. : Get/Post를 내가 다 처리하는 매핑
@GetMapping("reg")
public String reg() {
return "/WEB-INF/view/admin/menu/reg.jsp";
}
// 폼입력해서 제출이요.
@PostMapping("reg")
public String reg(String title) {
// 등록하고나서!
System.out.println("메뉴 등록 완료");
return "redirect:list";
// @Controller는 view단을 찾으므로 url을 입력해줘야한다.
// redirection은 reg라는 페이지에서 클라이언트가 list라는 url로 가라고 한다.
// 경로가 더 이상 없다면 현재 경로에서 찾는다.
// 그 요청은 jsp파일의 form 태그에서 method post를 설정해줘야 한다. Post 요청이라서!
}
}
7) 스프링에서 쿠키를 가져오는 방법 !!(중요)
@CookieValue("my") String myCookie
를 이용해서 저장되었던 쿠키를 가져올 수 있다.
- 쿠키를 확인하고 싶으면, 쿠키를 먼저 index 페이지에서 만들어서 list 페이지에서 쿠키 출력을 확인하자!
- 쿠키는 특수문자나 한글이 포함되어 인코딩된 방식으로
cookie+%EC%A7%80%EB%A5%AD%7E
이렇게 심어지고 우리는 이러한 쿠키를 로직에 이용하기 위해서 디코딩을 해줘서 디코딩한 결과 값인cookie 지륭~
라는 쿠키를 이용할 수 있다**
package kr.co.rland.web.controller.admin;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
@Controller("adminMenuController")
@RequestMapping("/admin/menu/")
public class MenuController {
// 400 에러는 데이터가 파라미터 인자가 없는 경우이다.
// 403 에러는 권한 관련 에러이다.
// 405 에러는 처리메서드가 post요청인데 get요청만 있는 경우
@RequestMapping("list")
public String list(
@RequestParam(name = "p", defaultValue = "1") int page,
@RequestParam(name = "q", required = false) String query,
// 지금은 스프링에서 이렇게 쿠키를 가져온다.
@CookieValue("my") String myCookie
) throws UnsupportedEncodingException
{
// 쿠키도 인코딩되어 있기 때문에 디코딩해서 우리는 사용한다.
// Ajax에서도 url도 인코딩해서 보낸다! post, get 요청!
myCookie = URLDecoder.decode(myCookie, "utf-8");
System.out.println(myCookie);
System.out.printf("page: %d, query: %s\n",page, query);
return "/WEB-INF/view/admin/menu/list.jsp";
}
}
8) 헤더 요청 방법!
@RequestHeader
를 통해 request헤더를 컨트롤러로 가져 올 수 있다.
package kr.co.rland.web.controller.admin;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
@Controller("adminMenuController")
@RequestMapping("/admin/menu/")
public class MenuController {
// 400 에러는 데이터가 파라미터 인자가 없는 경우이다.
// 403 에러는 권한 관련 에러이다.
// 405 에러는 처리메서드가 post요청인데 get요청만 있는 경우
@RequestMapping("list")
public String list(
@RequestParam(name = "p", defaultValue = "1") int page,
@RequestParam(name = "q", required = false) String query,
// @Requestparam같은 경우는 무조건 파라미터가 필요하다.
// 지금은 스프링에서 이렇게 쿠키를 가져온다.
@CookieValue("my") String myCookie,
// 헤더 요청!
@RequestHeader("Accept-Language") String language
) throws UnsupportedEncodingException
{
System.out.printf("Accept-Language: %s\n", language);
// 출력 : Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
return "/WEB-INF/view/admin/menu/list.jsp";
}
}
9) 파일 전송
- 보통, 파일 전송은 샌드박스 안에서만 가능하다.
- 그래서 파일 전송은 다음 방법으로 전송이 가능하다.
파일 전송 창
에서 클릭해서 파일을 보내는 방법드래그 앤 드랍
의 파일 전송 방법ctrl + c, ctrl + v
파일 전송 방법
- 원래 파일 전송은 url 형식으로 인코딩하여 보내지는데 우리는 url형식의 파일이 필요하지 않고 실제 파일이 필요하다. 그래서 html form 형식의 인코딩 방식을
multipart/form-data
로 설정하여 파일명, 파일크기, 바이너리 파일 형식 등으로 여러 part로 나누어 파일 전송한다!
- 실습 코드 :
- 그래서 파일전송은 바이너리 파일로 전송을 한다. MultipartFile는 원래 인터페이스라서 앞에 다른 수식어가 붙지만 일반적으로 MultipartFile으로 파일 전송할 때, 참조형식으로 MultipartFile라는 참조 객체를 받는다.
- 따라서, jsp에서 문자열로 보내는 것이 아니라 바이너리 파일로 인코딩해서 보내줘야 한다.(url 형식도 아니다.)
- 추가** : 파일 전송 시, 별도의 url이 필요하지 않아서
@ResponseBody
어노테이션을 사용한다. 전체적으로는@Controller
로서 view를 찾아주고 일부분만 문자열로 반환할 때,@ResponseBody
를 사용한다!
- reg.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<section>
<h1>파일 입력 필드</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<fieldset>
<legend>메뉴 입력 필드</legend>
<div>
<label>제목 : </label>
<input type="file" name="img">
</div>
<div>
<input type="submit" value="등록">
</div>
</fieldset>
</form>
</section>
</body>
</html>
- MenuController.java
- 파일 전송 시, 별도의 url이 필요하지 않아서
@ResponseBody
어노테이션을 사용한다. - @Controller는 view를 바로 찾아주는데, return 되는 것이 바로 문자열로 반환할 때 @ResponseBody 사용!
- 전체적으로는 @Controller로서 view를 찾아주고 일부분만 문자열로 반환할 때 @ResponseBody 사용한다!
- 파일 전송 시, 별도의 url이 필요하지 않아서
package kr.co.rland.web.controller;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
// FrontController(POJO 클래스)를 만드는 방법!
// 클래스는 보통 폴더명이 된다.(기능별로 넣자!)
@Controller
@RequestMapping("/")
public class HomeController {
// 업로드는 요청이므로 Pos 요청이다. 그리고, 파일 전송은 view 단이 필요 없다!
// 따라서, 반환을 @ResponseBody로서 문자열로 반환해준다!
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile img) {
// return 되는 것이 바로 문자열로 반환할 때 Body 사용!
System.out.println(img.getOriginalFilename());
return "ok";
}
}