국비교육(22-23)

49일차(2)/Spring(3) : 요청 파라미터 추출 / forward, redirect 이동 / Dependency Injection

서리/Seori 2022. 12. 15. 23:51

49일차(2)/Spring(3) : 요청 파라미터 추출 / forward, redirect 이동 / Dependency Injection

 

- Spring에서 요청 파라미터 추출하는 방법 3가지

- 페이지 이동 방법(forward, redirect 방식 비교)

- ModelAndView 객체 사용

- Dependency Injection 의 개념 및 사용방법

 

 

- 하단 Servers 탭의 서버 설정

 

 

 

- 서버 우클릭- Add and Remove - 지난 프로젝트는 좌측으로 빼두기(remove)

- 동시에 여러개의 프로젝트에서 서버를 쓰면 시간도 오래 걸리고 에러가 날 수도 있으므로

 필요없는 프로젝트는 remove로 빼놓기!

 

 


 

- 새 프로젝트 생성 Spring01_Basic

- com.sy.spring01 : 패키지 이름의 세번째 단어가 context 경로가 된다.

 

 

home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/home.jsp</title>
</head>
<body>
	<div class="container">
		<h1>인덱스 페이지입니다.</h1>
		<ul>
			<li><a href="${pageContext.request.contextPath}/member/insertform">요청 파라미터 추출 테스트</a></li>
			<li>	<a href="${pageContext.request.contextPath}/move/test">이동 테스트</a></li>
			<li><a href="di/test">Dependency Injection 테스트</a></li>
		</ul>
		
		<h3>공지사항</h3>
		<ul>
			<c:forEach var="tmp" items="${noticeList }">
				<li>${tmp }</li>
			</c:forEach>
		</ul>
	</div>
</body>
</html>

 

- com.sy.spring01 패키지 안에 member.dto 패키지 만들기

- MemberDto

package com.sy.spring01.member.dto;

public class MemberDto {
	private int num;
	private String name;
	private String addr;
	
	public MemberDto() {}

	public MemberDto(int num, String name, String addr) {
		super();
		this.num = num;
		this.name = name;
		this.addr = addr;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAddr() {
		return addr;
	}

	public void setAddr(String addr) {
		this.addr = addr;
	};	
	
}

 

- com.sy.spring01패키지 안에 member 패키지 만들기

MemberController.java

package com.sy.spring01.member;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.sy.spring01.member.dto.MemberDto;

//component scan 을 통해서 bean이 될 수 있도록 어노테이션을 붙인다.
@Controller
public class MemberController {
	
	@RequestMapping("/member/insertform")
	public String insertform() {
		
		// WEB-INF/view/member/insertform.jsp로 forward이동해서 응답
		return "member/insertform";
		
	}
	
	@ResponseBody //리턴하는 문자열을 클라이언트에게 직접 출력하기
	@RequestMapping("/member/insert")
	public String insert(HttpServletRequest request) {
		/*
		 * [ 요청 파라미터 추출하는 방법1 ] 
		 * 
		 * HttpServletReqest 객체를 Controller메소드로 전달받아서 추출
		 */
		int num=Integer.parseInt(request.getParameter("num"));
		String name=request.getParameter("name");
		String addr=request.getParameter("addr");
		System.out.println(num+" | "+name+" | "+addr);
		
		return "ok1";		
	}
	@ResponseBody
	@RequestMapping("/member/insert2")
	public String insert2(int num, String name, String addr) {
		/*
		 * [ 요청 파라미터 추출하는 방법2 ]
		 * 
		 *  파라미터명과 동일하게 메소드의 매개변수를 선언해 놓으면 자동으로 추출해서 넣어준다.
		 *  <input name="num"> 이면 int num or String num
		 *  <input name="email"> 이면 String email 이런식으로 선언하면 된다.
		 *  
		 *  insert2(@RequestParam int num,@RequestParam String name,@RequestParam String addr)
		 *  에서 @RequestParam 어노테이션이 생략된 형태이다.
		 */
		System.out.println(num+" | "+name+" | "+addr);
		
		return "ok2";		
	}
	@ResponseBody
	@RequestMapping("/member/insert3")
	public String insert3(MemberDto dto) {
		/*
		 * [ 요청 파라미터 추출하는 방법3 ]
		 * 
		 * 파라미터명과 동일한 필드명을 가지고 있는 dto 클래스 type을 메소드의 매개변수로 선언해 놓으면
		 * 자동으로 추출해서 dto에 추출한 값을 setter메소드를 이용해서 넣은 다음 해당 dto 객체의
		 * 참조값에 전달한다.
		 * 
		 * pubilc class MemberDto{
		 * 	 private int num; => <input name="num">
		 * 	 private String name; => <input name="name">
		 *   private String addr; => <input name="addr">
		 *   
		 *   insert(@ModelAttribute MemberDto dto)에서 @ModelAtrribute가 생략된 형태이다.
		 */
		System.out.println(dto.getNum()+" | "+dto.getName()+" | "+dto.getAddr());
		return "ok3";
	}
}

 

 

@ResponseBody 어노테이션

- 리턴하는 문자열이 클라이언트에게 직접 출력되게 하는 어노테이션이다.

- 이것을 사용하면 리턴하는 문자열이 바로 클라이언트에게 응답되게 할 수 있다.

 

@RequestMapping(" / xxx/yyy "){

     return " xxx/yyy "; }

 

- / 슬래시가 들어가고 들어가지 않는 자리 잘 기억하기!

 

 

views / member 폴더안에 만들기

/views/member/insertform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/member/insertform.jsp</title>
</head>
<body>
	<div class="container">
		<h1>회원추가 폼</h1>
		<form action="${pageContext.request.contextPath}/member/insert" method="post">
			번호 <input type="text" name="num" /><br />
			이름 <input type="text" name="name" /><br />
			주소 <input type="text" name="addr" /><br />
			<button type="submit">추가</button>
		</form>
		
