본문 바로가기

Frame Work/Spring

Spring Annotation

Spring Annotation?

 

스프링의 xml 설정 파일은 HandlerMapping, Controller, ViewResolver 등등 여러 클래스를 bean으로 등록해야 하기 때문에 나중에 과도한 xml 설정으로 유지보수가 어려워질 수 있다는 한계가 있습니다. 따라서 어노테이션이라는 '@' 방식으로 xml 파일의 설정을 최소화 하여 그 한계를 극복하기 위해 등장했습니다. 

 

오늘 포스팅에서는 기본적인 어노테이션 사용 방식과 종류에 대해 알아 보겠습니다. 

 

사용방식 및 종류 

 

Spring MVC Annotation을 사용하려면 먼저 <bean> 루트 엘림너트에 context 네임 스페이스를 추가한 후, 

<context:component-scan> tag를 생성합니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
x s i : s c h e m a L o c a t i o n = " h t t p : / / w w w . s p r i n g f r a m e w o r k . o r g / s c h e m a / b e a n s 
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-4.3.xsd">

<!-- 아래 설정을 통해 glory.spring.web 패키지 및 하위 패키지가 모두 스캔 대상에 포함되었습니다. -->
<context:component-scan base-package="glory.spring.web">
</context:component-scan>

</beans>

 

해당 범위를 스캔

 

 

@ Controller 

xml 설정으로 컨트롤러 클래스를 따로 만들어서 관리 하는 방식과 달리 클래스 선언부에 @Controller를 설정하면 <context:component-scan>으로 스프링 컨테이너가 컨트롤러 객체를 자동으로 생성합니다. 

 

단순히 객체를 생성하는 것에 그치지 않고 DispatcherServlet이 인식하는 Controller 객체로 만들어줍니다. 이는 @Component를 상속한 @Controller, @Service, @Repository 등도 마찬가지 입니다. 

 

 

* 간단한 InsertBoard Controller 예시 

 

package tommy.spring.web.board;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
public class InsertBoardController {
	public void insertBoard(HttpServletRequest request) {
		System.out.println("글 등록 처리");
		// 1. 사용자 입력 정보 추출
		// request.setCharacterEncoding("UTF-8");
		String title = request.getParameter("title");
		String writer = request.getParameter("writer");
		String content = request.getParameter("content");
		// 2. 데이터베이스 연동 처리
		BoardVO vo = new BoardVO();
		vo.setTitle(title);
		vo.setWriter(writer);
		vo.setContent(content);
		BoardDAO boardDAO = new BoardDAO();
		boardDAO.insertBoard(vo);
	}
}

 

- InsertBoardController는 스프링 컨테이너가 자동으로 생성하고 Controller 객체로 인식합니다. 중요한 점은 어노테이션을 사용하면서 POJO 방식의 Class로 변경되었으므로 메서드 이름을 insertBoard, 리턴타입 void,매개변수가 요청에 대한 HttpServletRequest로 변경할 수 있습니다. 

 

 

@RequestMapping 

 

 

@Controller로 Controller 객체로 인식하게 할 수 있지만 클라이언트 요청에 따른 insertBoard() 메소드는 실행할 수 없습니다. xml에는 HandlerMapping을 이용해서 insertBoard.do 요청을 처리했습니다. 이를 어노테이션으로 변경할 경우 @RequestMapping을 이용할 수 있습니다. 

 

