먼저 10분 테코톡에서 봤던 재미있는 에피소드를 언급하면서 시작하려고 한다.
어떤 주니어 개발자가 이런 업무 지시를 받았다.
“회원가입 하는 시간 좀 측정해서 로그로 남겨주세요.”
public void join(JoinRequest joinRequest) {
memberRepository.save(joinRequest.toMember());
}
public void join(JoinRequest joinRequest) {
stopWatch stopWatch = new StopWatch();
stopWatch.start();
try{
memberRepository.save(joinRequest.toMember());
} finally {
stopWatch.stop();
log.info("join spent {} ms", stopWatch.getLastTaskTimeMillis());
}
}
뚝딱뚝딱 만들어서 다음 날 갔더니
“너무 좋네요. 이제 우리 회사의 모든 서비스에 다 같은 형식으로 남겨주세요.”
근데 이 회사에는 1억 개의 서비스가 존재했고, 이 개발자는 퇴사했다.
코드를 좀 뜯어서 생각해보면, 실제 서비스를 제공할 때 필요없는 부분이 있다.
비즈니스 로직은 꼭 제공되어야 하지만,

시간 측정 로그 생성, 권한 체크, 트랜잭션 걸기 등등… 부가적인 부분은 그럴 필요가 없다.

이와 같은 부가적인 로직을 인프라 로직이라고 한다.
인프라 로직의 중복이 횡으로 나타나기 때문에 횡단 관심사 라고 부른다.

execution(* com.example.service.*.*(..))예시 코드를 보면 좀 더 이해가 쉽다.
@Component
@Aspect
public class _Aspect {
@Before("execution(* d_aop.b_autoproxy.*.*(..))")
public void before(){
System.out.println("출근 카드를 찍는다.");
}
}
| 용어 | 위 코드에서의 예 |
|---|---|
| Aspect | _Aspect 클래스 전체 |
| Advice | before() 메서드 (횡단 관심사: "출근 카드를 찍는다." 출력) |
| Pointcut | "execution(* d_aop.b_autoproxy.*.*(..))" (어떤 메서드에 적용할지 정하는 표현식) |
| Join Point | d_aop.b_autoproxy 패키지 내 모든 메서드 실행 시점 (실제 Advice 적용 지점) |
| Weaving | Advice(before)를 Pointcut에 맞는 Join Point에 적용하는 작업 |
| Proxy | Spring이 만들어주는 가짜 객체 (Advice를 끼워 넣기 위한 Wrapper 객체) |
Spring 컨테이너에 등록할 Bean, DI, 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:context="http://www.springframework.org/schema/context"
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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="d_aop.b_autoproxy" />
<aop:aspectj-autoproxy/>
</beans>
Component scan : <context:component-scan base-package="d_aop.b_autoproxy" />
@Component가 붙은 모든 Class 를 Bean 으로 등록@Controller : MVC 의 Controller@Service : 비즈니스 로직 담당 클래스@Repository : DAO 클래스에는 기본적으로 @Component 를 포함하고 있음.
🔥컴포넌트 스캔을 하지 않으면?
매번 클래스마다 XML에 하나하나 Bean 으로 등록해야 하고<bean id="harryPotter" class="a_regist.a_xml.bean.Book" />뭐 클래스 이름 바뀌거나 하면 고치고
실수하면 에러나고 불편한 게 여간 많은 게 아님 !!
AOP 설정 : <aop:aspectj-autoproxy/>
@Aspect 어노테이션이 붙은 클래스를 자동으로 AOP 설정@Component
@Aspect
public class _Aspect {
@Before("execution(* d_aop.b_autoproxy.*.*(..))")
public void before(){
System.out.println("출근 카드를 찍는다.");
}
@After("execution(public * d_aop.b_autoproxy.*.*(..))")
public void after(){
System.out.println("집에 간다.");
}
@AfterThrowing(value = "execution(public * d_aop.b_autoproxy.*.*(..))", throwing = "ex")
public void afterThrowing(Exception ex){
System.out.println(ex.getMessage());
System.out.println("쉬는 날이었다.");
}
@AfterReturning(pointcut = "execution(* d_aop.b_autoproxy.Man.*(..))", returning = "res")
public void afterReturning(Object res){
System.out.println(res);
}
}
이렇게 작성한 파일을 Java 코드에서 이렇게 불러온다.
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"/aop/autoproxy/application-context.xml"});
@Before("execution(* d_aop.b_autoproxy.*.*(..))")
d_aop.b_autoproxy패키지의 모든 클래스의 모든 메서드 실행 전에before()를 실행
package d_aop.b_autoproxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Run {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[]{"/aop/autoproxy/application-context.xml"});
Man man = context.getBean("man", Man.class);
man.develop();
}
}
<aop:aspectj-autoproxy/> 설정을 보고@Aspect 붙은 클래스(여기서는 _Aspect)를 찾아서@Component된 대상 객체들(Man, Woman, Child)을 Proxy 객체로 감싸서 생성함Run.java에서 man.develop()을 호출하면Proxy(Man).develop() 호출됨_Aspect.before() 실행됨Man.develop() 실행됨