학원/spring

1.2 (AOP 동작방식/SpringMVC-기본 출력, @RequestParam (String,int 값 생략 시) )

쿠룽지 2024. 1. 3. 08:05
728x90
반응형

 

 

<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! **메서드가 호출된 후에 나온다! 반환된 객체 : [상품 출시]



위처럼 설정파일을 이용해서 설정하는건
래거시에서 주로 사용함

 

 

 

설정파일이 아니라 어노테이션을 사용한 예제는 아래에

더보기
설정파일이 아니라 어노테이션을 사용한 예제
 
공통기능
kr.spring.ch21
MyFirstAspect
 
 
 
 
package kr.spring.ch21;
 
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
 
//공통 기능을 구현한 클래스
@Aspect
public class MyFirstAspect {
/*
 * 구현 가능한 Advice(언제 공통 기능을 핵심 로직에 적용할 지를 정의) 종류
 * 종류 설명
 * Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행
 * @Before
 *
 * After Returning Advice 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능을 실행 (예외 발생하면 공통 기능 실행 못함)
 * @AfterReturning
 *
 * After Throwing Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능을 실행
 * @AfterThrowing
 *
 * After Advice 대상 객체의 메서드를 실행하는 도중 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능을 실행(try~catch~finally의 finally 블럭과 비슷)
 * @After
 *
 * Around  대상 객체의 메서드 실행 전,후 또는 예외 발생 시점에 공통 기능을 실행
 * @Around
 */
 
//핵심 기능이 있는 객체의 메서드 검색
@Pointcut("execution(public String launch())")
public void getPointcut() {}
 
@Before("getPointcut()")
public void before() {
//메서드 시작 직전에 동작하는 어드바이스
System.out.println("Hello Before! **메서드가 호출되기 전에 나온다!");
 
}
}
 
-----------------------------------------------------
 
 
applicationContextAOP 복사해서
applicationContextAOP2.xml 로 변경 후 생성
 
 
 
<?xml version="1.0" encoding="UTF-8"?>
<!-- 어노테이션 방식으로 AOP를 구현할 때 명시 -->
<aop:aspectj-autoproxy/>
 
<!-- 공통 기능이 구현된 클래스 빈 객체 설정 -->
<bean id="myFirstAdvice" class="kr.spring.ch21.MyFirstAspect"/>
<!-- 핵심 기능이 구현된 클래스 빈 객체 설정 -->
<bean id="product" class="kr.spring.product.Product"/>
 
</beans>
 
 
-----------------------------------------------------
 
kr.spring.ch21
SpringMain
 
 
package kr.spring.ch21;
 
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("applicationContextAOP2.xml");
 
//핵심 기능이 구현된 메서드 호출
Product p = (Product)context.getBean("product");
p.launch();
 
context.close();
}
}
 
 
 
[출력화면]
Hello Before! **메서드가 호출되기 전에 나온다!
launch() 메서드 출력
 
-----------------------------------------------------
 
MyFirstAspect
 
 
@AfterReturning(value="getPointcut()",returning="msg")
public void afterReturning(String msg) {
//메서드 호출이 예외를 내보내지 않고 종료했을 때 동작하는 어드바이스
System.out.println("Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 " + msg);
}
 
 
-----------------------------------------------------
 
 
SpringMain 실행 시
 
 
 
[화면 출력]
launch() 메서드 출력
Hello AfterReturning! **메서드가 호출한 후에 나온다! 전달된 객체 [상품 출시]
 
 
 
예외가 없을 때 정상출력됨
-----------------------------------------------------
 
MyFirstAspect.java
 
 
 
 
@AfterThrowing(value="getPointcut()",throwing="ex")
public void afterThrowing(Throwable ex) {
//메서드 호출이 예외를 던졌을 때 동작하는 어드바이스
System.out.println("Hello AfterThrowing! **예외가 생기면 나온다!");
}
 
 
 
-----------------------------------------------------
 
SpringMain
 
 
 
[예외 X 화면 출력]
launch() 메서드 출력
 
 
 
Product.java 에서
예외 발생 구문 추가 시 System.out.println(20/0);
 
[예외 O 화면 출력]
launch() 메서드 출력
Hello AfterThrowing! **예외가 생기면 나온다!
 
-----------------------------------------------------
 
MyFirstAspect.java
 
 
 
@After("getPointcut()")
public void after() {
//메서드 종료 후에 동작하는 어드바이스
System.out.println("Hello After! **메서드가 호출된 후에 나온다");
}
 
-----------------------------------------------------
 
SpringMain
 
 
[출력화면]
launch() 메서드 출력
Hello After! **메서드가 호출된 후에 나온다
 
 
예외가 발생해도 동작
 
 
 
Product.java에서
예외구문 주석처리 후 다시 한번 더 실행
 
[출력화면]
launch() 메서드 출력
Hello After! **메서드가 호출된 후에 나온다
-----------------------------------------------------
 
 
MyFirstAspect.java
 
 
@Around("getPointcut()")
public String around(ProceedingJoinPoint joinPoint)throws Throwable {
//메서드 호출 전후에 동작하는 어드바이스
System.out.println("Hello Around before **메서드가 호출되기 전에 나온다!");
 
String s = null;
 
try {
//핵심 기능을 수행하는 메서드를 호출하고 결과값을 반환
s = (String)joinPoint.proceed();
}catch(Exception e) {
e.printStackTrace();
}finally {
System.out.println("Hello Around after! **메서드가 호출된 후에 나온다! 반환된 객체 : " + s);
}
return s;
}
 
 
-----------------------------------------------------
 
 
SpringMain 실행
 
 
예외가 발생하건 발생하지 않던간에
실행되어야 하기 때문에
try~catch~finally 구문을 넣어야함
 
 
 
[출력화면]
Hello Around before **메서드가 호출되기 전에 나온다!
launch() 메서드 출력
Hello Around after! **메서드가 호출된 후에 나온다! 반환된 객체 : [상품 출시]
 
 
 
 
AOP 끝

 

 

 

 

 


 

 

 

 

 

 

<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 : 내용입니다

 

 

 

728x90
반응형