		<h1>회원추가 폼2</h1>
		<form action="${pageContext.request.contextPath}/member/insert2" method="post">
			번호 <input type="text" name="num" /><br />
			이름 <input type="text" name="name" /><br />
			주소 <input type="text" name="addr" /><br />
			<button type="submit">추가</button>
		</form>
		
		<h1>회원추가 폼3</h1>
		<form action="${pageContext.request.contextPath}/member/insert" method="post">
			번호 <input type="text" name="num" /><br />
			이름 <input type="text" name="name" /><br />
			주소 <input type="text" name="addr" /><br />
			<button type="submit">추가</button>
		</form>
	</div>
</body>
</html>

 

 

- 주의!! form에서 이동하는 링크에 .jsp 는 안 들어간다. (기본으로 접미사로 들어가게 되어있으므로)

 

 

 

* 요청 패러미터 추출 방식 3가지!!

 

public String insert(HttpServletRequest request) { }

- 1번: 함수값을 호출하면서 알아서 넣어주니까 필요한 객체가 있으면 매개변수로 선언만 하면 된다

 

 


 

public String insert2(int num, String name, String addr) { }

- 2번: 파라미터명과 동일하게 메소드의 매개변수를 선언해 놓으면 자동으로 추출해서 넣어준다.
파라미터의 name=" " 값에 맞춰서!

 

- insert2(@RequestParam int num,@RequestParam String name,@RequestParam String addr)  에서

  @RequestParam 어노테이션이 생략된 형태이다.

 

- int num 이렇게 넣으면 숫자로 변환하는 작업도 알아서 해준다.

- 위와 같이 parseInt() 를 하지 않아도 된다.

 


 

public String insert3(MemberDto dto) { }

 

- 3번 : 파라미터명과 동일한 필드명을 가지고 있는 dto 클래스 type을 메소드의 매개변수로 선언해 놓으면
 자동으로 추출해서 dto에 추출한 값을 setter메소드를 이용해서 넣은 다음 해당 dto 객체의 참조값에 전달한다.

 

- 이 필드명이 일치하면 불러올 수 있다.

 

- dto 의 setter 메소드를 활용해서 넣어주는 것이다.(setNum, setName, setAddr)

- 이것을 사용하려면 DTO에 getter,setter가 필요한 구조로 잘 만들어져 있어야 한다.

 

- insert(@ModelAttribute MemberDto dto)에서 @ModelAtrribute가 생략된 형태이다.(어노테이션 생략 가능)

 

 

- 2번, 3번이 가장 잘 쓰인다.

 

 

- 2번은 게시판 글 상세보기 등에서 get방식 전송에서 읽어오는 데에 자주 사용된다.

