BACKEND/스프링 Spring

[spring3.0] AOP

꾸준히개발하자 2022. 3. 28. 22:21

 

AOP 란? 모든 영역에 공통적으로 적용되는 코딩이다.

주어진 조건에 따라서 특정 클래스 메서드에 적용한다.

 

횡단 관심사 : 모든 코딩에 공통적으로 적용되는 코딩

크로스커팅 관심사(cross-cutting concerns)는 다른 관심사에 영향을 미치는 프로그램의 애스펙트이다.

 

조인 포인트 : 어디다가 적용할지 EX) 짝수 클래스 홀수 클래스

어드바이스 : 조인 포인트에 적용할 코드 

포인트 컷 : 언제 어드바이스를 실행하는지 위치표시(시작하거나 끝날 때)  

애스팩트 : 어드바이스(동작)와 포인트 컷(동작을 적용하는 조건)을 조합해서  횡단 관심사에 대한 코드와 그것을 적용할 지점을 정의한 것.

 

 

추가

 

1. 횡단 관심사

  • 모든 영역에 공통적으로 적용되는 코딩
  • 중간중간 삽입되어야 할 기능들(로깅,보안,트랜잭션)
  • 주로 메소드 단위로 확인한다.

2. 조인 포인트

  • 어드바이스를 실행할 수 있는 지점 -> 포인트 컷의 후보 
  • 어플리케이션 실행의 특정 지점

3. 어드바이스

  • 조인포인트에게 실행 할 것인가?
  • 종류
  • before : 메서드 호출 전
  • after : 메서드 호출 후
  • after returning : 메서드가 정상적으로 실행 된 후
  • after throwing : 메서드를 실행하는 도중 에러가 발생하는 경우
  • around : 메서드 앞 뒤로 실행한다.
  • 조인포인트에서 실행 할 코드 이다.

4. 포인트 컷

  • 어드바이스를 실행하는 조인 포인트
  • 어드바이스를 실행하는 시간(위치)

5. 애스팩트

  • 어드바이스 + 포인트컷
  • 무엇을 언제할지 정의한다 

 

 

 

 

메이븐에서 라이브러리 등록

 

Spring AOP 5.3.17

 

 

 

AspectJ Weaver 1.8.10

 

 

AspectJ Runtime 1.8.10 aspectjrt

 

 

 

CGLib 3.2.4

pom.xml  환경설정 파일 ( 여기서 메이븐 라이브러리를 등록한다 ) 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.springframework.samples.spring</groupId>
	<artifactId>spring-utility</artifactId>
	<version>1.0.0.CI-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>Spring Utility</name>
	<url>https://www.springframework.org</url>
	<description>
		<![CDATA[
      This project is a minimal jar utility with Spring configuration.
    ]]>
	</description>
	<properties>
		<maven.test.failure.ignore>true</maven.test.failure.ignore>
		<spring.framework.version>3.0.6.RELEASE</spring.framework.version>
	</properties>	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.7</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.framework.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.framework.version}</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.14</version>
		</dependency>
		
		<!-- spring-aop -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-aop</artifactId>
		    <version>${spring.framework.version}</version>
		</dependency>
		
		<!-- aspectjweaver -->
		<dependency>
		    <groupId>org.aspectj</groupId>
		    <artifactId>aspectjweaver</artifactId>
		    <version>1.8.10</version>
		</dependency>
		
		<!-- aspectjrt -->
		<dependency>
		    <groupId>org.aspectj</groupId>
		    <artifactId>aspectjrt</artifactId>
		    <version>1.8.10</version>
		</dependency>
		
		<!-- AOP Proxy cglib -->
		<dependency>
		    <groupId>cglib</groupId>
		    <artifactId>cglib</artifactId>
		    <version>3.2.4</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
 
		</plugins>
	</build> 
</project>

중요한 것은

Spring-aop는 <version>${spring.framework.version}</version>  현재 스프링 버전에 맞춰줘야 한다.

