'핵심 기능 코드' 와 '공통 기능 코드' 를 분류시켜 만드는 방식.
(같은 기능이 여러 번 사용되는 공통 기능 코드의 경우, 중복 반복 작성되기 때문이다.)
여기서 공통 기능이란 자주 사용하는 코드들을 따로 빼서 모아놓는 것을 말한다.
그리고 핵심 기능을 실행할 때, 특정 시기(실행 전/실행 후/실행 전후 등)에 공통 기능을 실행해주는 방식으로 사용된다.
ex) 예를 들면,
System.out.println("메소드 실행 시작!") -> 메소드 실행 시작 전마다 이 기능을 작동시킬 것이므로 공통 기능으로 분류하여 구현.
public void doPlay1() -> 몇 번 실행하지 않으므로 핵심 기능으로 분류.
✅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()");
}
}
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()");
}
}