[Spring] 스프링 AOP 공부하기

heegon·2025년 11월 6일

목록 보기
12/17

개념

'핵심 기능 코드''공통 기능 코드'분류시켜 만드는 방식.
(같은 기능이 여러 번 사용되는 공통 기능 코드의 경우, 중복 반복 작성되기 때문이다.)

여기서 공통 기능이란 자주 사용하는 코드들을 따로 빼서 모아놓는 것을 말한다.
그리고 핵심 기능을 실행할 때, 특정 시기(실행 전/실행 후/실행 전후 등)에 공통 기능을 실행해주는 방식으로 사용된다.

ex) 예를 들면,
System.out.println("메소드 실행 시작!") -> 메소드 실행 시작 전마다 이 기능을 작동시킬 것이므로 공통 기능으로 분류하여 구현.
public void doPlay1() -> 몇 번 실행하지 않으므로 핵심 기능으로 분류.


기본 용어

  • Aspect : 공통 기능 을 의미한다.
  • Advice : 공통 기능의 기능 자체 를 의미한다.
  • JoinPoint : 공통 기능을 적용해야 하는 부분 (스프링에서 메소드)
  • Pointcut : 공통 기능의 일부분으로 실제로 공통 기능이 적용 되는 부분
  • Weaving : 공통 기능을 핵심 기능에 적용하는 행위 를 의미.

AOP 적용 방식 1 : XML

✅pom.xml

가장 먼저 pom.xml 파일에서 의존 설정을 해줘야 한다.
아래 2개를 추가해주면 된다. version은 잘 맞춰주어야 한다.

<!-- cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.12</version>
</dependency>
		
<!-- AOP -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.7.4</version>
</dependency>

✅Student.java

데이터 클래스 1번

public class Student {

	private String name;
	private int age;
	private int gradeNum;
	private int classNum;
	
	public void getStudentInfo() {
		System.out.println("이름 : " + getName());
		System.out.println("나이 : " + getAge());
		System.out.println("학년 : " + getGradeNum());
		System.out.println("반 번호 : " + getClassNum());
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public int getGradeNum() {
		return gradeNum;
	}
	public void setGradeNum(int gradeNum) {
		this.gradeNum = gradeNum;
	}
	public int getClassNum() {
		return classNum;
	}
	public void setClassNum(int classNum) {
		this.classNum = classNum;
	}
	
}

✅Worker.java

데이터 클래스 2번

public class Worker {

	private String name;
	private int age;
	private String job;
	
	public void getWorkerInfo() {
		System.out.println("이름 : " + getName());
		System.out.println("나이 : " + getAge());
		System.out.println("직업 : " + getJob());
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getJob() {
		return job;
	}

	public void setJob(String job) {
		this.job = job;
	}
	
}

✅MainClass.java

앞 글인 DI 의존주입에서 사용한 Main 클래스와 다른 점이 없다.
이번에는 xml 방식으로 의존 주입을 하였다.

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();
		
		System.out.println("=============");
		
		Worker worker = ctx.getBean("worker", Worker.class);
		worker.getWorkerInfo();
		
		ctx.close();

	}

}

어노테이션

✅applicationCTX.xml

기본 형태 설명

<bean id="아이디" class="패키지.메소드" />

<aop:config>
	<aop:aspect id="식별자" ref="아이디">
    	<aop:pointcut id="포인트컷이름" expression="적용범위 표현식" />
        <aop:적용스타일 pointcut-ref="포인트컷이름" method="Advice메서드 이름" />
    </aop:aspect>
</aop:config>

적용 스타일
aop:around -> 대상 메소드 실행 전후 모두 제어 가능 (인자 반드시 필요)
aop:before -> 실행 전
aop:after-returning -> 정상 종료 후
aop:after-throwing -> 예외 발생 시
aop:after -> 항상 마지막에 실행 (예외처리 구문 중 finally 와 유사)

적용범위 표현식
'execution, within, bean' 이렇게 3가지가 있다.
expression="within(kr.study.spring.*)" -> kr.study.spring 패키지 바로 아래에 있는 클래스들 안의 메소드에 적용.
expression="within(kr.study.spring..*) -> kr.study.spring 패키지의 하위 패키지 전부의 클래스 안의 메소드까지 적용.
expression="execution(* kr.study.spring.Worker.*())" -> Worker 클래스의 메소드에만 적용.
expression="bean(student)") -> student 라는 이름을 가진 클래스의 메소드에만 적용
expression="bean(*ker)") -> ~ker로 끝나는 클래스의 메소드에만 적용된다.