메이븐 라이브러리를 pom.xml 에 추가해준다.

 

 

SpringAOP 

src/main/java - com.exe.aop - TargetA.java

package com.exe.aop;

import org.springframework.stereotype.Component;

@Component("targetA")
public class TargetA {
	
	public void doSomething1() {
		System.out.println("TargetA.doSomething1");
	}
	
	public void doSomething2() {
		System.out.println("TargetA.doSomething2");
	}
	
	public void doAnother1() {
		System.out.println("TargetA.doAnother1");
	}
	
	public void doAnother2() {
		System.out.println("TargetA.doAnother2");
	}
}

TargetB.java

src/main/java - com.exe.aop - TargetB.java

 

package com.exe.aop;

import org.springframework.stereotype.Component;

@Component("targetB")
public class TargetB {
	
	public void doSomething1(String str) {
		System.out.println("TargetB.doSomething1:" + str);
	}
	
	public void doSomething2() {
		System.out.println("TargetB.doSomething2");
	}
	
	public void doAnother1() {
		System.out.println("TargetB.doAnother1");
	}
	
	public void doAnother2() {
		System.out.println("TargetB.doAnother2");
	}
}

 

src/main/java - app-context.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: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-3.0.xsd
	http://www.springframework.org/schema/context
	https://www.springframework.org/schema/context/spring-context-3.0.xsd
	http://www.springframework.org/schema/aop
	https://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	
	<!-- context 부분을 aop도 똑같이 적어준다. -->

aop도 context처럼 문서를 위한 XML 네임스페이스를 명시한다.

 

<description>Example configuration to get you started.</description>

	<context:component-scan base-package="com.exe.aop" />

	<bean id="beforeAdvice" class="com.exe.aop.MyBeforeAdvice"/>

base-package  어느 패키지에 지정할 것인지.   모두 다 적용하려면  *

 MyBeforeAdvice 클래스에 beforeAdvice객체 생성 - 의존성 주입 

 

 

 

com.exe.aop 패키지에서 MyBeforeAdvice.java

package com.exe.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;


public class MyBeforeAdvice {

	public void beforeMethodCall() {
		// 어드바이스 
		// 메소드가 실행되기전 실행될 코드 
		System.out.println("메소드 실행전(BeforeAdvice)");
	}
}

 

특정 메서드가 실행되기 전  beforeMethodCall 실행  

<aop:config>
		<aop:aspect ref="beforeAdvice">
			<aop:pointcut expression="execution(public void com..aop.*.*(*.*))"
			 id="beforePointcut"/>		
			<aop:before method="beforeMethodCall" pointcut-ref="beforePointcut"/>
		</aop:aspect>

aspect는 pointcut과 어드바이스를 실행할 프로그램을 어디다가 적용할지 정의한다.

 

xecution(public void com.. aop.*.*2(..))" 

-   execution(메서드 접근 지정자) 반환 값 패키지. 클래스. 메서드명(인수)

 

-  메서드에 끝에 2가 들어가는 것만 실행

 

aop:before method의 pointcut-ref는 pointcut id beforePointcut를 참조한다.

 expression = execution(public void com.. aop.*.*(*.*)) 특정 메서드가 실행하기 전에 

beforeMethodCall 메서드를 호출하라는 의미이다.

 

 

AopMain.java

package com.exe.aop;

import org.springframework.context.support.GenericXmlApplicationContext;

public class AopMain {