 (저런 값을 굳이 dto에 넣을 필요는 없기 때문에)

 

- 요청 파라미터를 자동으로 추출하고싶으면 controller에 선언만 해놓으면 된다.

 


 

- 새 예제 추가(이동)

 

 

com.sy.spring01/ TestController.java (최종)

package com.sy.spring01;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class TestController {
	
	@Autowired //spring bean container에 Remoncon type객체가 있으면 자동 주입된다.(DI)
	private RemoconService service; //RemoconServiceImpl 객체의 참조값을 주입받을 필드
	
	@ResponseBody
	@RequestMapping("/di/up")
	public String diUp() {
		/*
		 * 채널을 올리는 로직을 수행할 때 필요한 객체는?
		 * (원래는 RemoconServiceImpl 객체가 필요하다.)
		 * DI되어서 필드에 참조값이 있으므로 단순히 사용만 하면 된다.
		 */
		
		service.up();
		
		return "up ok!";
	}
	
	@ResponseBody
	@RequestMapping("/di/down")
	public String diDown() {
		service.down();
		return "down ok!";
	}
	
	@RequestMapping("/di/test")
	public String diTest() {
		
		return "di/test";
	}
	
	@RequestMapping("/move/test")
	public String test() {
		/*
		 * Controller 메소드에서 return type을 String 으로 설정한 후
		 * 문자열을 리턴해주면 해당 문자열은 view page(jsp) 페이지의 위치가 된다.
		 * 즉 해당 jsp 페이지로 자동 forward 이동되어서 응답하게 된다.
		 */

		// /WEB-INF/views/move/test.jsp 페이지로 forward 이동
		return "move/test";
	}
	
	@RequestMapping("/move/update")
	public String update() {
		//무언가 수정을 했다고 가정
		System.out.println("무언가 수정했습니다.");
		//클라이언트에게 새로운 경로로 요청을 다시 하라고 강요하기(redirect 이동)
		
		// "redirect: 리다일렉트 이동할 절대경로" (context path는 쓰지 않는다)
		return "redirect:/move/test";
	}
	
	@RequestMapping("/move/fortune")
	public ModelAndView fortune() {
		//Model과 view page의 위치를 동시에 넣을 수 있는 ModelAndView 객체 생성
		ModelAndView mView=new ModelAndView();
		
		//view page에 전달할 모델(데이터)라고 가정하자
		String fortuneToday="동쪽으로 가면 귀인을 만나요!";
		
		// HttpServletRequest 객체에 담는 대신, .addObject(key, value) 형태로 ModelAndView 객체에 담으면 된다.
		mView.addObject("fortuneToday", fortuneToday);
		//view page의 위치를 담는다.
		mView.setViewName("move/fortune");
		
		//리턴해주기
		return mView;
	}
	
	// ModelAndView객체가 필요하다면 직접 생성하지 않고 메소드의 매개변수로 선언하면 자동으로 전달된다.
	@RequestMapping("/move/fortune2")
	public ModelAndView fortune2(ModelAndView mView) {		
		
		//view page에 전달할 모델(데이터)라고 가정하자
		String fortuneToday="동쪽으로 가면 귀인을 만나요!";
		
		// HttpServletRequest 객체에 담는 대신, .addObject(key, value) 형태로 ModelAndView 객체에 담으면 된다.
		mView.addObject("fortuneToday", fortuneToday);
		//view page의 위치를 담는다.
		mView.setViewName("move/fortune");
		
		//리턴해주기
		return mView;
	}
	
	@RequestMapping("/move/fortune3")
	public ModelAndView fortune3() {		
		
		ModelAndView mView=new ModelAndView("move/fortune");
		
		//view page에 전달할 모델(데이터)라고 가정하자
		String fortuneToday="동쪽으로 가면 귀인을 만나요!";
		
		// HttpServletRequest 객체에 담는 대신, .addObject(key, value) 형태로 ModelAndView 객체에 담으면 된다.
		mView.addObject("fortuneToday", fortuneToday);		
		
		//리턴해주기
		return mView;
	}
}

 

- 컨트롤러가 추가되었으면 새로고침만으로는 반영이 안 되고, 서버를 껐다 켜야 한다.

 

/views/move/test.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/move/test.jsp</title>
</head>
<body>
	<div class="container">
		<h3>페이지 이동 테스트</h3>
		<p>이 페이지는 /move/test 요청에 대해서 forward 이동된 jsp페이지 입니다.</p>
		<ul>
			<li><a href="update">리다일렉트 이동하기</a></li>
			<li><a href="fortune">forward 이동하는 다른 방법</a></li>
			<li><a href="fortune2">forward 이동하는 다른 방법2</a></li>
			<li><a href="fortune3">forward 이동하는 다른 방법3</a></li>
		</ul>
	</div>
</body>
</html>

 

- 문자열만 넣어놓으면 자동으로 forward된다.

 

 

- update 링크 추가 (상대경로)

- 새로운 <a>를 추가하면 이렇게 나온다. 이 주소에 따른 controller를 만들어야 한다!

 

@RequestMapping("/move/update")
public String update() {
    //무언가 수정을 했다고 가정
    System.out.println("무언가 수정했습니다.");
    //클라이언트에게 새로운 경로로 요청을 다시 하라고 강요하기(redirect 이동)

    // "redirect: 리다일렉트 이동할 절대경로" (context path는 쓰지 않는다)
    return "redirect:/move/test";
}

- testController에 update() 메소드를 추가

 

- 콘솔창에 인쇄되면서 창은 새로고침되듯이 깜박거린다.

- 주소창에 .jsp가 나오지는 않는다.

 

- 지금 2가지 요청을 수행한 것이다!

 

return "redirect:/move/test";

" redirect: 리다일렉트 이동할 절대경로 " 

- 이렇게 입력하면 리다일렉트 요청이 된다. (context path는 쓰지 않는다)

 


 

- test.jsp에 링크 추가. 새로운 forward 이동

 

/move/fortune.jsp 만들기

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>views/move/fortune.jsp</title>
</head>
<body>
	<div class="container">
		<p>오늘의 운세: <strong>${fortuneToday }</strong></p>
	</div>
</body>
</html>

 

- TestController에 메소드 추가 (test/fortune 에 대한 이동)

@RequestMapping("/move/fortune")
public ModelAndView fortune() {
    //Model과 view page의 위치를 동시에 넣을 수 있는 ModelAndView 객체 생성
    ModelAndView mView=new ModelAndView();

    //view page에 전달할 모델(데이터)라고 가정하자
    String fortuneToday="동쪽으로 가면 귀인을 만나요!";

    // HttpServletRequest 객체에 담는 대신, .addObject(key, value) 형태로 ModelAndView 객체에 담으면 된다.
    mView.addObject("fortuneToday", fortuneToday);
    //view page의 위치를 담는다.
    mView.setViewName("move/fortune");

    //리턴해주기
    return mView;
}

 

* ModelAndView : 데이터와 뷰 페이지의 정보를 보여주는 객체

- Model과 view page의 위치를 동시에 넣을 수 있다.

 

- HttpServletRequest 객체에 담지 않고 ModelAndView 객체에 담기

- .addObject() 로 key, value 형태로 담을 수 있다.

 

 

- ModelAndView 타입을 리턴타입으로 설정하는 것의 장점!

- 데이터와 view page를 같이 담아서 리턴해준다.

 

 

- 페이지도 이동하고, String(데이터)도 전달한다.

- 여러가지 정보를 담아서 한번에 리턴해주는 객체!

 

- Controller에서는 원하는 메소드를 아무거나 만들 수 있다. 메소드의 인자도 자유롭게 정할 수 있다.

 

 


 

fortune2 링크 추가

 

- TestController에 메소드 추가

@RequestMapping("/move/fortune2")
public ModelAndView fortune2(ModelAndView mView) {		

    //view page에 전달할 모델(데이터)라고 가정하자
    String fortuneToday="동쪽으로 가면 귀인을 만나요!";

    // HttpServletRequest 객체에 담는 대신, .addObject(key, value) 형태로 ModelAndView 객체에 담으면 된다.
    mView.addObject("fortuneToday", fortuneToday);
    //view page의 위치를 담는다.
    mView.setViewName("move/fortune");

    //리턴해주기
    return mView;
}

 

- ModelAndView 객체를 따로 new 하지 않고 그 대신 매개변수로 받을 수도 있다.

- 매개변수로 선언하면 자동으로 객체가 전달된다.

 


 

fortune3 링크추가

 

- TestController에 메소드 추가

@RequestMapping("/move/fortune3")
public ModelAndView fortune3() {		

    ModelAndView mView=new ModelAndView("move/fortune");

    //view page에 전달할 모델(데이터)라고 가정하자
    String fortuneToday="동쪽으로 가면 귀인을 만나요!";

    // HttpServletRequest 객체에 담는 대신, .addObject(key, value) 형태로 ModelAndView 객체에 담으면 된다.
    mView.addObject("fortuneToday", fortuneToday);		

    //리턴해주기
    return mView;
}

 

- ctrl+space로 보면 viewpage의 정보를 생성자의 인자로 전달할 수도 있다.

 

- 이렇게 작성하면 addObject() 만 하고, setViewName() 은 따로 해주지 않아도 된다. 지워주기!

 

 


 

- home.jsp에 DI 링크 추가

- Dependency Injection : 의존관계 주입. 스프링의 중요한 개념. 

 

 

* 대규모 프로젝트에서 spring framework를 사용하는 이유는 무엇일까?

 

- 유지보수가 편리해서이다.  이 유지보수를 편리하게 하기 위해서 나온 개념이 Dependency Injection 이다.

- 유지보수가 편하려면 객체들 간의 의존관계가 느슨해야 한다.