⭐⭐⭐ 그러니까 쉽게 생각하자면, 현재 Student 클래스가 있다. 이 클래스에게만 AOP를 적용하고 싶다면 kr.study.spring.student 이렇게 적용범위 표현식을 사용하면 된다. 어떤 클래스의 앞뒤, 혹은 앞, 혹은 뒤에 공통 기능을 넣을지를 적용범위 표현식에서 지정한다고 생각하면 된다. 앞뒤, 앞, 뒤 같은 타이밍에 관한 것은 위의 적용 스타일로 지정한다.

예시 코드

<!-- AOP 관련 -->
	<bean id="logAop"  class="kr.study.spring.LogAop" />
	
	<aop:config>
		<aop:aspect id="logger" ref="logAop">
			<aop:pointcut id="publicM" expression="within(kr.study.spring.*)" />
			<aop:around pointcut-ref="publicM" method="loggerAop" />
		</aop:aspect>
		
		<aop:aspect id="logger" ref="logAop">
			<aop:pointcut id="publicM" expression="within(kr.study.spring.*)" />
			<aop:before pointcut-ref="publicM" method="beforeAdvice" />
		</aop:aspect>
		
		<aop:aspect id="logger" ref="logAop">
			<aop:pointcut id="publicM" expression="within(kr.study.spring.*)" />
			<aop:after-returning pointcut-ref="publicM" method="afterReturningAdvice" />
		</aop:aspect>
		
		<aop:aspect id="logger" ref="logAop">
			<aop:pointcut id="publicM" expression="within(kr.study.spring.*)" />
			<aop:after-throwing pointcut-ref="publicM" method="afterAdvice" />
		</aop:aspect>
	</aop:config>

✅LogAop.java

⭐ 공통 기능을 담은 클래스 (AOP 클래스) ⭐
=> 공통으로 적용할 로직을 모아두는 클래스이다.
=> 여러 번 사용하는 기능(클래스나 메소드)를 모아두었다고 생각하면 쉽다.

자주 쓸 것 같은 코드 구조 1 (메소드의 실행 전후 모두 적용 가능)

public Object 메소드명(ProceedingJoinPoint pjp) throws Throwable {
	//전 처리
    Object result = pjp.proceed(); //실제 메소드 실행
    //후 처리
    return result;
}

여기에서 pjp.getSignature() 는 현재 실행 중인 메소드의 정보를 가져오는 메소드이다.

예시 코드

public class LogAop {
	
    //공통 기능 메소드 1 (메소드 실행 전후 제어)
	public Object loggerAop(ProceedingJoinPoint joinpoint) throws Throwable {
		String signatureStr = joinpoint.getSignature().toShortString();
		System.out.println(signatureStr + " is start.");
		long startTime = System.currentTimeMillis();
        //System.currentTimeMillis() -> 현재 시간을 ms초로 알려줌.
		
		try {
			Object obj = joinpoint.proceed();
			return obj;
		} finally {
			long endTime = System.currentTimeMillis();
			System.out.println(signatureStr + " is finished.");
			System.out.println(signatureStr + " 경과시간 : " + (endTime - startTime));
		}
	}
	
    //실행 전
	public void beforeAdvice(JoinPoint joinPoint) {
		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()");
	}
}

AOP 적용 방식 2 : 어노테이션

pom.xml, MainClass.java, Student.java, Worker.java 의 코드는 위와 동일하다.

어노테이션

✅applicationCTX.xml

<!-- AOP 관련 -->
<aop:aspectj-autoproxy />
<bean id="logAop" class="kr.study.spring.LogAop" />

놀랍게도 이게 다다.

✅LogAop.java

@Aspect -> AOP 클래스 임을 표현(?)

@Pointcut("범위 지정 표현식") -> PointCut

@Around("포인트컷 메소드()") -> around, before, after 등등 명시

예시 코드

@Aspect
public class LogAop {

	@Pointcut("within(kr.study.spring.*)")
	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("pointcutMethod()")
	public void beforeAdvice(JoinPoint joinPoint) {
		System.out.println("beforeAdvice()");
	}
	
	@AfterReturning("pointcutMethod()")
	public void afterReturningAdvice() {
		System.out.println("afterReturningAdvice()");
	}
	
	
	@AfterThrowing("pointcutMethod()")
	public void afterThrowingAdvice() {
		System.out.println("afterThrowingAdvice()");
	}
	
	
	@After("pointcutMethod()")
	public void afterAdvice() {
		System.out.println("afterAdvice()");
	}
}
profile
❤️

0개의 댓글