1.2 (AOP 동작방식/SpringMVC-기본 출력, @RequestParam (String,int 값 생략 시) )
<DI 복습>
DI = 컨테이너에 어떻게 클래스를 넣을 것이냐를 얘기하는 것 (의존관계 주입)
자바코드기반설정은 설정파일이 따로 없음
대신 ch19에서 작성했던 SpringConfig.java에 @Configuration을 적어서 얘는 설정파일이다 라고 명시함
+@Bean 이라고 명시해서 메서드 명이 빈의 이름이 됨
+메인클래스 AnnotaionConfigApplication을 만들어서 ()안에 클래스 명을 직접 명시해야함
new AnnotationConfigApplicationContext(SpringConfig.class);
<스프링 - AOP>
관점지향 프로그래밍: 공통으로 사용하는 기능들을 외부의 독립된 클래스로 분리,
해당 기능을 프로그램 코드에 직접 명시하지 않고 선언적으로 처리하여 적용하는 것
선언적으로 처리하여 적용하는 것 == 설정파일이나 어노테이션으로 처리하는 등 명시하는 행동
자동스캔기능을 중심으로 쓰기 때문에 다운그레이드 후 학습 진행했음
ch09 우클릭 > build path > project facets > java-11 > apply and close
Spring에서의 AOP에는 구현 가능한 Advice 종류가 5가지다
Before 1 After 3 Around (전후) 1
종류 | 설명 |
Before Advice | 대상 객체의 메서드 호출 전에 공통 기능을 실행 |
After Returning Advice | 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능을 실행 |
After Throwing Advice | 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행 |
After Advice | 대상 객체의 메서드를 실행하는 도중에 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행. |
Around Advice | 대상 객체의 메서드 실행 전, 후 또는 예외 발생 시점에 공통 기능을 실행하는데 사용 |
1. Before Advice (@Before)
대상 객체의 메서드 호출 전에 공통 기능을 실행
핵심기능
kr.spring.product
Product (class)
package kr.spring.product;
public class Product {
//핵심 기능 수행
public String launch() {
System.out.println("launch() 메서드 출력");
return "[상품 출시]";
}
}
공통기능
kr.spring.ch20
MyFirstAdvice
package kr.spring.ch20;
//공통 기능을 수행하는 클래스
public class MyFirstAdvice {
/*
* 구현 가능한 Advice(언제 공통 기능을 핵심 로직에 적용할 지를 정의) 종류
* 종류 설명
* Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행
* After Returning Advice 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능을 실행 (예외 발생하면 공통 기능 실행 못함)
* After Throwing Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행
* After Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행
* (try~catch~finally의 finally 블럭과 비슷)
* Around Advice 대상 객체의 메서드 실행 전,후 또는 예외 발생 시점에 공통 기능을 실행
*/
public void before(){
System.out.println("Hello Before! **메서드가 호출되기 전에 나온다!");
}
}
설정파일
applicationContextAOP.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">
<!--
AspectJ의 Pointcut 표현식
execution(Integer kr.spring.ch01..WriteArticleService.write(..))
리턴 타입이 Integer인 WriteArticleService의 write() 메서드(파라미터는 0개 이상)
execution(* kr.spring.ch01.*.*())
kr.spring.ch01 패키지의 파라미터가 없는 모든 메서드 호출
execution(* kr.spring.ch01..*.*(..))
kr.spring.ch01 패키지 및 하위 패키지에 있는 파라미터가 0개 이상인 메서드 호출
execution(* get*(*))
이름이 get으로 시작하고 1개의 파라미터를 갖는 메서드 호출
execution(* get*(*,*))
이름이 get으로 시작하고 2개의 파라미터를 갖는 메서드 호출
execution(* read*(Integer,..)) / 타입 명시가능 ..이 0개기 때문에 Integer,.. 이면 1개 이상
메서드 이름이 read로 시작하고 첫번째 파라미터 타입이 Integer이며, 1개 이상의 파라미터를 갖는 메서드 호출
-->
<!-- 공통기능이 구현된 클래스 -->
<bean id="myFirstAdvice" class="kr.spring.ch20.MyFirstAdvice"/>
<!-- 핵심기능이 구현된 클래스 -->
<bean id="product" class="kr.spring.product.Product"/>
<!-- AOP 설정 -->
<aop:config>
<!-- 공통 기능을 구현한 클래스 지정 -->
<aop:aspect id="aspect" ref="myFirstAdvice">
<!-- 공통 기능을 적용할 클래스(핵심 기능을 구현한 클래스)를 검색 -->
<aop:pointcut expression="execution(public String launch())" id="publicMethod"/>
<!-- 핵심 기능을 실행할 때 어느 시점에 공통 기능을 적용할지 지정 -->
<aop:before method="before" pointcut-ref="publicMethod"/>
</aop:aspect>
</aop:config>
</beans>
AOP에서 핵심기능을 검색해서 사용하는데 그럴 때 사용하는 게 AspectJ의 Pointcut 표현식임
핵심기능 (product)와 공통기능(MyFirstAdvice) 둘 다 컨테이너에 넣고 두 기능을 연결해줘야함
<aop:before> 이라고 명시
메인클래스
kr.spring.ch20
SpringMain
package kr.spring.ch20;
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("applicationContextAOP.xml");
//핵심 기능을 수행하는 메서드 호출
Product p = (Product)context.getBean("product");
p.launch();
context.close();
}
}
[console]
Hello Before! **메서드가 호출되기 전에 나온다!
launch() 메서드 출력
2. After Returning Advice (@AfterReturning)
대상 객체의 메서드가 예외없이 실행한 이후에 공통 기능을 실행 (예외가 발생하면 공통 기능을 실행하지 못함)
핵심 기능은 그대로, 공통기능은 위에서 명시했던 파일 바로 아래에 이어서 명시
공통기능
kr.spring.ch20
MyFirstAdvice
package kr.spring.ch20;
import org.aspectj.lang.ProceedingJoinPoint;
//공통 기능을 수행하는 클래스
public class MyFirstAdvice {
/*
* 구현 가능한 Advice(언제 공통 기능을 핵심 로직에 적용할 지를 정의) 종류
* 종류 설명
* Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행
* After Returning Advice 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능을 실행 (예외 발생하면 공통 기능 실행 못함)
* After Throwing Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행
* After Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행
* (try~catch~finally의 finally 블럭과 비슷)
* Around Advice 대상 객체의 메서드 실행 전,후 또는 예외 발생 시점에 공통 기능을 실행
*/
public void before() {
//메서드 시작 직전에 동작하는 어드바이스
System.out.println("Hello Before! **메서드가 호출되기 전에 나온다!");
}
public void afterReturning(String msg) {
//메서드 호출이 예외를 내보내지 않게 종료했을 때 동작하는 어드바이스
System.out.println("Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 : " + msg);
}
}
msg 에는 Product(핵심기능) 의 return을 출력하려고 함
설정파일
applicationContextAOP.xml
<!-- AOP 설정 -->
<aop:config>
<!-- 공통 기능을 구현한 클래스 지정 -->
<aop:aspect id="aspect" ref="myFirstAdvice">
<!-- 공통 기능을 적용할 클래스(핵심 기능을 구현한 클래스)를 검색 -->
<aop:pointcut expression="execution(public String launch())" id="publicMethod"/>
<!-- 핵심 기능을 실행할 때 어느 시점에 공통 기능을 적용할지 지정 -->
<!-- <aop:before method="before" pointcut-ref="publicMethod"/> -->
<aop:after-returning method="afterReturning" pointcut-ref="publicMethod" returning="msg"/> <!-- product return을 받는 과정이 있어야함 -->
</aop:aspect>
</aop:config>
위에서 명시했던 aop:before은 주석처리하고 after-returning 만 실행되게 함
메인클래스
SpringMain (기존 파일) 실행 시
[console]
launch() 메서드 출력
Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 : [상품 출시]
***
이 구문은 예외가 발생하지 않았을 때에만 실행이 되기 때문에
Product (핵심파일)에 강제로 예외를 발생시키면 오류가 남
핵심기능
package kr.spring.product;
public class Product {
//핵심 기능 수행
public String launch() {
System.out.println("launch() 메서드 출력");
//예외 발생시 호출되는 공통 기능을 테스트하기 위해
System.out.println(20/0); //예외발생 구문 강제로 주입
return "[상품 출시]";
}
}
System.out.println(20/0); 을 넣어서 예외가 발생하게 했을 경우
console에서 launch() 메서드 출력 이후 예외가 출력된 것을 볼 수 있음
정상적으로 동작됐을 때에만 공통기능이 수행되기 때문임
3. After Throwing Advice (@AfterThrowing)
대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능 실행
예외가 발생하는 구문을 넣은 Product 파일은 그대로 두고
After Throwing Advice를 테스트
공통기능
MyFirstAdvice
package kr.spring.ch20;
//공통 기능을 수행하는 클래스
public class MyFirstAdvice {
/*
* 구현 가능한 Advice(언제 공통 기능을 핵심 로직에 적용할 지를 정의) 종류
* 종류 설명
* Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행
* After Returning Advice 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능을 실행 (예외 발생하면 공통 기능 실행 못함)
* After Throwing Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행
* After Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행
* (try~catch~finally의 finally 블럭과 비슷)
* Around Advice 대상 객체의 메서드 실행 전,후 또는 예외 발생 시점에 공통 기능을 실행
*/
public void before() {
//메서드 시작 직전에 동작하는 어드바이스
System.out.println("Hello Before! **메서드가 호출되기 전에 나온다!");
}
public void afterReturning(String msg) {
//메서드 호출이 예외를 내보내지 않게 종료했을 때 동작하는 어드바이스
System.out.println("Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 : " + msg);
}
public void afterThrowing(Exception ex) {
//메서드 호출이 예외를 던졌을 때 동작하는 어드바이스
System.out.println("Hello AfterThrowing! **예외가 생기면 나온다! 예외 : " + ex);
}
}
인자인 Exception ex를 통해 예외를 출력할 수 있음
설정파일
applicationContextAOP.xml
<aop:after-throwing method="afterThrowing" pointcut-ref="publicMethod" throwing="ex"/>
aop:config - aop:aspect - aop:pointcut - aop:after-throwing 순대로 명시 (위 기재했기 때문에 생략)
메인클래스
SpringMain (기존 파일) 실행 시
[console]
launch() 메서드 출력
Hello AfterThrowing! **예외가 생기면 나온다! 예외 : java.lang.ArithmeticException: / by zero
***
Product.java 에 있는 예외발생구문을 주석처리한다면?
[console]
launch() 메서드 출력
위처럼 공통기능이 보이지 않음
이러한 원리를 이용해서 웹에서는
예외가 발생하면 자원정리를 한다거나 rollback 처리를 한다거나 하는 데에 쓰인다고 하심
2. After Advice (@After)
대상 객체의 메서드를 실행하는 도중 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행
(try~catch~finally의 finally 블럭과 비슷)
공통기능
MyFirstAdvice
package kr.spring.ch20;
//공통 기능을 수행하는 클래스
public class MyFirstAdvice {
/*
* 구현 가능한 Advice(언제 공통 기능을 핵심 로직에 적용할 지를 정의) 종류
* 종류 설명
* Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행
* After Returning Advice 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능을 실행 (예외 발생하면 공통 기능 실행 못함)
* After Throwing Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행
* After Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행
* (try~catch~finally의 finally 블럭과 비슷)
* Around Advice 대상 객체의 메서드 실행 전,후 또는 예외 발생 시점에 공통 기능을 실행
*/
public void before() {
//메서드 시작 직전에 동작하는 어드바이스
System.out.println("Hello Before! **메서드가 호출되기 전에 나온다!");
}
public void afterReturning(String msg) {
//메서드 호출이 예외를 내보내지 않게 종료했을 때 동작하는 어드바이스
System.out.println("Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 : " + msg);
}
public void afterThrowing(Exception ex) {
//메서드 호출이 예외를 던졌을 때 동작하는 어드바이스
System.out.println("Hello AfterThrowing! **예외가 생기면 나온다! 예외 : " + ex);
}
public void after() {
//메서드 종료 후에 동작하는 어드바이스 (예외가 발생해도 실행됨)
System.out.println("Hello After! **메서드가 호출된 후에 나온다!");
}
}
설정파일
applicationContextAOP.xml
<aop:after method="after" pointcut-ref="publicMethod"/>
aop:config - aop:aspect - aop:pointcut - aop:after 순대로 명시 (위 기재했기 때문에 생략)
메인클래스
SpringMain (기존 파일) 실행 시
[console]
launch() 메서드 출력
Hello After! **메서드가 호출된 후에 나온다!
***
Product.java에서 주석처리한 예외발생 구문에 주석을 제거해도 정상 실행됨
[console]
launch() 메서드 출력
Hello After! **메서드가 호출된 후에 나온다!
Exception in thread "main" java.lang.ArithmeticException: / by zero
2. Around Advice (@Around)
대상 객체의 메서드 실행 전,후 또는 예외 발생 시점에 공통 기능을 실행 (before+after 합친것과 비슷)
전후로 동작되는 것이기 때문에 코드를 작성해야 함
공통기능
MyFirstAdvice
package kr.spring.ch20;
//공통 기능을 수행하는 클래스
public class MyFirstAdvice {
/*
* 구현 가능한 Advice(언제 공통 기능을 핵심 로직에 적용할 지를 정의) 종류
* 종류 설명
* Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행
* After Returning Advice 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능을 실행 (예외 발생하면 공통 기능 실행 못함)
* After Throwing Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행
* After Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행
* (try~catch~finally의 finally 블럭과 비슷)
* Around Advice 대상 객체의 메서드 실행 전,후 또는 예외 발생 시점에 공통 기능을 실행
*/
public void before() {
//메서드 시작 직전에 동작하는 어드바이스
System.out.println("Hello Before! **메서드가 호출되기 전에 나온다!");
}
public void afterReturning(String msg) {
//메서드 호출이 예외를 내보내지 않게 종료했을 때 동작하는 어드바이스
System.out.println("Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 : " + msg);
}
public void afterThrowing(Exception ex) {
//메서드 호출이 예외를 던졌을 때 동작하는 어드바이스
System.out.println("Hello AfterThrowing! **예외가 생기면 나온다! 예외 : " + ex);
}
public void after() {
//메서드 종료 후에 동작하는 어드바이스 (예외가 발생해도 실행됨)
System.out.println("Hello After! **메서드가 호출된 후에 나온다!");
}
//반환 타입을 지정하면 이 기능 이후에 실행되는 공통 기능에서 반환하는
//데이터를 받을 수 있음
public String around(ProceedingJoinPoint joinPoint)throws Throwable{ //String도 되고 void도 됨
//메서드 호출 전후에 동작하는 어드바이스
System.out.println("Hello Around before! **메서드가 호출되기 전에 나온다");
String s = null;
//try~catch~finally 구조로 명시해야 예외가 발생해도 메서드 실행 후 공통 기능을 수행
try {
//핵심 기능이 수행된 후 데이터 반환
s = (String)joinPoint.proceed();
}catch(Exception e) {
e.printStackTrace();
}finally { //예외가 발생해도 finally는 실행됨
System.out.println("Hello Around after! **메서드가 호출된 후에 나온다! 반환된 객체 : " + s);
}
return s;
}
}
ProceedingJoinPoint = 핵심기능을 호출하는 기능을 가지고 있음
설정파일
applicationContextAOP.xml
<aop:around method="around" pointcut-ref="publicMethod"/>
aop:config - aop:aspect - aop:pointcut - aop:around 순대로 명시 (위 기재했기 때문에 생략)
메인클래스
SpringMain (기존 파일) 실행 시
[예외 발생 시 출력화면]
Hello Around before! **메서드가 호출되기 전에 나온다
launch() 메서드 출력
java.lang.ArithmeticException: / by zero
(오류)
Hello Around after! **메서드가 호출된 후에 나온다! 반환된 객체 : null
[예외 발생하지 않을 시 출력화면]
Hello Around before! **메서드가 호출되기 전에 나온다
launch() 메서드 출력
Hello Around after! **메서드가 호출된 후에 나온다! 반환된 객체 : [상품 출시]
위처럼 설정파일을 이용해서 설정하는건
래거시에서 주로 사용함
설정파일이 아니라 어노테이션을 사용한 예제는 아래에
<SpringMVC>
선생님이 올려주신 ch10-Spring_MVC.war 파일 내려받기 -> war파일 import
autoscan은 안쓸거라 17로 해도 상관없는데 11로 다운그레이드 해서 진행할거기 때문에 다운그레이드 ->
webapp index.jsp 실행시켰을 시에 새 창에 test라고 뜨면 된 것
SpringMVC = 어노테이션 기반
기본적인 설정파일 = 1) servlet-context.xml 2) root-context.xml 3) web.xml (래거시 기준)
부트는 합쳐져서 1개의 설정파일로 사용
1) servlet-context.xml
빈 객체 지정, jsp호출, db연동을 제외한 모든 연동을 여기서 함
2) root-context.xml
데이터베이스를 연동할 때 쓰임
아직 db연동을 안해서 한동안 수정하지 않을거임
3) web.xml
<filter> servlet이 구동되기 전에 동작, utf-8로 encoding을 한다고 적혀있음 (따로 인코딩을 안해서 코드 줄어듦)
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 부분의 DispatcherServlet 가 본래 컨트롤러 역할을 했는데 얘가 컨트롤러 역할을 하긴 하지만 spring에서는 모델클래스를 컨트롤러라고 함
<스프링 MVC의 주요 구성 요소>
구성요소 | 설명 |
DispatcherServlet | 클라이언트의 요청을 전달받는다. 컨트롤러에게 클라이언트의 요청을 전달하고, 컨트롤러가 리턴한 결과값을 View에 전달하여 알맞은 응답을 생성하도록 한다. |
HandlerMapping | 클라이언트의 요청 URL을 어떤 컨트롤러가 처리할지를 결정한다. |
컨트롤러(Controller) | 클라이언트의 요청을 처리한 뒤, 그 결과를 DispatcherServlet에 알려준다. 스트럿츠의 Action과 동일한 역할을 수행한다. |
ModelAndView | 컨트롤러가 처리한 결과 정보 및 뷰 선택에 필요한 정보를 담는다. |
ViewResolver | 컨트롤러의 처리 결과를 생성할 뷰를 결정한다. |
뷰(View) | 컨트롤러의 처리 결과 화면을 생성한다. JSP나 Velocity 템플릿 파일 등을 뷰로 사용한다. |