 의존관계를 느슨히게 하려면 핵심 의존 객체의 생성과 관리를 spring에 맡긴다.

 

- 객체를 spring이 생성하게 하고, spring으로부터 받아서 쓴다.

- 필요한 객체가 있으면 spring bean container로부터 주입받아서 사용하는 구조로 프로그래밍을 해야한다.

- 단. 주입받을 때는 반드시 interface type으로 받아서 사용한다.

 

- container에서 컴포넌트 스캔을 해서 어떤 객체를 관리할지 정해서 bean이 만들어진다는 것 기억!

 

 

기본 spring01 패키지에 interface를 만들어준다.

 

RemoconService (인터페이스)

package com.sy.spring01;

public interface RemoconService {
	public void up();
	public void down();
}

- 추삼메소드 2개가 있다.

 

RemoconServiceImpl (클래스)

package com.sy.spring01;

import org.springframework.stereotype.Service;

@Service
public class RemoconServiceImpl implements RemoconService{

	@Override
	public void up() {		
		System.out.println("채널을 올려요!");
	}

	@Override
	public void down() {
		System.out.println("채널을 내려요!");		
	}
}

 

- 인터페이스를 상속받고 override 해준다.

- @Service 어노테이션을 붙여주기

 

 

- TestController에 diTest 메소드 추가

@RequestMapping("/di/test")
public String diTest() {
    return "di/test";
}

 

views/di/ test.jsp 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/di/test.jsp</title>
</head>
<body>
	<div class="container">
		<h2>DI 테스트 링크</h2>
		<ul>
			<li><a href="up">채널 올리기</a></li>
			<li><a href="down">채널 내리기</a></li>
		</ul>
	</div>
</body>
</html>

 

- 현재 상태

 

 

TestController에 메소드추가(up, down)

@Autowired //spring bean container에 Remoncon type객체가 있으면 자동 주입된다.(DI)
private RemoconService service; //RemoconServiceImpl 객체의 참조값을 주입받을 필드

@ResponseBody
@RequestMapping("/di/up")
public String diUp() {
    service.up();
    return "up ok!";
}

@ResponseBody
@RequestMapping("/di/down")
public String diDown() {
    service.down();
    return "down ok!";
}

 

 

- 이렇게 해도 잘 작동하지만, 스프링의 관점에서는 그다지 좋지 않다.

- 이렇게 객체를 메소드 안에서 바로 new해서 사용하는 것은 좋지 않다.

- 이러면 RemoconServiceImpl 객체가 수정되거나 삭제되면 전체를 다 뜯어고쳐야 한다.

 

- 따라서 객체 생성을 spring에 맡기는 것이 좋다.

 

@Service

- 이미 이 annotation을 붙임으로서 핵심 의존 객체의 생성과 관리를 spring에 맡긴 것이다.

 

- 그렇다면 이제 1)주입받기 2)인터페이스 타입으로 받기 를 해주면 된다.

 

