방카@Dev
[Spring]AOP(Aspect Oriented Programming)_스프링 핵심원리(고급편) 본문
#메이븐 설정파일(pom.xml)
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
AOP란?
- 비즈니스 로직의 핵심 기능과 공통 기능(부가 기능)을 분리 시켜놓고, 공통 기능(횡단 관심사)을 필요로 하는 핵심 기능들에 사용하는 방법
1. 에스펙트(Aspect)
- 부가 기능(어드바이스)과 부가 기능을 어디에 적용할지 선택(포인트 컷)하는 기능을 합해서 하나의 모듈로 만든 것
- 부가기능과 해당 부가기능을 어디에 적용할지 정의한 것(ex. 로그 출력 기능을 모든 컨트롤러에 적용해라!)
- 애플리케이션을 바라보는 관점을 하나하나의 기능에서 횡단 관심사(cross-cutting concerns) 관점으로 달리 보는 것
- 에스펙트를 사용한 프로그래밍 방식을 관점 지향 프로그래밍(AOP)
2.어드바이스(Advice)
- ASPECT의 기능 자체(부가 기능 - e.g.로그 출력 기능)
- 특정 조인 포인트에서 aspect에 의해 취해지는 부가 기능
- around(주변), before(전), after(후)와 같은 다양한 종류의 어드바이스가 있음
3. 조인 포인트(Join Point)
- Advice를 적용할 수 있는 위치
- 메소드 실행, 생성자 호출, 필드 값 접근, static 메서드 접근 같은 프로그램 실행 중의 AOP 적용 가능 지점
- aspectJ를 이용하여 컴파일 시점, 클래스 로딩 시점에 적용하는 AOP는 바이트코드를 조작하기 때문에 해당 기능을 모든 지점에 다 적용 가능
- 프록시 방식을 사용하는 스프링 AOP는 메서드 실행 지점에만 AOP를 적용
- 프록시는 메서드 오버라이딩 개념으로 동작
- 프록시를 사용하는 스프링 AOP의 조인 포인트는 메서드 실행으로 제한
4. 포인트 컷(Pointcut)
- 조인 포인트 중 어드바이스가 적용될 위치를 선별하는 기능
5. 타겟(Target)
- 어드바이스를 받는 객체, 포인트 컷으로 결정
6. 위빙(weaving)
- advice를 핵심 기능에 적용 하는 행위(원본 로직에 부가 기능 로직이 추가되는 것, 에스펙트와 실제 코드를 연결해서 붙이는 것)
- 포인트컷으로 결정한 타켓의 조인 포인트에 어드바이스를 적용하는 행위
# 위빙 적용 시점
(1) 컴파일 시점 : java 소스코드를 컴파일러를 사용해서 .class를 만드는 시점에 부가기능 로직 추가
- 실제 대상 코드에 에스펙트를 통한 부가 기능 호출 코드 포함. AspectJ를 직접 사용
(2) 클래스 로딩 시점 : .class 파일을 JVM 내부의 클래스 로더 저장 전에 부가기능 로직 추가
- 실제 대상 코드에 에스펙트를 통한 부가 기능 호출 코드 포함. AspectJ를 직접 사용
(3) 런타임 시점 : 컴파일 및 클래스 로더에 로딩하여 자바가 실행되고 난 다음 시점, 자바의 main 메서드가 이미 실행된 다음. 프록시를 통해 스프링 빈에 부가 기능을 적용할 수 있음.
- 실제 대상 코드는 그대로 유지되고 대신 프록시를 통해 부가기능이 적용되므로 항상 프록시를 통해 부가 기능 사용
- aop 기능을 구현하기 위해 만든 프록시 객체, 스프링에서 aop 프록시는 jdk 동적 프록시 또는 cglib 프록시 사용
* AspectJ 프레임워크
- AOP의 대표적인 구현.
- 횡단 관심사의 깔끔한 모듈화
- 1)오류 검사 및 처리 2)동기화 3)성능 최적화(캐싱) 4)모니터링 및 로깅
- 스프링은 AspectJ의 문법을 차용하고 프록시 방식의 AOP를 적용하기 때문에 AspectJ를 직접 사용하는 것이 아님
# 스프링에서 AOP 구현 방식
1) xml 스키마 기반의 AOP 구현
package com.kbfg.digi;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class MainClass {
public static void main(String[] args) {
AbstractApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationCTX.xml");
Student student = ctx.getBean("student", Student.class);
student.getStudentInfo();
Worker worker = ctx.getBean("worker", Worker.class);
worker.getWorkerInfo();
ctx.close();
}
}
<applicationCTX.xml>
- Namespaces에서 aop 선택
<?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"
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">
<bean id="logAop" class="com.kbfg.digi.LogAop" />
<aop:config>
<aop:aspect id="Logger" ref="logAop">
<!-- LogAop-> 공통기능 -->
<aop:pointcut id="publicM"
expression="within(com.kbfg.digi.*)" />
<!--aop:pointcut -> expression= "within(kr.soft.study.*) 이 범위에서 메서드 실행되면
전체가 pointcut이 된다. -->
<aop:around pointcut-ref="publicM" method="loggerAop" />
</aop:aspect>
<aop:aspect id="Logger" ref="logAop">
<aop:pointcut id="publicM"
expression="within(com.kbfg.digi.*)" />
<aop:before pointcut-ref="publicM" method="beforeAdvice" />
</aop:aspect>
<aop:aspect id="Logger" ref="logAop">
<aop:pointcut id="publicM"
expression="within(com.kbfg.digi.*)" />
<aop:after-returning pointcut-ref="publicM"
method="afterReturningAdvice" />
</aop:aspect>
<aop:aspect id="Logger" ref="logAop">
<aop:pointcut id="publicM"
expression="within(com.kbfg.digi.*)" />
<aop:after-throwing pointcut-ref="publicM"
method="afterThrowingAdvice" />
</aop:aspect>
<aop:aspect id="Logger" ref="logAop">
<aop:pointcut id="publicM"
expression="within(com.kbfg.digi.*)" />
<aop:after pointcut-ref="publicM" method="afterAdvice" />
</aop:aspect>
</aop:config>
<bean id="student" class="com.kbfg.digi.Student">
<property name="name" value="홍길동" />
<property name="age" value="10" />
<property name="gradeNum" value="3" />
<property name="classNum" value="5" />
</bean>
<bean id="worker" class="com.kbfg.digi.Worker">
<property name="name" value="홍길순" />
<property name="age" value="35" />
<property name="job" value="개발자" />
</bean>
</beans>
package com.kbfg.digi;
import org.aspectj.lang.ProceedingJoinPoint;
public class LogAop {
public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
String signatureStr = joinpoint.getSignature().toShortString();
System.out.println(signatureStr + "is start.");
long st = System.currentTimeMillis();
try {
Object obj = joinpoint.proceed();
return obj;
} finally {
long et = System.currentTimeMillis();
System.out.println(signatureStr + " is finished.");
System.out.println(signatureStr + " 경과시간 : "+ (et-st));
}
}
public void beforeAdvice() {
System.out.println("beforeAdvice()");
}
public void afterReturningAdvice() {
System.out.println("afterReturningAdvice");
}
public void afterThrowingAdvice() {
System.out.println("afterThrowingAdvice()");
}
public void afterAdvice() {
System.out.println("afterAdvice()");
}
}
2) @Aspect 어노테이션 기반의 aop 구현
<applicationCTX.xml>
- <aop:aspectj-autoproxy/> 태그 추가
<?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"
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">
<aop:aspectj-autoproxy/> //추가
<bean id="logAop" class="com.kbfg.digi.LogAop" />
<bean id="student" class="com.kbfg.digi.Student">
<property name="name" value="홍길동" />
<property name="age" value="10" />
<property name="gradeNum" value="3" />
<property name="classNum" value="5" />
</bean>
<bean id="worker" class="com.kbfg.digi.Worker">
<property name="name" value="홍길순" />
<property name="age" value="35" />
<property name="job" value="개발자" />
</bean>
</beans>
package com.kbfg.digi;
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.aspectj.lang.annotation.Pointcut;
@Aspect
public class LogAop {
// @Pointcut("execution(public void get*(..))") //public void인 모든 get메소드
// @Pointcut("execution(* com.kbfg.digi..*(..))") //com.kbfg.digi 패키지에 파라미터가 없는 모든 메서드
// @Pointcut("execution(* com.kbfg.digi..*.*(..))") //com.kbfg.digi 패키지 & 하위 패키지에 파라미터가 없는 모든 메소드
@Pointcut("execution(* com.kbfg.digi.Worker.*(..))") //com.kbfg.digi.Worker안의 모든 메소드
// @Pointcut("within(com.kbfg.digi.*)") //kr.soft.study. 패키지 안에 있는 모든 메서드
// @Pointcut("within(com.kbfg.digi..*)") //kr.soft.study. 패키지 및 하위 패키지 안의 모든 메서드
// @Pointcut("within(com.kbfg.digi.Worker)") //kr.soft.study.Worker 안의 모든 메서드
// @Pointcut("bean(student)") //bean - 특정 빈에 적용하겠다
// @Pointcut("bean(*ker)") //~ker로 끝나는 빈에만 적용
private void pointcutMethod() {}
@Around("pointcutMethod()")
public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
String signatureStr = joinpoint.getSignature().toShortString();
System.out.println(signatureStr + " is start.");
long st = System.currentTimeMillis();
try {
Object obj = joinpoint.proceed();
return obj;
} finally {
long et = System.currentTimeMillis();
System.out.println( signatureStr + " is finished.");
System.out.println( signatureStr + " 경과시간 : " + (et - st));
}
}
@Before("within(com.kbfg.digi.*)")
public void beforAdvice() {
System.out.println("beforAdvice()");
}
@AfterReturning("within(com.kbfg.digi.*)")
public void afterReturningAdvice() {
System.out.println("afterReturningAdvice");
}
@AfterThrowing("within(com.kbfg.digi.*)")
public void afterThrowingAdvice() {
System.out.println("afterThrowingAdvice()");
}
@After("within(com.kbfg.digi.*)")
public void afterAdvice() {
System.out.println("afterAdvice()");
}
}
'BackEnd > Spring' 카테고리의 다른 글
[Spring]IntelliJ 자바 버전 변경하는 방법 (0) | 2024.06.15 |
---|---|
[Spring]MVC에서 데이터를 주고 받는 방법 (0) | 2024.06.12 |
[Spring]Spring Environment (0) | 2024.06.10 |
[Spring]Bean 생성주기 (0) | 2024.06.10 |
[Spring]Ch9.데이터베이스:관리자 회원가입 기능 만들기_올인원 스프링 프레임워크 (1) | 2024.06.09 |