클라이언트가 요청 ->
dispatcherServlet 이 요청을 받음 ->
HandlerMapping = 요청 url과 실제 모델 클래스를 갖고 있음 (actionmap.properties 역할)
url과 매핑되는 정보를 DispatcherServlet에게 보내줌 ->
객체를 ModelAndView에 저장 ( model = 데이터 view = jsp ) + DispatcherServlet에게 보냄 ->
model(데이터)을 request에 저장 ->
DispatcherServlet이 view 호출
그렇기 때문에 우리는 controller와 view만 생성하면 됨
controller (모델클래스) 만들기
ch10-Spring_MVC
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
public class HelloController {
//요청URL과 실행 메서드 연결
@RequestMapping("/hello.do")
public ModelAndView hello() {
ModelAndView mav = new ModelAndView(); //객체 생성
//뷰 이름 지정
mav.setViewName("hello"); //확장자 명시하지 않고 파일명만 명시
//뷰에서 사용할 데이터 지정
mav.addObject("greeting","안녕하세요");
return mav;
}
}
@Controller // @RequestMapping 사용
jsp의 모델클래스는 요청에 따라 1개의 메서드만 호출할 수 있기 때문에 1개만 만들었는데
스프링에서는 다수의 메서드를 만들 수 있음 (선택) -- 하지만 요청 url은 달라야함
- 어차피 요청 url로 불러오는 것이기 때문에 hello() 라는 메서드명은 중요하지 않지만 겹치면 안됨
- mav.setViewName("hello"); 에서 hello뒤에 /views 나 .jsp 등 경로와 확장자를 붙이지 않는 이유는
servlet-context.xml 의 prefix와 suffix에서 미리 명시했기 때문에 붙이지 않는 것임
뷰 파일
WEB-INF
views
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>
jsp 에선 ${greeting} 이라고만 명시 해도 됨 (컨테이너에서 연결한다면)
설정파일
servlet-context.xml
<!-- MVC 기본 설정 -->
<beans:bean id="helloController" class="kr.spring.ch01.controller.HelloController" />
실행페이지
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>
<a href="${pageContext.request.contextPath}/hello.do">HelloController</a>
</body>
</html>
hello.do 라고 a태그를 걸어줬기 때문에
index 실행 후 HelloController 링크를 클릭한다면 화면에 안녕하세요 문구가 출력됨
HelloController의 mav.addObject("greeting", "안녕하세요")가 request에 저장이 되어 있기 때문에
${greeting} 이라고 호출 가능한 것임
더 확장시켜보기
컨트롤러
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.servlet.ModelAndView;
@Controller
public class SearchController {
//요청URL과 실행 메서드 연결
@RequestMapping("/search/internal.do") //하위 경로를 만들수도 있음
public ModelAndView searchInternal() {
ModelAndView mav = new ModelAndView();
//뷰 이름 지정
mav.setViewName("search/internal");
//뷰에서 호출할 데이터 저장
mav.addObject("query", "내부 검색");
return mav;
}
}
-매핑할 때 xml에서 연결했던 것처럼 controller 안에서 연결해줌
-mav.setViewName("search/internal"); 에서 search앞에 /가 없는 이유는
servlet-context.xml에 <beans:property name="prefix" value="/WEB-INF/views/" /> 처럼 views 뒤에 이미 / 가 있기 때문
뷰 파일
WEB-INF
views(folder)
search(folder)
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>
${query}
</body>
</html>
설정파일
servlet-context.xml
<beans:bean id="searchController" class="kr.spring.ch02.controller.SearchController"/>
실행파일
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>
<a href="${pageContext.request.contextPath}/hello.do">HelloController</a><br>
<a href="${pageContext.request.contextPath}/search/internal.do">SearchController</a><br>
</body>
</html>
index.jsp 실행 시
SearchController 링크를 들어가면 내부검색 문구가 화면에 출력됨
url = http://localhost:8080/ch10-Spring_MVC/search/internal.do
****
/search/internal.do 라고 호출해도 되지만
/search/internal.do?query=apple 이런식으로 호출해도 가능함 (둘다 get방식)
순수 jsp에선 request.getParameter("query") 라고 뽑아냈는데 spring에선 어노테이션을 사용함
@RequestParam("query") 라고 명시하고 데이터를 받을 수 있음
@RequestParam() 이용하여 데이터를 받아보기
전달한 데이터 바로 출력/ 아까 위에서 작성한 내용만 조금 수정
컨트롤러
SearchController.java
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 {
//요청URL과 실행 메서드 연결
@RequestMapping("/search/internal.do") //하위 경로를 만들수도 있음
public ModelAndView searchInternal(@RequestParam("query") String query) {
ModelAndView mav = new ModelAndView();
//뷰 이름 지정
mav.setViewName("search/internal");
//뷰에서 호출할 데이터 저장
mav.addObject("query", query);
return mav;
}
}
public ModelAndView searchInternal() { 구문에서 어노테이션 추가함
public ModelAndView searchInternal(@RequestParam("query") String query)
실행파일
index.jsp
<a href="${pageContext.request.contextPath}/search/internal.do?query=apple">
SearchController - internal.do</a><br>
index.jsp 실행 시
SearchController 링크를 들어가면 apple이 화면에 출력됨
***
public ModelAndView searchInternal(@RequestParam("query") String query) 에서
@RequestParam("query")를 지우고 String query만 있다면 동작이 될까?
Yes -->
어노테이션을 생략해도 위 query와 index.jsp에 명시한 파라미터명이 동일하면 정상실행됨
그러나 어노테이션을 명시한다면 필수적으로 데이터를 넣어야 오류가 나지 않음
1)
SearchController.java- @RequestParam 명시 / index.jsp- /search/internal.do 만 명시
==> 에러 ( Required String parameter 'query' is not present )
어노테이션을 넣게 되면 필수적으로 요청되어야 하는 파라미터기 때문
2)
SearchController.java- String query만 명시 / index.jsp- /search/internal.do 만 명시
==> 정상출력
[console] query = null
다른 타입의 정보를 받을 수 있는지 알아보는 예제
컨트롤러
SearchController.java
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 요청 파라미터를 메서드의 파라미터로 전달
* [형식]
* 1. @RequestParam(요청파라미터네임) 메서드의 인자(파라미터)
* 요청파라미터를 필수적으로 사용하지 않으면 오류 발생
* 아래오 같이 required는 false로 지정하면 요청파라미터가 없어도 오류 발생하지 않음
* @RequestParam(value="query",required=false)
*
* 2. 요청파라미터명과 호출메서드의 인자명이 같으면
* 요청파라미터명 생략 가능 -> @RequestParam String query (어노테이션+메서드인자명)
*
* 3. @RequestParam 생략 가능
* 요청파라미터명과 호출메서드의 인자명을 동일하게 표기
* query를 필수적으로 사용하지 않아도 오류가 발생하지 않음
*/
//요청URL과 실행 메서드 연결
@RequestMapping("/search/internal.do") //하위 경로를 만들수도 있음
public ModelAndView searchInternal(String query) {
System.out.println("query = " + query);
ModelAndView mav = new ModelAndView();
//뷰 이름 지정
mav.setViewName("search/internal");
//뷰에서 호출할 데이터 저장
mav.addObject("query", query);
return mav;
}
@RequestMapping("/search/external.do")
public ModelAndView searchExternal(
@RequestParam("query") String query,
@RequestParam("p") int pageNumber) {//타입이 다른데 정보를 받을 수 있는지 test
System.out.println("query = " + query);
System.out.println("p = " + pageNumber);
ModelAndView mav = new ModelAndView();
//뷰이름 지정
mav.setViewName("search/external");
//뷰에 전달할 데이터 (request에 저장)
mav.addObject("query", query);
mav.addObject("pageNumber", pageNumber);
return mav;
}
}
아래 searchExternal에서 String 타입 query와 int 타입 p의 정보를 각각 받을 수 있는지 알아보자
뷰 파일
WEB-INF
views
search
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>
${query}, ${pageNumber}
</body>
</html>
설정파일은 이미 위에서 빈 설정을 했기 때문에 index에 태그만 추가하면 됨
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>
<a href="${pageContext.request.contextPath}/hello.do">HelloController</a><br>
<a href="${pageContext.request.contextPath}/search/internal.do?query=apple">SearchController - internal.do</a><br>
<a href="${pageContext.request.contextPath}/search/external.do?query=seoul&p=5">SearchController - external.do</a><br>
</body>
</html>
search/external.do?query=seoul&p=5라고 명시
[console] ( SearchController에서 print함 )
query = seoul
p = 5
[출력화면]
SearchController - external.do 태그 누르면
seoul, 5 나옴
Integer.parseInt 라고 따로 명시 안해도 Spring에서는 int라고 하면 자동으로 숫자로 받아짐
***
위 파라미터에서 query, pageNumber 둘다
required=false를 같이 기재하고 query만 값을 넘기면 어떻게 되는가?
SearchController
@RequestMapping("/search/external.do")
public ModelAndView searchExternal(
@RequestParam(value="query", required=false) String query,
@RequestParam(value="p", required=false) int pageNumber) {
System.out.println("query = " + query);
System.out.println("p = " + pageNumber);
ModelAndView mav = new ModelAndView();
//뷰이름 지정
mav.setViewName("search/external");
//뷰에 전달할 데이터 (request에 저장)
mav.addObject("query", query);
mav.addObject("pageNumber", pageNumber);
return mav;
}
index.jsp
<a href="${pageContext.request.contextPath}/search/external.do">
SearchController - external.do</a><br>
[출력화면]
500 오류
데이터를 전달하지 않을 시 null값이 되는데 p의 타입은 int기 때문에 null값을 받지 못함
(Integer라면 받을 수 있지만 int라서 불가능)
그래서 문자열처럼 required=false 대신 defaultValue를 기재해야 오류가 나지않음
SearchController.java
@RequestParam(value="p", defaultValue="1") int pageNumber)
라고 수정하고 다시 index에서 실행하면 에러없이 출력되는걸 볼 수 있음
[출력화면]
, 1 // query는 null 이기 때문에 공백, p는 기본값 1이 출력되는 것을 볼 수 있음
================정리================
공백을 허용하고 싶을 때
String ->
1) @RequestParam(value="query", required=false) String query
2) String query
int ->
1)@RequestParam(value="p", defaultValue="1") int pageNumber)
get방식이 아닌 post방식으로도 데이터를 전달할 수 있는데 post로 전달하기 위해선 폼을 꼭 작성해야 함
왜냐하면 폼에 post방식이라고 명시해야 작동되기 때문임
폼 호출 메소드 + 폼에서 동작 요청하는 메소드를 같이 만들면 됨
(요청 url을 똑같이 기재했을 때 보통 충돌이 나는데 요청 방식을 다르게 받으면 충돌나지 않음)
컨트롤러
kr.spring.ch03.controller
NewArticleController
package kr.spring.ch03.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class NewArticleController {
//Get 요청이 들어올 때 호출
@GetMapping("/article/newArticle.do")
public String form() {
return "article/newArticleForm";
}
//Post 요청이 들어올 때 호출
@PostMapping("/article/newArticle.do")
public String submit(){
return "article/newArticleSubmitted";
}
}
이 방식은 ModelAndView를 사용하지 않고 view파일명만 dispatcherServlet에 넘겨줌
-> Dispatcher가 view 정보를 viewresolver에 넘겨서 자동으로 알아내기 때문
요청url이 같기 때문에 (newArticle.do) @RequestMapping을 쓰면 충돌이 일어나지만
@Get / @PostMapping 을 사용해서 나누면 충돌나지 않음
get - 폼 호출 // post - 데이터 호출로 나눠서 해볼 것임
설정파일
servlet-context.xml
<beans:bean id="newArticleController" class="kr.spring.ch03.controller.NewArticleController"/>
폼 생성 (GetMapping, form())
views
article(folder)
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="1">
제목 : <input type="text" name="title"><br>
내용 : <textarea rows="5" cols="30" name="content"></textarea>
<input type="submit" value="전송">
</form>
</body>
</html>
form action="newArticle.do" method="post" // 요청 url은 같지만 데이터를 post로 전송하는 폼을 작성
post 방법은 method="post" 라고 꼭 폼을 만들어야 되지만
get방식은 그냥 폼 없이 @GetMapping("/article/newArticle.do")를 호출해도 됨
실행파일
index.jsp
<a href="${pageContext.request.contextPath}/article/newArticle.do">NewArticleController</a><br>
[출력화면]
NewArticleController
태그 누르면 글작성 폼 보임 (newArticleForm.jsp)
NewArticleController.java
newArticleSubmitted 에다가 게시글 등록완료 화면출력 하고 console에 값이 출력되게 할거임
컨트롤러
NewArticleController
package kr.spring.ch03.controller;
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.RequestParam;
@Controller
public class NewArticleController {
//Get 요청이 들어올 때 호출
@GetMapping("/article/newArticle.do")
public String form() {
return "article/newArticleForm";
}
//Post 요청이 들어올 때 호출
@PostMapping("/article/newArticle.do")
public String submit(@RequestParam int parentId,
@RequestParam String title,
@RequestParam String content){
System.out.println("parentId : " + parentId);
System.out.println("title : " + title);
System.out.println("content : " + content);
return "article/newArticleSubmitted";
}
}
뷰 파일
article(folder)
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>
게시글이 등록되었습니다.
</body>
</html>
설정파일과 index는 요청url이 같기 때문에 추가로 기재할 것 X
index.jsp 실행 시
NewArticleController
태그 누르고 글 작성하면
게시글이 등록되었습니다. 라고 출력되고
console에 입력한 정보가 나옴
[console]
parentId : 1
title : 제목
content : 내용
***
컨트롤러의 @RequestParam 생략해도 정상 실행됨
//Post 요청이 들어올 때 호출
@PostMapping("/article/newArticle.do")
public String submit(int parentId,String title,String content){
System.out.println("parentId : " + parentId);
System.out.println("title : " + title);
System.out.println("content : " + content);
return "article/newArticleSubmitted";
}
수정하고 다시 index.jsp 실행해도
오류 없이 실행됨
[console]
parentId : 1
title : 제목입니다
content : 내용입니다