OOP (Object Oriented Programming: 객체 지향 프로그래밍)
12. AOP (Aspect Oriented Programming: 관점 지향 프로그래밍)
==> @Transactional 이 AOP 기술로 만들어짐. (성공: commit , 실패: rollback)
https://docs.spring.io/spring-framework/docs/5.2.25.RELEASE/spring-framework-reference/core.html#aop
1> 개념
브라우저 ----------> A서블릿 ----------> 서비스 ----------> DAO ----------> DB
(핵심기능:필수 (핵심기능 (핵심기능
+ + +
부수기능: 로깅) 부수기능: 로깅) 부수기능: 로깅)
브라우저 ----------> B서블릿 ----------> 서비스 ----------> DAO ----------> DB
(핵심기능 (핵심기능 (핵심기능
+ + +
부수기능) 부수기능: 로깅) 부수기능: 로깅)
- 각 layer 가 달라도 공통적으로 사용되는 코드들이 있음.
ex> 로그처리
- 최종적인 AOP 개념은 다음고 ㅏ같다.
핵심기능과 부수기능(분리기능)
2> AOP 기술
가. AOP 원천기술
- AspectJ (1995년)
- 굉장히 무겁다.
startup 시간이 많이 걸림.
- target class 의 많은 이벤트가 발생시 AOP 적용될 수 있다.
ex>
변수값이 변경,
생성자 호출,
메서드 호출,
...
나. Spring AOP
- 원천기술인 AspectJ 에서 일부분의 기술만 빌려와서 만듬.
- Spring 기반의 AOP 프레임워크.
- target class 에서 메서드 호출되는 이벤트에서만 AOP 가 적용됨.
3> 용어정리
가. Aspect
- 여러 빈에 공통적으로 사용되는 부수기능을 구현한 빈을 의미.
- @Aspect 어노테이션 사용
나. JoinPoint
http://docs.spring.io/spring-framework/docs/5.2.25.RELEASE/spring-framework-reference/core.html#aop-pointcuts-examples
- 핵심기능에 Aspect 가 적용되는 시점을 의미.
Spring AOP 에서는 메서드 호출되는 시점만을 의미한다.
- 이벤트로 적용됨.
ex> 핵심기능에서 발생 가능한 이벤트 종류?
# 핵심기능(타켓클래스: target class)
@Service
public class DeptServiceImpl {
int num;
public void setNum(int n) {} // 메서드 호출 이벤트
public void getNum() {} // 메서드 호출 이벤트
public DeptServiceImpl() {} // 생성자 호출 이벤트
}
# 부가기능
@Aspect
public class MyAspect {
public void log_print() {
System.out.println("로그출력");
}
}
다. PointCut
- JoinPoint 는 AOP 가 주입되는 시점인 메서드 호출시점을 의미하고
PointCut 는 메서드들 중에서 어떤 메서드를 호출했을 때 주입할 것인지를
알려주는 표현식이다.
- execution("public void getNum()")
execution("public void get*()")
execution("public * get*()")
execution("public * get*(**)")
라. Advice
- JoinPoint 는 AOP 가 주입되는 시점인 메서드 호출시점을 의미하고
PointCut 는 메서드들 중에서 어떤 메서드를 호출했을 때 주입할 것인지를
알려주는 표현식이고
Advice 는 호출된 메서드 전/후/성공/에러(전,후,성공,에러) 시점을 의미.
전: @Before (Before Advice)
- 아래 예시로는 getNum() 전에 삽입
후: @After (After Advice)
- 아래 예시로는 getNum() 후에 삽입
성공: @AfterReturning (AfterReturning Advice)
실패: @AfterThrowing (AfterThrowing Advice)
전/후/성공/에러: @Around (Around Advice)
ex>
# 사용
DeptServiceImpl service = ctx.getBean();
// 전
int n = service.getNum();
// 후
마. weaving
- target object 와 aspect 연결의미.
4> 구현
가. 의존성 설정
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
==> aspectjweaver-1.9.7.jar 다운로드 됨.
나. Aspect 작성
- 부가기능을 구현한 빈
- @Aspect 어노테이션 지정
다. Aspect 내에서 advice 와 pointcut 을 설정
- @Before(pointcut 설정)
target object 의 필수기능인 메서드가 호출하기전에 위빙
ex> @Before("execution(public * say*(..))")
- @After(pointcut 설정)
target object 의 필수기능인 메서드가 호출 후에 위빙
ex> @After("execution(public * say*(..))")
- @AfterReturning(pointcut=pointcut설정, returning=리턴값저장변수설정)
https://docs.spring.io/spring-framework/docs/5.2.25.RELEASE/spring-framework-reference/core.html#aop-advice-after-returning
target object 의 필수기능인 메서드가 리턴한 값을 얻을 수 있다.
ex>
@AfterReturning(pointcut="execution(public * say*(..))", returning="xxx") // 핵심기능에 리턴값이 있을때 사용
public void afterLogging(JoinPoint join, Object xxx) {...}
- @AfterThrowing(pointcut=pointcut설정, throwing=발생된예외저장변수설정)
target object 의 필수기능인 메서드가 예외발생 되었을때
ex>
@AfterThrowing(pointcut="execution(public * say*(..))", throwing="ex")
public void afterThrowingLogging(JoinPoint join, Exception ex) {...}
- @Around(pointcut설정)
@Before + @After + @AfterReturning + @AfterThrowing 모두 포함하는 기능
ex>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
Object retVal = pjp.proceed();
return retVal;
}
package com.exam;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import com.exam.service.TargetObjectBean;
@SpringBootApplication
public class Application implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
ApplicationContext ctx;
@Override
public void run(String... args) throws Exception {
logger.info("logger:ApplicationContext:{}",ctx);
try {
TargetObjectBean tob = ctx.getBean("target", TargetObjectBean.class);
logger.info("logger:타겟객체 sayEcho메서드호출:{}",tob.sayEcho("홍길동"));
} catch (Exception e) {
logger.error("logger: 에러발생, {}", e.getMessage());
}
}
}
package com.exam.aop;
import org.aspectj.lang.JoinPoint;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
@Configuration // 설정한다는 의미로 @Configuration / @Component 도 가능
@Aspect
public class LoggingAspect {
Logger logger = LoggerFactory.getLogger(getClass());
// @Before("execution(public String sayEcho(..))") // sayEcho 호출되기 전에
// public void beforeLogging() {
// logger.info("logger:LoggingAspect:{}","Before Advice");
// }
// @Before("execution(public * say*(..))") // say 로 시작하는 모든 기능
// public void beforeLogging(JoinPoint join) {
// logger.info("logger:호출된 핵심기능 메서드명:{}",join.getSignature().getName());
// logger.info("logger:LoggingAspect:{}","Before Advice");
// }
//-------------------------------------------------------
// @After("execution(public * say*(..))") // say 로 시작하는 모든 기능
// public void afterLogging(JoinPoint join) {
// logger.info("logger:호출된 핵심기능 메서드명:{}",join.getSignature().getName());
// logger.info("logger:LoggingAspect:{}","After Advice");
// }
// @AfterReturning(pointcut="execution(public * say*(..))", returning="xxx") // 핵심기능에 리턴값이 있을때 사용
// public void afterReturningLogging(JoinPoint join, Object xxx) {
// logger.info("logger:호출된 핵심기능 메서드명:{}",join.getSignature().getName());
// logger.info("logger:호출된 핵심기능 메서드의 리턴값:{}", xxx);
// logger.info("logger:LoggingAspect:{}","AfterReturning Advice");
// }
//--------------------------------------------------------
// 예외가 발생됬을때 위빙됨.
// @AfterThrowing(pointcut="execution(public * say*(..))", throwing="ex")
// public void afterThrowingLogging(JoinPoint join, Exception ex) {
// logger.info("logger:호출된 핵심기능 메서드명:{}",join.getSignature().getName());
// logger.info("logger:호출된 핵심기능 메서드명에서 예외클래스 정보:{}", ex.getMessage());
// logger.info("logger:LoggingAspect:{}","AfterThrowing Advice");
// }
//------------------------------------------------------------
@Around("execution(public * say*(..))")
public Object aroundLogging(ProceedingJoinPoint join) {
logger.info("logger:@Before 역할:{}","before Advice"); // sayEcho 전에 출력됨.
Object retVal = null;
try {
retVal = join.proceed();
logger.info("logger: retVal: {}", retVal);
} catch (Throwable e) {
logger.info("logger:@AfterThrowing 역할:{}","AfterThrowing Advice");
} // 기준
logger.info("logger:@After|@AfterReturning 역할:{}","After|AfterReturning Advice"); // sayEcho 후에 출려됨.
return retVal;
}
}
package com.exam.service;
import org.aspectj.lang.annotation.AfterThrowing;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
// 인터페이스 사용안함.
//@Service // 가능
@Component("target")
public class TargetObjectBean {
// 핵심기능
public String sayEcho(String name) {
System.out.println("sayEcho 호출");
// @AfterThrowing 를 테스트해보기 위한 강제 예외발생
int n = 10;
int result = n/0;
return "안녕하세요" + name;
}
}
# application.properties
logging.level.org.springframework=info
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.exam</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5. AOP 패턴
가. Pointcut 정의한 빈 작성
public class CommonPointcutConfig {
@Pointcut("execution(public * say*(..))")
public void businessService() {}
@Pointcut("execution(public * aa*(..))")
public void businessService2() {}
}
나. Aspect 에서 빈의 메서드 호출해서 pointcut 적용
package com.exam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import com.exam.service.TargetObjectBean;
@SpringBootApplication
public class Application implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
ApplicationContext ctx;
@Override
public void run(String... args) throws Exception {
logger.info("logger:ApplicationContext:{}",ctx);
try {
TargetObjectBean tob = ctx.getBean("target", TargetObjectBean.class);
logger.info("logger:타겟객체 sayEcho메서드호출: {}",
tob.sayEcho("홍길동"));
}catch(Exception e) {
logger.error("logger:에러발생, {}", e.getMessage());
}
}
}
package com.exam.aop;
import org.aspectj.lang.annotation.Pointcut;
public class CommonPointcutConfig {
@Pointcut("execution(public * say*(..))")
public void businessService() {}
@Pointcut("execution(public * aa*(..))")
public void businessService2() {}
}
package com.exam.aop;
import org.aspectj.lang.JoinPoint;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class LoggingAspect {
Logger logger = LoggerFactory.getLogger(getClass());
@Before("com.exam.aop.CommonPointcutConfig.businessService()")
public void commonPointcutConfigLogging(JoinPoint join) {
logger.info("logger:호출된 핵심기능 메서드명:{}", join.getSignature().getName());
logger.info("logger:LoggingAspect:{}", "before Advice");
}
// @Around("execution(public * say*(..))")
// public Object aroundLogging(ProceedingJoinPoint join){
//
// logger.info("logger:@Before 역할:{}", "before Advice");
// Object retVal =null;
// try {
// retVal = join.proceed();
// logger.info("logger:retVal :{}", retVal);
//
// } catch (Throwable e) {
// logger.info("logger:@AfterThrowing 역할:{}", "AfterThrowing Advice");
// }
// logger.info("logger:@After|@AfterReturning 역할:{}", "After|AfterReturning Advice");
//
// return retVal;
// }
// @AfterThrowing(pointcut="execution(public * say*(..))", throwing = "ex")
// public void afterThrowingLogging(JoinPoint join, Exception ex) {
// logger.info("logger:호출된 핵심기능 메서드명:{}", join.getSignature().getName());
// logger.info("logger:호출된 핵심기능 메서드명에서 예외클래스 정보:{}", ex.getMessage());
// logger.info("logger:LoggingAspect:{}", "AfterThrowing Advice");
// }
// @AfterReturning(pointcut="execution(public * say*(..))", returning = "xxx")
// public void afterReturningLogging(JoinPoint join, Object xxx) {
// logger.info("logger:호출된 핵심기능 메서드명:{}", join.getSignature().getName());
// logger.info("logger:호출된 핵심기능 메서드명의 리턴값:{}", xxx);
// logger.info("logger:LoggingAspect:{}", "AfterReturning Advice");
// }
// @After("execution(public * say*(..))")
// public void afterLogging(JoinPoint join) {
// logger.info("logger:호출된 핵심기능 메서드명:{}", join.getSignature().getName());
// logger.info("logger:LoggingAspect:{}", "after Advice");
// }
// @Before("execution(public * say*(..))")
// public void beforeLogging(JoinPoint join) {
// logger.info("logger:호출된 핵심기능 메서드명:{}", join.getSignature().getName());
// logger.info("logger:LoggingAspect:{}", "before Advice");
// }
}
package com.exam.service;
import org.springframework.stereotype.Component;
// 인터페이스 사용안함.
@Component("target")
public class TargetObjectBean {
// 핵심기능
public String sayEcho(String name) {
System.out.println("sayEcho 호출");
int n = 10;
int result = n/2;
return "안녕하세요"+ name;
}
}
# application.properties
logging.level.org.springframework=info
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.exam</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<AOP 커스텀어노테이션>
package com.exam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import com.exam.service.TargetObjectBean;
@SpringBootApplication
public class Application implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
ApplicationContext ctx;
@Override
public void run(String... args) throws Exception {
logger.info("logger:ApplicationContext:{}",ctx);
try {
TargetObjectBean tob = ctx.getBean("target", TargetObjectBean.class);
logger.info("logger:타겟객체 sayEcho메서드호출: {}",
tob.sayEcho("홍길동"));
}catch(Exception e) {
logger.error("logger:에러발생, {}", e.getMessage());
}
}
}
package com.exam.aop;
import org.aspectj.lang.annotation.Pointcut;
public class CommonPointcutConfig {
@Pointcut("execution(public * say*(..))")
public void businessService() {}
@Pointcut("execution(public * aa*(..))")
public void businessService2() {}
@Pointcut("@annotation(com.exam.aop.PerformanceTime)")
public void performanceTimeAnnotation() {}
}
package com.exam.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceTime {}
package com.exam.aop;
import org.aspectj.lang.JoinPoint;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class PerformanceTimeAspect {
Logger logger = LoggerFactory.getLogger(getClass());
@Around("com.exam.aop.CommonPointcutConfig.performanceTimeAnnotation()")
public Object performanceTime(ProceedingJoinPoint join) {
Object retVal=null;
long startTime = 0L;
long endTime = 0L;
try {
startTime = System.currentTimeMillis();
retVal = join.proceed();
endTime = System.currentTimeMillis();
} catch (Throwable e) {
}
System.out.println("총 작업시간 : " + (endTime - startTime));
return retVal;
}
}
package com.exam.service;
import org.springframework.stereotype.Component;
import com.exam.aop.PerformanceTime;
// 인터페이스 사용안함.
@Component("target")
public class TargetObjectBean {
// 핵심기능
@PerformanceTime
public String sayEcho(String name) {
System.out.println("sayEcho 호출");
int n = 10;
int result = n/2;
return "안녕하세요"+ name;
}
}
# application.properties
logging.level.org.springframework=info
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.exam</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
'[study]이론정리 > Spring Boot' 카테고리의 다른 글
Spring 기반의 DB 연동 - SpringJDBC + mysql 연동 (0) | 2024.07.02 |
---|---|
Spring 기반의 DB 연동 - SpringJDBC + h2 연동 (0) | 2024.07.02 |
프로파일(Profile) (0) | 2024.07.02 |
초기화 및 cleanup 작업 처리(@PostConstruct & @PreDestroy) (0) | 2024.07.02 |
빈의 scope (0) | 2024.07.02 |