Study Web Development

2월 25일

이전으로

Spring

3. 스프링 AOP

3-3 @Aspect 어노테이션을 이용한 AOP

  1. 새 패키지 kr.spring.ch02 생성 후 클래스 MyFirstAspect 생성
package kr.spring.ch02;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyFirstAspect {
	// 공통 기능 수행
	@Pointcut("execution(public String launch())")
	public void getPointcut() {
		
	}
	
	// @Before("getPointcut()")
	public void before() {
		// 메서드 시작 직전에 동작하는 어드바이스
		System.out.println("Hello Before! **메서드가 호출되기 전에 나온다!");
	}
	
	// @AfterReturning(value = "getPointcut()", returning = "msg")
	public void afterReturning(String msg) {
		// 메서드 호출이 예외를 내보내지 않고 종료했을 때 동작하는 어드바이스
		System.out.println("Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 : " + msg);
	}
	
	// @AfterThrowing(value = "getPointcut()", throwing = "ex")
	public void afterThrowing(Throwable ex) {
		// 메서드 호출이 예외를 던졌을 때 동작하는 어드바이스
		System.out.println("Hello AfterThrowing! **예외가 생기면 나온다! 예외 : " + ex);
	}
	
	// @After("getPointcut()")
	public void after() {
		// 예외가 발생해도 실행됨
		// 메서드 종료 후에 동작하는 어드바이스
		System.out.println("Hello After! **메서드가 호출된 후에 나온다!");
	}
	
	@Around("getPointcut()")
	public String around(ProceedingJoinPoint joinpoint) throws Throwable {
		// 메서드 호출 전후에 동작하는 어드바이스
		System.out.println("Hello Around before! **메서드가 호출되기 전에 나온다!");
		String s = null;
		// try~catch~finally 구조로 명시해야 예외가 발생해도 메서드 실행 후 공통 기능을 수행함
		try {
			// 핵심 기능이 수행된 후 데이터 반환
			s = (String)joinpoint.proceed();
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		finally {
			System.out.println("Hello Around after! **메서드가 호출된 후에 나온다! 반환된 객체 : " + s);
		}
		
		return s;
	}
}
  1. src/main/resourcesapplicationContextAnnote.xml 생성
<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 어노테이션 방식으로 AOP를 구현할 때 사용 -->
	<aop:aspectj-autoproxy/>
	
	<!-- 공통 기능을 구현한 클래스를 bean으로 등록 -->
	<bean id="myFirstAspect" class="kr.spring.ch02.MyFirstAspect"/>
	
	<!-- 핵심 기능을 구현한 클래스를 bean으로 등록 -->
	<bean id="product" class="kr.spring.product.Product"/>
</beans>
  1. 새 클래스 SpringMain 생성
package kr.spring.ch02;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import kr.spring.product.Product;

public class SpringMain {
	public static void main(String[] args) {
		AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContextAnnote.xml");
		
		// 핵심 기능 실행, 설정 파일에 지정한 설정대로 공통 기능이 수행됨
		Product p = (Product)context.getBean("product");
		p.launch();
		
		context.close();
	}
}

4. 스프링 MVC

스프링 MVC의 구성 요소

4-1 스프링 MVC 설정

4-1-1 DispatcherServlet 설정 및 스프링 컨텍스트 설정
  1. 새 SpringLegacyProject 생성하여 프로젝트명을 ch04MVC, 최상위 패키지명을 kr.spring.mvc로 지정하고 Spring MVC Project 선택
  2. pom.xml에서 <properties> 태그 사이의 내용을 다음처럼 변경
		<java-version>1.8</java-version>
		<org.springframework-version>5.0.20.RELEASE</org.springframework-version>
  1. Java Build Path와 Project Facets에서 버전을 1.8로 변경
  2. 프로젝트명 오른쪽 클릭하고 Properties 선택하고 Web Project Settings 선택하면 context root 확인 가능
    • 기본값은 프로젝트 생성시 지정한 최상위 패키지명의 마지막 부분
  3. src/main/webapp/WEB-INFweb.xml 열고 <web-app> 태그 사이의 내용을 다음처럼 변경
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
	<filter> <!-- POST 방식 전송시 인코딩 처리를 위해 내장된 필터를 사용; 필터는 요청이 DispatcherServlet에 의해 다뤄지기 전,후에 동작 -->
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping> <!-- 컨텍스트 루트 하위의 모든 요청(*.do뿐만 아니라 *.jsp 등 확장자가 다른 경우까지 포함)에 대해 인코딩 필터 처리하도록 설정 -->
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
  1. webapp 폴더 오른쪽 클릭하고 index.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Spring MVC</title>
</head>
<body>
Hello Spring!!
</body>
</html>
  1. Servers 탭에서 새 서버 생성하기 선택하여 Apache-Tomcat v9.0 Server 선택하고 Next
  2. Browse... 선택하여 경로를 C:\javaWork\apps\apache-tomcat-9.0.55로 지정하고 Next 선택한 다음 ch04MVC를 Add하고 Finish
  3. src/main/webapp/WEB-INF/spring/appServlet에서 servlet-context.xml 열고 다음을 삭제
	<context:component-scan base-package="kr.spring.mvc" />
  1. kr.spring.mvc 패키지 삭제
4-1-2 컨트롤러 구현 및 설정 추가
  1. 새 패키지 kr.spring.ch01.controller 생성 후 새 클래스 HelloController 생성
package kr.spring.ch01.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller // 어노테이션으로 클래스를 HandlerMapping에 등록
public class HelloController {
	// 요청 URL과 실행 메서드 연결
	@RequestMapping("/hello.do")
	public ModelAndView hello() { // 요청 매핑은 어노테이션으로 처리되기 때문에 메서드명은 자유롭게 지정 가능
		ModelAndView mav = new ModelAndView();
		// 뷰 이름 지정
		mav.setViewName("hello"); // JSP 파일명만 지정; servlet-context.xml에 설정된 prefix와 suffix를 조합하여 뷰의 전체 경로를 얻게 됨
		// 뷰에서 사용할 데이터 세팅
		mav.addObject("greeting", "안녕하세요"); // addOjbect() 메서드로 ModelAndView 객체에 속성명과 속성값을 저장하면 DispatcherServlet에 의해 해당 데이터가 request로 넘겨짐
		return mav; // DispatcherServlet에 ModelAndView 객체를 반환
	}
}
4-1-3 servlet-context.xml 설정
  1. src/main/webapp/WEB-INF/spring/appServlet에서 servlet-context.xml 열고 <beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- MVC 기본 설정 -->
	<beans:bean id="helloController" class="kr.spring.ch01.controller.HelloController"/>
4-1-4 뷰 코드 구현
  1. webapp에서 index.jsp<body> 태그 사이의 내용을 다음처럼 수정
<a href="${pageContext.request.contextPath}/hello.do">HelloController</a><br>
  1. src/main/webapp/views에서 home.jsp 삭제하고 새 JSP 파일 hello.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>인사</title>
</head>
<body>
인사말 : <strong>${greeting}</strong>
</body>
</html>
@RequestParam
  1. 새 패키지 kr.spring.ch02.controller 생성 후 새 클래스 SearchController 생성
package kr.spring.ch02.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SearchController {
	/*
	 * @RequestParam 어노테이션은 HTTP 요청 파라미터를 메서드의 인자로 전달하며, 파라미터명과 호출 메서드의 인자명이 같으면 어노테이션은 생략 가능
	 * 자료형 역시 인자의 자료형에 맞게 자동으로 형변환됨
	 * 
	 * @RequestParam 어노테이션은 required 속성의 기본값이 true라 파라미터를 전달하지 않으면 400 오류가 발생
	 * 해당 속성을 false로 설정하거나 또는 아예 어노테이션을 생략하면 파라미터 값이 없을 때 인자가 null로 처리되어 400 오류가 발생하지 않음
	 * 단, 인자의 자료형이 int 등 객체가 아닌 경우에는 null을 처리할 수 없어 500 오류가 발생하므로 defaultValue 속성에 값을 지정하는 방식을 사용해야 함
	 */
	@RequestMapping("/search/internal.do")
	public ModelAndView searchInternal(@RequestParam("query") String name, @RequestParam(value = "p", defaultValue = "1") int pageNumber) {
		System.out.println("name = " + name + ", p = " + pageNumber);
		
		return new ModelAndView("search/internal"); // 뷰만 반환시에는 ModelAndView 생성자에 뷰 이름을 전달
	}
	
	@RequestMapping("/search/external.do")
	public ModelAndView searchExternal(@RequestParam(value = "query", required = false) String query, @RequestParam(value = "p", defaultValue = "1") int pageNumber) {
		System.out.println("query = " + query + ", p = " + pageNumber);
		
		return new ModelAndView("search/external");
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- 파라미터(=GET 방식)로 전송된 데이터 처리 -->
	<beans:bean id="searchController" class="kr.spring.ch02.controller.SearchController"/>
  1. index.jsp<body> 태그 사이에 다음의 내용을 추가
<a href="${pageContext.request.contextPath}/search/internal.do?query=dev">SearchController : internal.do</a><br>
<a href="${pageContext.request.contextPath}/search/external.do">SearchController : external.do</a><br>
  1. views 폴더에 하위 폴더로 search를 만들고 새 JSP 파일 internal.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>내부 검색</title>
</head>
<body>
내부 검색
</body>
</html>
  1. search 폴더에 새 JSP 파일 external.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>외부 검색</title>
</head>
<body>
외부 검색
</body>
</html>
@GetMapping, @PostMapping, @ModelAttribute
  1. 새 패키지 kr.spring.ch03.controller 생성 후 새 클래스 NewArticleController 생성
package kr.spring.ch03.controller;

import org.springframework.beans.factory.annotation.Autowired;
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.RequestMethod;

import kr.spring.ch03.service.ArticleService;
import kr.spring.ch03.vo.NewArticleVO;

@Controller
public class NewArticleController {
	@Autowired // 프로퍼티에 @Autowired 명시한 경우, setter는 명시하지 않아도 어노테이션에 의해 자동으로 생성됨
	private ArticleService articleService;
	
	// GET 방식으로 폼 호출
	// @RequestMapping(value = "/article/newArticle.do", method = RequestMethod.GET)
	@GetMapping("/article/newArticle.do") // Spring 4.3에 추가된 보다 간편한 어노테이션
	public String form() {
		return "article/newArticleForm"; // 데이터 없이 뷰만 반환하는 경우, 메서드 반환형을 String으로 지정하고 뷰 이름을 문자열로 반환하면 DispatcherServlet에서 처리
	}
	
	/*
	 * 1) 호출 메서드에 인자로 자바빈을 명시하면, 실행시 자바빈을 생성해서 request에 전달된 데이터의 파라미터명과 자바빈의 프로퍼티명을 대조해서 일치하면 데이터를 프로퍼티에 세팅
	 * 2) 호출 메서드에 인자로 자바빈을 명시하면, 자바빈의 클래스명 첫 글자를 소문자로 바꾼 이름을 속성명으로 사용하여 자바빈을 request 영역에 저장
	 * 3) @ModelAttribute 어노테이션을 이용해서 자바빈을 지정하면, 자바빈을 request 영역에 저장시 속성명을 지정 가능(지정하지 않으면 어노테이션 사용하지 않을 경우와 동일한 속성명 사용)
	 */
	
	// POST 방식으로 폼으로부터 데이터를 전달받음
	// @RequestMapping(value = "/article/newArticle.do", method = RequestMethod.POST)
	@PostMapping("/article/newArticle.do") // Spring 4.3에 추가된 보다 간편한 어노테이션
	public String submit(NewArticleVO vo) {
		articleService.writeArticle(vo);
		
		return "article/newArticleSubmitted";
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- POST 방식으로 전송된 데이터 처리 -->
	<beans:bean id="newArticleController" class="kr.spring.ch03.controller.NewArticleController"/>
  1. index.jsp<body> 태그 사이에 다음의 내용을 추가
<a href="${pageContext.request.contextPath}/article/newArticle.do">NewArticleController</a><br>
  1. views 폴더에 하위 폴더로 article을 만들고 새 JSP 파일 newArticleForm.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시글 쓰기</title>
</head>
<body>
게시글 쓰기 입력 폼
<form action="newArticle.do" method="post">
	<input type="hidden" name="parentId" value="7">
	제목 : <input type="text" name="title"><br>
	내용 : <textarea rows="5" cols="30" name="content"></textarea><br>
	<input type="submit" value="전송">
</form>
</body>
</html>
  1. 새 패키지 kr.spring.ch03.vo 생성하고 새 클래스 NewArticleVO 생성
package kr.spring.ch03.vo;

public class NewArticleVO {
	private String title;
	private String content;
	private int parentId;
	
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public int getParentId() {
		return parentId;
	}
	public void setParentId(int parentId) {
		this.parentId = parentId;
	}
	
	@Override
	public String toString() {
		return "NewArticleVO [title=" + title + ", content=" + content + ", parentId=" + parentId + "]";
	}
}
  1. article 폴더에 새 JSP 파일 newArticleSubmitted.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시글 등록 완료</title>
</head>
<body>
게시글 등록이 완료되었습니다.<br>
<br>
제목 : ${newArticleVO.title}<br>
내용 : ${newArticleVO.content}<br>
아이디 : ${newArticleVO.parentId}
</body>
</html>
  1. 새 패키지 kr.spring.ch03.service 생성하고 새 클래스 ArticleService 생성
package kr.spring.ch03.service;

import kr.spring.ch03.vo.NewArticleVO;

public class ArticleService {
	public void writeArticle(NewArticleVO vo) {
		System.out.println("신규 게시글 등록 : " + vo);
	}
}
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
	<!-- @Autowired 사용을 위해서  -->
	<context:annotation-config/>

	<!-- POST 방식으로 전송된 데이터 처리 -->
	<beans:bean id="articleService" class="kr.spring.ch03.service.ArticleService"/>
@CookieValue
  1. 새 패키지 kr.spring.ch04.controller 생성하고 새 클래스 CookieController 생성
package kr.spring.ch04.controller;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

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

@Controller
public class CookieController {
	@RequestMapping("/cookie/make.do")
	public String make(HttpServletResponse response) { // 메서드가 구동될 때 인자의 자료형을 확인하여 일치하는 객체가 있으면 전달; HttpServletResponse 객체는 이미 생성되어 있으므로 전달됨
		// 쿠키를 생성해서 클라이언트에 전송
		response.addCookie(new Cookie("auth", "1"));
		
		return "cookie/make";
	}
	
	/*
	 * @CookieValue 어노테이션을 이용하면 쿠키 값을 메서드 인자로 전달받을 수 있음
	 * 해당 쿠키가 존재하지 않으면 기본적으로 400 오류가 발생
	 * @CookieValue(value = "쿠키 이름", required = false)로 지정시, 쿠키가 존재하지 않으면 인자에 null 값을 전달
	 * @CookieValue(value = "쿠키 이름", defaultValue = "값")로 지정시, 쿠키가 존재하지 않으면 인자에 defaultValue에 지정했던 값을 전달
	 */
	
	@RequestMapping("/cookie/view.do")
	public String view(@CookieValue(value = "auth", defaultValue = "0") String auth) {
		System.out.println("auth 쿠키 : " + auth);
		
		return "cookie/view";
	}
}
  1. views 폴더에 하위 폴더로 cookie 생성하고 새 JSP 파일 make.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키</title>
</head>
<body>
쿠키를 생성함
</body>
</html>
  1. servlet-context.xml<beans:beans> 태그 사이에 다음의 내용을 추가
<beans:bean id="cookieController" class="kr.spring.ch04.controller.CookieController"/>
  1. index.jsp<body> 태그 사이에 다음 내용을 추가
<a href="${pageContext.request.contextPath}/cookie/make.do">CookieController : make.do</a><br>
<a href="${pageContext.request.contextPath}/cookie/view.do">CookieController : view.do</a><br>
  1. cookie 폴더에 새 JSP 파일 view.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키</title>
</head>
<body>
쿠키 확인
</body>
</html>

다음으로