티스토리 뷰

 

1.    AOP ( 관점 지향 프로그래밍

-      AOP = Aspect Oriented Programming  ( 관심 지향 프로그래밍 )

-      1 ) Aspect

-       : 개발할 때의 관심사( 성능 / 속도, 자원사용효율성, 트랜잭션( All or Nothing ), 로깅 )

-       : 추상적인 의미이다.

-      2 ) Target

-       : 핵심 비즈니스 로직이 구현된 서비스 객체 ( 비즈니스 계층의 서비스 )

-       : @Service( 비즈니스 계층의 서비스 ) -- > 관심사들을 적용할 대상

-      3 ) JoinPoint

-       : Target 객체가 가지고 있는 메소드들 ( ex. transfer 메소드 - 계좌이체 )

-      4 ) Advice

-       : 횡단 관심사( Cross-Concern )를 실제로 구현한 객체 ( ex. 트랜잭션 처리 )

-       : Aspect를 구현한 것이 Advice이다.

-      5 ) Proxy

-       : TargetJointPoint 수행시, 필요로 하는 Advice를 결합시켜 하나로 수행시키는 객체

-       : 필수조건 – Target과 동일하게 보여야 한다. ( , 타입이나 메소드 모두 Target과 동일하게 갖추고 있어야 한다. )

-      6 ) PointCut

-       : 어느 Target, 어느 JointPoint에 어떤 Advice를 적용시킬지를 적용하는 방법

-      + 필터는 Servlet을, 인터셉터는 Controller를, AOP는 Service를 가로채서 선처리와 후처리를 해준다. (**)


[ 1. AOP ]

 

[ 1 - 1. root-context.xml에 AOP 설정하기 - AOP는 웹과 관련이 없다. ]

 

[ + namespace에서 AOP열기 ]

 

 

[ + source 설정 ]

 

더보기

[ + 코드 보기 ]

<?xml version="1.0" encoding="UTF-8"?>

<!-- xmlns:와 xsi:는 디폴트 namespace이다. -->
<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"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://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
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- Root Context: defines shared resources visible to all other web components -->

	<!-- 여기 안에 속한 것은 모두 빈 타입으로 등록하겠다는 의미이다. -->
	<!-- <context:component-scan base-package="org.zerock.myapp.sample" /> -->

	<!-- Spting AOP를 위한 설정 2가지 -->
	
	<!-- + AOP를 어노테이션을 기반으로 빈으로 등록하겠다. -->
	<context:annotation-config />

	<!-- + proxy를 자동으로 설정해준다. -->
	<aop:aspectj-autoproxy />
		
</beans>

 

