TIL - 13주차 코드

 


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. 리플렉션 참고 사이트 모음 :




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 사용한다!
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";
	}
}