- 여기에 service 필드를 하나 생성해준다. RemoconServiceImpl 객체의 참조값을 주입받을 필드!

- 하지만 생성한 상태이기만 하면 null이기 때문에 null point exception이 발생한다.

 

- 저 필드에 @Autowired 어노테이션을 추가하면 작동한다.

- 이렇게 하면 Spring bean container에 Remoncon type객체가 있으면 자동 주입된다.(DI)

- 원래는 null인데 저걸 추가해주면 객체가 들어간다. 인터페이스 타입으로 의존하는 구조로 만들어주는 것이다.

 

 

- 객체의 생성과 관리를 spring에 맡기는것. 인터페이스 타입인지 확인하고 spring이 찾아서 넣어준다.

 

- 이런 형태로 인터페이스를 만들 일이 많다.

- Dao를 만들때에도 인터페이스를 미리 정의해서 implement 해서 만들 수 있다.

 의존 관계를 느슨하게 만들기 위해!

 

- 이렇게 하면 RemoconServiceImpl 클래스 하나를 삭제하거나 Rename 해도 에러가 발생하지 않는다.

 TestController에서는 RemoconServiceImpl 을 사용하고 있지 않다.

 

- 코딩 양은 좀 많아지지만, 이것이 스프링을 사용하는 이유이다.

 

- down도 동일하다. service 필드에 핵심 의존 객체가 들어있다고 가정하고 그냥 사용만 하면 된다.