	public static void main(String[] args) {
		
        // xml파일을 읽어온다.
		GenericXmlApplicationContext context = 
				new GenericXmlApplicationContext("app-context.xml");
		
        
		TargetA ta = (TargetA)context.getBean("targetA"); //@Componet 객체 읽어옴
		TargetB tb = (TargetB)context.getBean("targetB");
		// 특정메소드가 실행되기전에 호출시키기 
		
		// beforeMethodCall 이 실행되고 나서 각각의 메소드가 실행됨
		ta.doSomething1();
		ta.doSomething2();
		ta.doAnother1();
		ta.doAnother2();
		  
		// "execution(public void com..aop.*B.*(..))
		// targetA는 다 나오지만 TargetB는 메소드실행전..(BeforeAdvice)실행후 출력 	
		/*
		tb.doSomething1("테스트");
		tb.doSomething2();
		tb.doAnother1();
		tb.doAnother2();
		*/
		
		// "execution(public void com..aop.*.*(..))	
		// execution(public void com..aop.*.*S*(..))"  대문자 S자가 들어간 메소드만 실행	
		// execution(public void com..aop.*.*2(..))"  메소드에 끝에 2가 들어가는 것만 실행 	
		// "execution(public void com..aop.*.*(String))"  String형태  매개변수에만 있는 메소드에 적용 			
	}
}

 

MyAfterAdvice.java

public class MyAfterAdvice {
	