package tommy.spring.web.board;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
public class InsertBoardController {
	//요청 매핑
	@RequestMapping(value = "/insertBoard.do")
	public void insertBoard(HttpServletRequest request) {
		System.out.println("글 등록 처리");

 

* 참고사항 : @RequestMaping의 value 속성은 생략할 수 있으면 생략합니다. 

 

 

 

※ xml 설정과 비교

 

<!-- 아래 xml 설정과 동일한 결과를 가진다. -->

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/insertBoard.do">insertBoard</prop>
</props>
</property>
</bean>
<bean id="insertBoard" class="tommy.spring.web.board.InsertBoardController"></bean>

 

 

Command 객체 

 

대부분 Controller는 사용자의 입력 정보를 추출하여 VO 객체에 저장 한 뒤 비지니스 컴포넌트의 메서드를 호출할 때 VO 객체로 전달합니다.

 

사용자의 입력 정보는 HttpServletRequest 객체의 getParameter() 메서드를 이용하는데 이때 입력 정보의 양이 많아지면 코드가 길어지는 불편함이 생깁니다. 

 

package tommy.spring.web.board;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
public class InsertBoardController {
	public void insertBoard(HttpServletRequest request) {
		System.out.println("글 등록 처리");
		// 1. 사용자 입력 정보 추출
        // 입력 정보의 양이 많아지면 코드가 길어지고 가독성의 문제 발생
		String title = request.getParameter("title");
		String writer = request.getParameter("writer");
		String content = request.getParameter("content");

 

 

이러한 문제를 Command 객체를 이용해서 해결 가능합니다. 

 

 

* InsertBoardController

 

 

package tommy.spring.web.board;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
public class InsertBoardController {
	@RequestMapping(value = "/insertBoard.do")
    // 매개변수로 BoardVO, BoardDAO 주입 
	public String insertBoard(BoardVO vo, BoardDAO boardDAO) {
		System.out.println("글 등록 처리");
		boardDAO.insertBoard(vo);
		return "getBoardList.do"; // 리턴 타입을 String 변환 후 .do로 반환
	}
}

 

위와 같이 매개변수에 vo, dao와 같은 class를 주입 해주면 스프링 컨테이너가 해당 객체를 생성하여 자동으로 전달해줍니다. 그리고 이런 방식으로 주입되는 class를 다른 말로 command 객체라고 부르기도 합니다. 

또한 command 객체에 해당하는 클래스는 Setter 메서드가 반드시 정의 되어 있어야 합니다. 

 

 

(주의사항 : DAO는 원래 Service Controller를 통해서 접근하는 것이 올바른 방식인데 편의를 위해 지금은 둘다 생성)

 

컨트롤러 메서드가 실행되고 View 경로를 리턴하면 기본적으로 포워딩 방식으로 처리합니다. 만약 리다이렉트 방식을 구현하려면 "redirect:" 라는 접두어를 붙여야 합니다. 

 

 

* 포워딩 방식과 리다이렉트 방식 차이 

 

https://kotlinworld.com/329

 

리다이렉트(Redirect)와 포워드(Forward)의 차이는 무엇인가?

리다이렉트와 포워드 특정 URL 접속 시 리다이렉트 또는 포워드가 일어나게 되면 작업 중인 페이지가 전환된다. 리다이렉트와 포워드는 페이지가 전환된다는 점에서 비슷한 역할을 한다. 하지만

kotlinworld.com

 

 

 

@Controller , @RequestMapping , Command 객체를 활용한 CRUD

 

 

package tommy.spring.web.board;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
public class BoardController {
		@RequestMapping("/insertBoard.do")
		public String insertBoard(BoardVO vo, BoardDAO boardDAO) {
				System.out.println("글 등록 처리");
				boardDAO.insertBoard(vo);
				return "getBoardList.do";
		}
		@RequestMapping("/updateBoard.do")
        public String updateBoard(BoardVO vo, BoardDAO boardDAO) {
                System.out.println("글 수정 기능 처리");
                boardDAO.updateBoard(vo);
                return "getBoardList.do";
		}
        @RequestMapping("/deleteBoard.do")
        public String deleteBoard(BoardVO vo, BoardDAO boardDAO) {
                System.out.println("글 삭제 처리");
                boardDAO.deleteBoard(vo);
                return "getBoardList.do";
        }
        @RequestMapping("/getBoard.do")
        public ModelAndView getBoard(BoardVO vo, BoardDAO boardDAO, ModelAndView mav) {
                System.out.println("글 상세 보기 처리");
                mav.addObject("board", boardDAO.getBoard(vo));
                mav.setViewName("getBoard.jsp");
                return mav;
        }
        @RequestMapping("/getBoardList.do")
        public ModelAndView getBoardList(BoardVO vo, BoardDAO boardDAO, ModelAndView mav) {
                System.out.println("글 목록 검색 처리");
                mav.addObject("boardList", boardDAO.getBoardList(vo)); // Model 정보저장
                mav.setViewName("getBoardList.jsp"); // View 정보저장
                return mav;
        }

 

요청 방식에 따른 처리 

 

@RequestMapping 어노테이션에 method 속성을 설정하지 않을 경우 GET, POST등 모든 HTTP 전송 방 식을 처리하게 됩니다. @RequestMapping 이용하면 마치 Service처럼 클라이언트의 요청 방식(GET/POST)에 따라 수행될 메서드를 다르게 처리할 수 있습니다.

 

 

package tommy.spring.web.user;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import tommy.spring.web.user.impl.UserDAO;
@Controller
public class LoginController {
	@RequestMapping(value ="/login.do", method = RequestMethod.GET)
	public String loginView(UserVO vo) {
            System.out.println("로그인 화면으로 이동");
            vo.setId("test");
            vo.setPassword("test");
            return "login.jsp";
	}
	@RequestMapping(value="/login.do", method = RequestMethod.POST)
    public String login(UserVO vo, UserDAO userDAO) {
    		System.out.println("로그인 인증 처리");
    		if (userDAO.getUser(vo) != null) {
					return "getBoardList.do";
			} else {
					return "login.jsp";
			}
	}
}

 

위와 같이 GET 방식으로 요청할 경우 로그인 인증처리를 수행하지 않고 바로 화면으로 이동되고 POST일 때 인증절차를 진행합니다. 

 

 

@ModelAttribute 

커맨드 객체 앞에 붙여서 설정 가능한 어노테이션

 

1. 커맨드 객체를 제일 먼저 초기화시켜주는 역할 

2. 화면까지  값을 전달 하는 역할 

3. 커맨드 객체의 이름을 변경하는 역할 

 

  사용 예시 
LoginController @RequestMapping(value ="/login.do", method = RequestMethod.GET)
public String loginView(@ModelAttribute("user") UserVO vo) {
                                     System.out.println("로그인 화면으로 이동");
                                     vo.setId("test");
                                     vo.setPassword("test");
                                     return "login.jsp";
}
login.jsp  <tr>
                  <td> 아이디 </td>
                  <td><input type="text" name="id"     value="${user.id}" /> </td>
</tr>
<tr>
                   <td>비밀번호</td>
                   <td><input type="password" name="password"   
                                                                            value="${user.password}"/></td> 

 

Servlet API 

 

Spring MVC는 Controller 메서드의 매개변수로 다양한 Servlet API를 사용할 수 있도록 지원합니다. 

Spring MVC가 제공하는 어노테이션을 이용해서 헤더, 쿠키, 세션 등의 정보에 접근 할 수 있기 때문에 서블릿 API를 직접 사용해야 하는 경우는 드물지만 아래의 경우에는 서블릿 API를 사용하는 것이 유리합니다. 

 

1. HttpSession의 생성을 직접 제어해야 하는 경우 

2. 컨트롤러에서 쿠키를 생성해야 하는 경우 

 

* HttpSession 사용 주의 사항 : HttpSession타입의 파라미터를 가질 경우 세션이 생성됩니다. 

 

 

* 참고 예시 

@RequestMapping("/someURL")
public ModelAndView process(HttpSession session, ...) {
if(session != null){//항상 true이기 때문에 이 방식으로 null check을 하는 것은 좋지 않다. 
// 중략 ... }


public ModelAndView process(HttpServletRequest request, ...) {
if(someCondition){
HttpSession session = request.getSession(false); // 이렇게 세션을 가져올 때 false check 후 
//세션이 false일 때는 생성하지 않는 방식이 좋다.
}
// 중략 ... }

 

 

* 활용 

 

- LoginController

 

<!-- 상단 부분 생략 -->
@RequestMapping(value = "/login.do", method = RequestMethod.POST)
public String login(UserVO vo, UserDAO userDAO, HttpSession session) {
		System.out.println("로그인 인증 처리");
		UserVO user = userDAO.getUser(vo);
		if (user != null) {
        			// 세션에 속성을 key값 value로
					session.setAttribute("userName", user.getName());
					return "getBoardList.do";
		} else {
				return "login.jsp";		
		}
	}
}

 

- getBoardList.jsp

 

<!-- 상단 부분 생략 -->
<html>
<head>
<meta charset="UTF-8">
<title>Board List</title>
</head>
<body>
<h1>글 목록</h1>
<!-- 세션에 등록된 key name으로 가져온다. -->
<h3>${userName} 회원님 환영합니다.<a href="logout.do">Log-Out</a></h3>

 

Controller의 리턴 타입 

 

크게 String과 ModelAndView로 나눌 수 있습니다. String으로 설정하면 완벽한 View 이름의 문자열 형태로 리턴 하겠다는 것이고 ModelAndView를 설정하면 검색된 Model 데이터와 View 이름을 모두 저장하여 리턴합니다. 

 

리턴타입 사용 예시 
ModelAndView public ModelAndView login(UserVO vo, UserDAO userDAO,                                                                                                                                       ModelAndView mav){
                                       System.out.println("로그인 인증 처리");
                                       UserVO user = userDAO.getUser(vo);
                                       if (user != null) {
                                                     mav.setViewName("getBoardList.jsp");
                                        } else {
                                                      mav.setViewName("login.jsp");
                                        }
                                        return mav;
                      }
String public String login(UserVO vo, UserDAO userDAO){
                          System.out.println("로그인 인증 처리");
                          UserVO user = userDAO.getUser(vo);
                          if(user != null){
                                         return "getBoardList.jsp";
                          }else{
                                          return "login.jsp";
                          }
             }

 

 

@RequestParam 

 

Command 객체를 이용하면 클라이언트에서 넘겨준 요청 파라미터 정보를 받아낼 수 있습니다. 그렇다면 Command 객체에 없는 파라미터의 전달 방식은 어떻게 할까요?

 

-> 스프링 MVC에서는 @RequestParam을 제공합니다.

 

1. @RequestParam 어노테이션이 적용된 파라미터가 String이 아닐 경우 실제 타입에 따라서 알맞게 타입 변환을 수행합니다.

 

2. 만약 변환에 실패하면 스프링 MVC는 잘못된 요청(Bad Request)를 의미하는 400 응답코드를 웹 브라우저에 전송합니다.

 

3. @RequestParam 어노테이션이 적용된 파라미터는 기본적으로 필수 파라미터이기 때문에 값이 넘어오지 않는 경우 오류를 발생시킵니다. 

 

4. 필수가 아닌 파라미터인 경우 required 속성 값을 false로 지정하면 됩니다. (값이 없어도 오류 x)

 

5. 필수가 아닌 요청 파라미터의 값이 존재하지 않을 경우 null값을 할당합니다. 만약 null 값을 할당할 수 없는 기본 데이터 타입인 경우는 타입 변환 에러가 발생합니다.

 

 

* 예시 

 

- 게시글 검색 기능 관련 JSP 

 

<!-- 검색 시작 -->
<form action="getBoardList.do" method="post">
<table border="1">
<tr>
	<td>
		<select name="searchCondition">
				<option value="TITLE">제목</option>
				<option value="CONTENT">내용</option>
		</select>
		<input type="text" name="searchKeyword" />
		<input type="submit" value="검색" />
	</td>
</tr>
</table>
</form><br/>
<!-- 검색 종료 -->

 

- BoardController

 

package tommy.spring.web.board;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
public class BoardController {
 	@RequestMapping("/getBoardList.do")
 	public String getBoardList(
    	// 게시글 정보 외의 검색 기능에 대한 파라미터를 따로 받기 위해 사용
		@RequestParam(value = "searchCondition", defaultValue = "TITLE", required = false) 
											String condition,
		@RequestParam(value = "searchKeyword", defaultValue = "", required = false) 
								String keyword, BoardDAO boardDAO, Model model) {
        System.out.println("글 목록 검색 처리");
        System.out.println("검색 조건 : " + condition);
        System.out.println("검색 단어 : " + keyword);

 

 

- BoardVO 

 

package tommy.spring.web.board;
import java.sql.Date;
public class BoardVO {
private int seq;
private String title;
private String writer;
private String content;
private Date regDate;
private int cnt;

//해당 속성 추가 
private String searchCondition;
private String searchKeyword;

//해당 속성 getter, setter 추가 
public String getSearchCondition() {
return searchCondition;
}
public void setSearchCondition(String searchCondition) {
this.searchCondition = searchCondition;
}
public String getSearchKeyword() {
return searchKeyword;
}
public void setSearchKeyword(String searchKeyword) {
this.searchKeyword = searchKeyword;
}
<!-- 하단 부분 생략 --

 

 

 

@SessionAttributes

 

@SessionAttributes는 수정작업을 처리할 때 유용하게 사용할 수 있는 어노테이션입니다.

 

예를 들어 상세화면에서 게시 글을 수정한다고 가정하면 글 수정 버튼을 클릭하면 사용자가 입력한 수정 내용을 가지고 “/updateBoard.do”요청을 전송할 것이고 BoardController의 updateBoard() 메서 드에서는 사용자가 입력한 정보를 이용해서 글 수정 작업을 처리할 것입니다.

 

그런데 문제는 사용자가 입력한 정보가 제목과 내용뿐이고 작성자 정보는 전달되지 않았기 때문에 Command 객체인 BoardVO에 writer 정보가 저장되지 않습니다.

 

물론 BoardDAO에 updateBoard()메서드가 제목과 내용 정보만 수정하도록 구현되었다면 문제가 없습니다. 하지만 만약 아래처럼 SQL문을 수정하여 작성자 정보까지 전달하게 되면 writer 컬럼은 null 값으로 수정됩니다. 

 

private final String BOARD_UPDATE = "update myboard set title=?, writer=?, content=? where seq=?";

 

스프링 MVC에서는 이러한 문제를 방지하기 위해 @SessionAttribute를 제공하고 있습니다.

 

 

* 예시 

 

-BoardController

 

package tommy.spring.web.board;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
public class BoardController {
        @RequestMapping("/updateBoard.do")
        public String updateBoard(BoardVO vo, BoardDAO boardDAO) {
                    System.out.println("글 수정 기능 처리");
                    System.out.println("작성자 이름 : " + vo.getWriter());
                    boardDAO.updateBoard(vo);
                    return "getBoardList.do";
		}
<!-- 하단 부분 생략 -->

 

 

null로 넘어온다.

 

 

 

-BoardController 수정

 

package tommy.spring.web.board;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import tommy.spring.web.board.impl.BoardDAO;
@Controller
@SessionAttributes("board") //ModelAttribute로 받은 board를 session에도 등록한다.
public class BoardController {
		@RequestMapping("/updateBoard.do")
		public String updateBoard(@ModelAttribute("board") BoardVO vo, BoardDAO boardDAO) {
                System.out.println("글 수정 기능 처리");
                System.out.println("번호 : " + vo.getSeq());
                System.out.println("제목 : " + vo.getTitle());
                System.out.println("작성자 : " + vo.getWriter());
                System.out.println("내용 : " + vo.getContent());
                System.out.println("등록일 : " + vo.getRegDate());
                System.out.println("조회수 : " + vo.getCnt());
                boardDAO.updateBoard(vo);
                return "getBoardList.do";
		}
        @RequestMapping("/getBoard.do")
        public String getBoard(BoardVO vo, BoardDAO boardDAO, Model model) {
                System.out.println("글 상세 보기 처리");
                model.addAttribute("board", boardDAO.getBoard(vo));
                return "getBoard.jsp";
		}

 

session에서 값을 가져와서 정상적으로 값이 반환된다.

 

-> 사용자가 상세화면을 요청하면 getBoard() 메서드는 검색결과인 BoardVO 객체를 board라는 이름으로 Model에 저장합니다. 이때 BoardController 클래스에 선언된 @SessionAttributes("board") 설정에 의해 Model에 "board"라는 이름으로 저장되는 데이터 가 있다면 그 데이터를 세션에도 자동으로 저장하는 것입니다.

'Frame Work > Spring' 카테고리의 다른 글

2-Layered Architecture  (0) 2022.11.11
Spring MVC  (0) 2022.10.27
MVC FrameWork 구조  (0) 2022.10.26
MVC pattern  (0) 2022.10.25
Spring Transaction  (0) 2022.10.22