티스토리 뷰
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
- : Target의 JointPoint 수행시, 필요로 하는 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
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 128일차 - Spring ( 트랜잭션 / 패스워드 암호화 / open API ) (******) (0) | 2022.08.30 |
---|---|
KH 127일차 - Spring( AOP 2 / 트랜잭션[미완성] ) (******) (0) | 2022.08.29 |
KH 125일차 - Spring( 인터셉터 3 - 세션 트레킹 : 자동 로그인 ) (******) (0) | 2022.08.25 |
KH 124일차 - Spring ( 인터셉터 - 세션 트레킹 : 로그인 / 로그아웃 / 인증 / 인가 / 자동 로그인 기능 ) (*********) (0) | 2022.08.24 |
KH 123일차 - Spring ( 인터셉터 ) (***) (0) | 2022.08.24 |