	// 하고싶은 작업	
	@After("execution(public void com..aop.*.*(..))")
	public void afterMethodCall() {
		System.out.println("메소드 실행후(AfterAdvice)...");
	}
}
<!-- aop:after : pointcut에 지정한 특정메소드가 실행되고 나서 실행  -->
	<!-- execution(메소드 접근지정자 반환값 패키지.클래스.메소드명(인수) -->
	<!-- execution(* com..aop.*.*(String) -->		
	
	<!-- 특정 메소드가 실행된후 afterMethodCall 실행  -->	
		<aop:aspect ref="afterAdvice"> 
			<aop:pointcut id="afterPointcut"
			 expression="execution(public void com.exe.aop.*.*(..))"/>
			<aop:after method="afterMethodCall"
			 pointcut-ref="afterPointcut"/>
		</aop:aspect>

 

MyAroundAdvice.java

public class MyAroundAdvice {
	
	
	// Object : 그 메소드가 어느것을 반환할지 모르므로 Object로 받음 
	// around : before -> 메소드 -> after
	// 메소드를 실행시켜주는 코딩은 ProceedingJoinPoint
	
	@Around("execution(public void com..aop.*.*(..))")
	public Object aroundMethodCall(ProceedingJoinPoint jointPoint) {
		
		// 반환값
		Object result = null;
		try {		
			System.out.println("메소드 실행 전(AroundAdvice)..."); // Arround 실행 전
			
			result = jointPoint.proceed(); // Arround 메소드 실행 전에 하는 메소드 
										   /*	ta.doSomething1();
												ta.doSomething2();
												ta.doAnother1();
												ta.doAnother2();*/
			System.out.println("메소드 실행 후(AroundAdvice)..."); // 메소드가 끝나고 실행
	
		} catch (Throwable e) { // 회장나와 ! 사장Exception 보다 높음  
			
		}
		return result;
	}
}

 

around는 beforeAdvice와 afterAdvice 합친 것이다.

즉 beforeAdvice 실행하면서 리턴 값을 주고 나서 afterAdvice가 실행한다.

 

이때 리턴 값의 타입은 object이다. (무엇을 줄지 모르니 최상위 객체)

 

ProceedingJoinPoint을 전달하며 proceed() 메서드 호출을 통해 대상 포인트컷을 실행한다.

 

result = joinPoint.proceed();  메소드 실행 전 메서드가 호출되고 나서

메서드를 통해 반환 값을 리턴 주고 나서  메서드가 끝나고 실행된다.

 

 

 

catch문에는 Throwable 회장 나와! Throwable를 적어줘야 한다. 

 

 

 

<bean id="afterAdvice" class="com.exe.aop.MyAfterAdvice"/>
	<bean id="aroundAdvice" class="com.exe.aop.MyAroundAdvice"></bean>
	<bean id="afterReturnAdvice" class="com.exe.aop.MyAfterReturnAdvice"/>
	<bean id="afterThrowAdvice" class="com.exe.aop.MyAfterThrowAdvice"/>

afterAdvice : 특정 메서드가 실행된 후 afterMethodCall 실행

aroundAdvice : before 실행하면서 리턴 주고 after실행 

afterReturnAdvice : 무조건 메서드가 정상적 끝났을 때 실행 

afterThrowAdvice : 메소드가 오류가 났을때 실행 

 

<!-- 특정 메소드가 실행되기전  beforeMethodCall 실행  -->
	<aop:config>
		<aop:aspect ref="beforeAdvice"> <!-- 애스팩트 :  어드바이스(동작)와 포인트컷(동작을적용하는조건)을 조합해서 
											횡단 관심사에 대한 코드와 그것을 적용할 지점을 정의한 것 --> 
			<aop:pointcut id ="beforePointcut"
			 expression="execution(public void com..aop.*.*(..))" /> <!-- B자로 끝나느 클래스만 적용 -->
			<aop:before method="beforeMethodCall" 
			pointcut-ref="beforePointcut"/> <!-- 메소드가 실행되기 전 expression조건에 before에 적용한다.	 -->
		</aop:aspect>
	
	<!-- aop:after : pointcut에 지정한 특정메소드가 실행되고 나서 실행  -->
	<!-- execution(메소드 접근지정자 반환값 패키지.클래스.메소드명(인수) -->
	<!-- execution(* com..aop.*.*(String) -->		
	
	<!-- 특정 메소드가 실행된후 afterMethodCall 실행  -->	
		<aop:aspect ref="afterAdvice"> 
			<aop:pointcut id="afterPointcut"
			 expression="execution(public void com.exe.aop.*.*(..))"/>
			<aop:after method="afterMethodCall"
			 pointcut-ref="afterPointcut"/>
		</aop:aspect>
	
	<!-- b 실행 하면서 리턴 주고 a실행해야함  -->
		<aop:aspect ref="aroundAdvice">
			<aop:pointcut id ="aroundPointcut"  
			 expression="execution(public void com..aop.*.*(..))" />  
			<aop:around method="aroundMethodCall" 
			pointcut-ref="aroundPointcut"/> 
		</aop:aspect>
	
	<!-- 무조건 메소드가 정상적 끝났을때  -->
	<!--  expression 표현 execution 집행 -->
		<aop:aspect ref="afterReturnAdvice">
			<aop:pointcut id ="afterReturnPointcut"  
			 expression="execution(public void com..aop.*.*(..))" />  
			<aop:after-returning method="afterReturnMethodCall" 
			pointcut-ref="afterReturnPointcut"/> 
		</aop:aspect>
	
	<!-- 메소드가 오류가 났을때 실행 -->
		<aop:aspect ref="afterThrowAdvice">
			<aop:pointcut id ="afterThrowPointcut"  
			 expression="execution(public void com..aop.*.*(..))" />  
			<aop:after-throwing method="afterThrowMethodCall" 
			pointcut-ref="afterThrowPointcut"/> 
		</aop:aspect>
	</aop:config>
</beans>

 

 

어노테이션으로 만들어보기 

 

MyAfterAdvice 클래스 

@Aspect
@Component
public class MyAfterAdvice {
	
	// 하고싶은 작업	
	@After("execution(public void com..aop.*.*(..))")
	public void afterMethodCall() {
		System.out.println("메소드 실행후(AfterAdvice)...");
	}
}

@Aspect  기능 아래의 기능과 같다.

<aop:aspect ref="afterAdvice"> 
			<aop:pointcut id="afterPointcut"
			 expression="execution(public void com.exe.aop.*.*(..))"/>
			<aop:after method="afterMethodCall"
			 pointcut-ref="afterPointcut"/>
		</aop:aspect>

@Component 기능은 아래와 같다.

<bean id="afterAdvice" class="com.exe.aop.MyAfterAdvice"/>

 

@After("execution(public void com.. aop.*.*(..))") 

하고 싶은 작업을 적는다. Aftrer 특정 조건의 메서드들이 실행되고 나서 

afterMethodCall() 메서드가 실행된다 

 

MyAfterReturnAdvice 클래스 

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAfterReturnAdvice {

	// 메소드가 정상적으로 실행이 됬을때
	@AfterReturning("execution(public void com..aop.*.*(..))")
	public void afterReturnMethodCall() {
		System.out.println("메서드 실행후(After return)..");
		// TargetA.doSomething2  정상적으로 실행이 됬을때
		// 메서드 실행후(After return)..	
	}
}

 

 

@AfterReturning :특정 메서드가 정상적으로 실행이 됬을 때 afterReturnMethodCall() 메소드가 실행된다. 

 

@Aspect
@Component
public class MyAfterThrowAdvice {
	
	// 에러가 났을때 실행
	@AfterThrowing("execution(public void com..aop.*.*(..))")
	public void afterThrowMethodCall() {
		System.out.println("메소드 에러발생후 실행(afterThrow...");
	}
}

 

@AfterThrowing : 특정 메서드가 에러가 발생하여 실행하지 못했을때 afterThrowMethodCall() 메소드가 실행된다. 

 

package com.exe.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAroundAdvice {
	
	
	// Object : 그 메소드가 어느것을 반환할지 모르므로 Object로 받음 
	// around : before -> 메소드 -> after
	// 메소드를 실행시켜주는 코딩은 ProceedingJoinPoint
	
	@Around("execution(public void com..aop.*.*(..))")
	public Object aroundMethodCall(ProceedingJoinPoint jointPoint) {
		
		// 반환값
		Object result = null;
		try {		
			System.out.println("메소드 실행 전(AroundAdvice)..."); // Arround 실행 전
			
			result = jointPoint.proceed(); // Arround 메소드 실행 전에 하는 메소드 
										   /*	ta.doSomething1();
												ta.doSomething2();
												ta.doAnother1();
												ta.doAnother2();*/
			System.out.println("메소드 실행 후(AroundAdvice)..."); // 메소드가 끝나고 실행
		} catch (Throwable e) { // 회장나와 ! 사장Exception 보다 높음  
			
		}
		return result;
	}
}

@Around("execution(public void com.. aop.*.*(..))" 

특정 메서드가 실행되기 전에  실행 전 메소드 호출시키고 

result = jointPoint.proceed();  실행하면서 반환 값을 준다.

반환 값은 object로 받는다.  (무엇을 받을지 모르니까)

반환 값을 주고 나서 실행 후 메서드를 실행한다. 

jointPoint.proceed()는 

Throwable로 예외처리해줘야 한다. 

 

 

 

 

 

MyBeforeAdvice 클래스 

package com.exe.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect  // <aop:aspect~</aop:aspect>구조로 만듬
@Component  // 객체 생성
public class MyBeforeAdvice {
	
	@Before("execution(public void com..aop.*.*(..))")//내가 정해준 execution에 적용
	public void beforeMethodCall() {
		
		// 어드바이스 
		// 메소드가 실행되기전 실행될 코드 
		System.out.println("메소드 실행전(BeforeAdvice)");
	}
}

 

@Before(executuin(public void com.. aop.*.*(..))")  내가 정해준 execution에 적용한다. 

 

어노테이션으로 만든 AOP도 똑같은 결과가 나오게 된다.

 

AroundAdvice : 특정 메서드 실행전 한번 호출하고 나서 리턴값을 주고 나서 특정 메소드 실행 후 메소드 출력

BeforeAdvice : 특정 메소드 실행 전에 호출

AroundAdvice : 특정 메서드가 실행 후에 호출

After return : 특정 메소드가 정상적으로 끝나면 호출 

afterThrowAdvice : 특정 메서드가 정상적으로 실행하지 못했으면 호출