[ 1 - 2. Service 인터페이스 생성 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;



public interface SampleService { // + Target
	
	// ==================================================================================
	// + 이 인터페이스를 구현할 클래스가 곧 Target 객체를 생성할 클래스가 된다. (***)
	// + -> SampleService 인터페이스가 Target이라는 의미이다.
	// + -> SampleService 내의 메소드인 doAdd가 JoinPoint라는 의미이다.
	// ==================================================================================
	
	// + JoinPoint
	public abstract Integer doAdd(String s1, String s2) throws Exception;

} // end interface

 

[ 1 - 3. Service 구현 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.service;

import org.springframework.stereotype.Service;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@Service // + Service에는 @Service를 붙여야 한다!
public class SampleServiceImpl implements SampleService { // + Target

	@Override
	public Integer doAdd(String s1, String s2) throws Exception { // + JoinPoint
		
		log.trace("doAdd({}, {}) invoked.", s1, s2);
		
		return Integer.parseInt(s1) + Integer.parseInt(s2);
		// + Integer.parseInt는 int 타입으로 반환한다.
		
	} // doAdd

} // end class

 

[ 1 - 4. Advice 생성 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.aop;

import java.util.Arrays;

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.springframework.stereotype.Component;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;


@NoArgsConstructor
@Log4j2

@Component // + Bean으로 등록
@Aspect // + Aspect를 구현한 것이 Advice
public class LogAdvice {
	
	// ==================================================================================
	// + Target(서비스) 객체의 JoinPoint(메소드)의 호출로그(Cross-Concern => Aspect)를 출력하는 Advice 구현
	// ==================================================================================
	// + 단, Advice의 종류는 "Before Advice"로 구현한다.
	// + 즉, Target 객체의 JoinPoint 호출 전에 수행되는 Advice를 구현할 것이다.
	// ==================================================================================
	// + @Aspect만으로는 자동으로 Bean Container에 Bean으로 등록할 수는 없다.
	// + 그렇기 때문에 stereotype 어노테이션이 필요하다. ( ex. @Component )
	// ==================================================================================
	
	// ==================================================================================
	// + Advice의 종류
	// ==================================================================================
	
//	@Before						public void before2() {;;}
//	@AfterThrowing				public void afterThrowing() {;;} : 예외가 발생한 뒤에 동작하는 코드
//	@AfterReturning				public void afterReturning() {;;} : 모든 실행이 정상적으로 일어났을때 수행
//	@After						public void after() {;;} : 예외 발생의 유무와 상관없이 수행
//	@Around						public void around() {;;} : 메소드의 실행 자체를 제어
	
	// ==================================================================================
	// + Advice는 value를 통해 advice를 어디에다가 바인딩 할 것인지 PointCut을 지정해줘야 한다.
	// ==================================================================================
	// + *Service이 Target, *(..)는 JoinPoint를 의미한다.
	// + 즉, org.zerock.myapp.service 밑의 모든 Service로 끝나는 파일 중에 모든 메소드를 선택한 것이다.
	// + (..)는 매개변수는 자유하는 의미이다.
	// + JoinPoint에 Advice를 적용하게 된다.
	// ==================================================================================
	// + Before Advice : (1) 매개변수가 없다. (2) 리턴타입이 void이다. (3) 메소드명은 자유 
	// ==================================================================================
	
	// ==================================================================================
	// + PointCut Expression을 @Before 어노테이션의 기본속성 값으로 설정해야 한다. (***)
	// + 1. 이때 AspctJ 언어가 제공하는 execution() 함수를 사용하여 pointcut expression을 설정
	// + 2. pointcut 표현식의 형태 : ( 접근제한자 패키지.클래스.메소드(매개변수) )
	// + 3. 위의 2번의 pointcut 표현식을 통해
	// +    (1) 어느 Target( 서비스 ) 객체에
	// +    (2) 어느 JoinPoint( 메소드 )에
	// +    (3) 어느 종류의 Advice( @Before )를 적용시킬지 설정한다.
	// ==================================================================================
	
	// ====================================================
	// + 1. Target 객체의 JoinPoint가 실행되기 직전에 수행
	// ====================================================
	@Before(value = "execution( * org.zerock.myapp.service.*Service.*(..) )")
	public void before() {
		log.info("*****************************");
		log.trace("<1> before() invoked.");
		log.info("*****************************");
	} // before
	
	// ====================================================
	// + 1-2. &&args(매개변수 이름)을 통해서 JoinPoint에 전달될 값을 미리 받아볼 수 있다.
	// ====================================================
	@Before(value = "execution( * org.zerock.myapp.service.SampleService.doAdd(..) ) && args(s1, s2)")
	public void before2(String s1, String s2) {
		log.info("*****************************");
		log.trace("<1-2> before2({}, {}) invoked.", s1, s2);
		log.info("*****************************");
	} // before2
	
	// ====================================================
	// + 2. Target 객체의 JoinPoint가 정상적으로 완료된 후에 수행
	// ====================================================
	// + @AfterReturning 결과값을 받아 볼 수 있다.
	// ====================================================
	@AfterReturning(pointcut = "execution( * org.zerock.myapp.service.*Service.*(..) )", returning = "result")
	public void afterReturning(Object result) {
		log.info("*****************************");
		log.trace("<2> afterReturning({}) invoked.", result);
		log.info("*****************************");
	} // afterReturning	
	
	// ====================================================
	// + 3. Target 객체의 JoinPoint의 예외가 발생한 후에 수행
	// ====================================================
	// + Log4j2에서 예외를 모두 StackTrace해버리기에 e가 찍히지는 않는다.
	// ====================================================
	@AfterThrowing(pointcut = "execution( * org.zerock.myapp.service.*Service.*(..) )", throwing = "e")
	public void afterThrowing(Exception e) {
		log.info("*****************************");
		log.trace("<3> afterThrowing({}) invoked.", e);
		log.info("*****************************");
	} // afterThrowing	
	
	// ====================================================
	// + 4. Target 객체의 JoinPoint가 완료된 후에 수행 ( 예외의 유무와 상관 x )
	// ====================================================
	@After(value = "execution( * org.zerock.myapp.service.*Service.*(..) )")
	public void after() {
		log.info("*****************************");
		log.trace("<4> after() invoked.");
		log.info("*****************************");
	} // after
	
	// ====================================================
	// + 5. 메소드의 실행 자체를 제어
	// ====================================================
	// + 반드시 매개변수로 ProceedingJoinPoint를 가져야 한다.
	// + joinPoint의 타입이 다양할 수 있기에 Object로 해야 한다.
	// ====================================================
	@Around(value = "execution( * org.zerock.myapp.service.*Service.joinPoint(..) )")
	public Object around(ProceedingJoinPoint pjp) throws Throwable {
		
		log.info("*****************************");
		log.trace("<5> around({}) invoked.", pjp);
		log.info("*****************************");
		
		log.info("\t + 1. type : {}", pjp.getClass().getName());
		log.info("\t + 2. target : {}", pjp.getTarget());
		log.info("\t + 3. args : {}", Arrays.toString(pjp.getArgs()));
		log.info("\t + 4. JoinPoint(메소드) : {}", pjp.getSignature());
		
		return "DONE";
		
	} // around

} // end class

 

[ 1 - 5. Service와 AOP 빈으로 등록하기 ]

 

더보기

[ + 코드 보기 ]

<?xml version="1.0" encoding="UTF-8"?>

<!-- xmlns:와 xsi:는 디폴트 namespace이다. -->
<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"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://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
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- Root Context: defines shared resources visible to all other web components -->

	<!-- 여기 안에 속한 것은 모두 빈 타입으로 등록하겠다는 의미이다. -->
	<context:component-scan base-package="org.zerock.myapp.service" />
	<context:component-scan base-package="org.zerock.myapp.aop" />

	<!-- Spting AOP를 위한 설정 2가지 -->

	<!-- + AOP를 어노테이션을 기반으로 빈으로 등록하겠다. -->
	<context:annotation-config />

	<!-- + proxy를 자동으로 설정해준다. -->
	<aop:aspectj-autoproxy />
		
</beans>

 

[ 1 - 6. Test하기 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.aop;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.zerock.myapp.service.SampleService;

import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/**/root-context.xml")

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LogAdviceTests {
	
	@Setter(onMethod_= {@Autowired})
	private SampleService service;
	
	// =======================================================
	// + 1. Service 객체 확인
	// =======================================================
	
	@BeforeAll
	void beforeAll() {
		
		log.trace("\t + beforeAll() invoked.");
		
		Objects.requireNonNull(this.service);
		log.info("\t + 1. service : {}", this.service);
		// + 1. service : org.zerock.myapp.service.SampleServiceImpl@f000000
		log.info("\t + 2. ****** type(proxy) ****** : {}", this.service.getClass().getName());
		// + 2. ****** type(proxy) ****** : com.sun.proxy.$Proxy43 ( proxy 객체가 들어온다. )
		
	} // beforeAll
	
	// =======================================================
	// + 2. Advice가 발생하는지 확인
	// =======================================================
	
	@Test
	@Order(1)
	@DisplayName("1. testDoAdd")
	@Timeout(value = 3, unit = TimeUnit.SECONDS)
	void testDoAdd() throws Exception {
		
		log.trace("\t + testDoAdd() invoked.");
		
		int result = this.service.doAdd("1000", "200");
		//int result = this.service.doAdd("1000", "이백이다아아앙"); // + afterThrowing 확인
		log.info("\t + result : {}", result);
		
	} // testDoAdd
	
	// =======================================================
	// + 3. @Around가 발생하는지 확인
	// =======================================================
	
	@Test
	@Order(2)
	@DisplayName("2. testjoinPoint")
	@Timeout(value = 3, unit = TimeUnit.SECONDS)
	void testjoinPoint() throws Exception {
		
		log.trace("\t + testjoinPoint() invoked.");
		
		String result = this.service.joinPoint("이츠미", "마리오오오오");
		log.info("\t + result : {}", result);
		
	} // testjoinPoint
} // end class

 

728x90
댓글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
최근에 올라온 글
Total
Today
Yesterday
공지사항