[스프링 입문을 위한 자바 객체 지향의 원리와 이해] 7장 스프링 삼각형과 설정 정보 (2) AOP - Aspect? 관점? 핵심 관심사? 횡단 관심사?

김성혁·2021년 7월 8일
0

AOP는 Aspect-Oriented Programming의 약자이고, 이를 번역하면 관점 지향 프로그램이 된다.

스프링 DI가 의존성(new)에 대한 주입이라면 스프링 AOP는 로직(code) 주입이라고 할 수 있다.

AOP - Aspect? 관점? 핵심 관심사? 횡단 관심사?


횡단 관심사 : 다수의 모듈에 공통적으로 나타나는 부분

핵심 관심사 : 모듈별로 다르게 나타나는 부분

코드 = 핵심 관심사 + 횡단 관심사

객체 지향에서 로직(코드)이 있는 곳은 당연히 메서드의 안쪽이다. 메서드에서 코드를 주입할 수 있는 곳

  • Around(메서드 전 구역)
  • Before(메서드 시작 전)
  • After(메서드 종료 후)
  • AfterReturning(메서드 정상 종료 후)
  • AfterThrowing(메서드에서 예외가 발생하면서 종료된 후)

총 5군데다.

package aop002;

public interface Person {
    void runSomething();
}
package aop002;

public class Boy implements Person {
    public void runSomething() {
        System.out.println("컴퓨터로 게임을 한다.");
    }
}
package aop002;

public class Girl implements Person{
    public void runSomething() {
        System.out.println("요리를 한다.");
    }
}
package aop002;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
    @Before("execution(*runSomething())")
    public void before(JoinPoint joinPoint) {
        System.out.println("얼굴 인식 확인: 문을 개방하라");
        //System.out.println("열쇠로 문을 열고 집에 들어간다.");
    }
}

  • @Aspect는 이클래스를 이제 AOP에서 사용하겠다는 의미
  • @Before는 대상 메서드 실행 전에 이 메서드를 실행하겠다는 의미
  • JoinPoint는 @Before에서 선언된 메서드인 aop002.Boy.runSomething()을 의미
  • AOP를 적용하면서 Boy.java에 단일 책임 원칙(SRP)을 자연스럽게 적용할 수 있다.
  • MyAspect.java가 횡단 관심사를 처리한다.

package aop002;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Start {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop002/aop002.xml");

        Person romeo = context.getBean("boy", Person.class);
        Person juliet = context.getBean("girl", Person.class);

        romeo.runSomething();
        juliet.runSomething();
    }
}
<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />

    <bean id="myAspect" class="aop002.MyAspect" />
    <bean id="boy" class="aop002.Boy" />
    <bean id="girl" class="aop002.Girl" />
</beans>

이 이외에도 빌드 도구에 AOP에 대한 의존성 주입하기

  • 빈이 설정되는 이유는 객체의 생성과 의존성 주입을 스프링 프레임워크에 위임하기 위해서다. 스프링 프레임워크는 객체 생성 뿐 아니라 객체의 생명주기 전반에 걸쳐 빈의 생성에서 소멸까지 관리한다.
  • <aop:aspectj-autoproxy />는 프록시 패턴을 이용해 횡단 관심사를 핵심 관심사에 주입한다는 의미(스프링 프레임워크에게 AOP 프록시를 자동으로 사용하라고 알려주는 지시자)

👨🏻‍💻 스프링 AOP 요약
스프링 AOP는 인터페이스(interface) 기반이다.
스프링 AOP는 프록시(proxy) 기반이다.
스프링 AOP는 런타임(runtime) 기반이다.

Pointcut - 자르는 지점? Aspect 적용 위치 지정자!


  • *runSomething()이 바로 Pointcut이다.
  • @Before("execution(*runSomething())")의 의미
    • 지금 선언하고 있는 메서드(public void before)를 기울어진 볼드체 부분의 메서드(*runSomething())가 실행되기 전(@Before)에 실행하라는 의미. 여기서 public void before는 횡단 관심사를 실행하는 메서드가 된다.
  • Pointcut은 횡단 관심사를 적용할 타깃 메서드를 선택하는 지시자(메서드 선택 필터)라는 의미 (타깃 클래스의 타킷 메서드 지정자)
  • 메서드뿐만 아니라 속성 등에도 Aspect를 적용할 수 있기에 그것들까지 고려한다면 Aspect 적용 위치 지정자 (지시자)가 맞는 표현

[접근제한자패턴] 리턴타입패턴 [패키지&클래스패턴.]메서드이름패턴(파라미터패턴)[throws 예외패턴]

[ ] 은 생략 가능을 의미

JoinPoint - 연결점? 연결 가능한 지점!


  • Pointcut의 후보가 되는 모든 메서드들이 JoinPoint, 즉 Aspect 적용이 가능한 지점
  • "Aspect 적용이 가능한 모든 지점"
  • 스프링 AOP에서 JoinPoint란 스프링 프레임워크가 관리하는 빈의 모든 메서드에 해당
  • JoinPoint 파라미터를 이용하면 실행 시점에 실제 호출된 메서드가 무엇인지, 실제 호출된 메서드를 소유한 객체가 무엇인지, 또 호출된 메서드의 파라미터는 무엇인지 등의 정보를 확인할 수 있다.

Advice - 조언? 언제, 무엇을!


  • pointcut에 적용할 로직, 즉 메서드를 의미
  • 여기에 언제라는 개념까지 포함
  • Pointcut에 언제, 무엇을 적용할지 정의한 메서드

package aop002;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
    **@Before**("execution(*runSomething())")
    **public void before(JoinPoint joinPoint) {
        System.out.println("얼굴 인식 확인: 문을 개방하라");
    }**
}
// Pointcut이 시작되기 전(@Before)에 before() 메서드를 실행

Aspect - 관점? 측면? Advisor의 집합체!


  • AOP에서 Aspect는 여러 개의 Advice와 여러 개의 Pointcut의 결합체를 의미

    Aspect = Advice들 + Pointcut들

  • Advice는 [언제(When), 무엇을(What)]를 의미, Pointcut은 [어디에(Where)]를 의미

    → 결국 Aspect는 When + Where + What(언제, 어디서, 무엇을)이 된다.

Advisor - 조언자? 어디서, 언제, 무엇을!


  • Advisor = 한 개의 Advice + 한 개의 Pointcut

POJO와 XML 기반 AOP


  • 기존의 어노테이션 기반으로 작성한 AOP 예제를 POJO와 XML 기반으로 전환

// POJO & XML 기반 - 스프링 프레임워크에 종속되지 않음
package aop003;

import org.aspectj.lang.JoinPoint;

public class MyAspect {
    public void before(JoinPoint joinPoint) {
        System.out.println("얼굴 인식 확인: 문을 개방하라");
        //System.out.println("열쇠로 문을 열고 집에 들어간다.");
    }
}
<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />

    <bean id="myAspect" class="aop003.MyAspect" />
    <bean id="boy" class="aop003.Boy" />
    <bean id="girl" class="aop003.Girl" />

    <aop:config>
        <aop:aspect ref="myAspect">
            <aop:before method="before" pointcut="execution(* runSomething())" />
        </aop:aspect>
    </aop:config>
</beans>

  • after 어드바이스
package aop004;

import org.aspectj.lang.JoinPoint;

public class MyAspect {
    public void before(JoinPoint joinPoint) {
        System.out.println("얼굴 인식 확인: 문을 개방하라");
    }

    public void lockDoor(JoinPoint joinPoint) {
        System.out.println("주인님 나갔다: 어이 문 잠가!!!");
    }
}
<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />

    <bean id="myAspect" class="aop004.MyAspect" />
    <bean id="boy" class="aop004.Boy" />
    <bean id="girl" class="aop004.Girl" />

    <aop:config>
        <aop:aspect ref="myAspect">
            <aop:before method="before" pointcut="execution(* runSomething())" />
            <aop:after method="lockDoor" pointcut="execution(* runSomething())" />
        </aop:aspect>
    </aop:config>
</beans>

XML 리펙토링 후 (코드의 중복을 없애기)

<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />

    <bean id="myAspect" class="aop004.MyAspect" />
    <bean id="boy" class="aop004.Boy" />
    <bean id="girl" class="aop004.Girl" />

    <aop:config>
        <aop:pointcut id="iampc" expression="execution(* runSomething())"/>
        <aop:aspect ref="myAspect">
            <aop:before method="before" pointcut-ref="iampc" />
            <aop:after method="lockDoor" pointcut-ref="iampc" />
        </aop:aspect>
    </aop:config>
</beans>

  • 어노테이션 기반
package aop005;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;

public class MyAspect {
    @Before("execution(* runSomething()")
    public void before(JoinPoint joinPoint) {
        System.out.println("얼굴 인식 확인: 문을 개방하라");
    }

    @After("execution(* runSomething())")
    public void lockDoor(JoinPoint joinPoint) {
        System.out.println("주인님 나갔다: 어이 문 잠가!!!");
    }
}
<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy />

    <bean id="myAspect" class="aop005.MyAspect" />
    <bean id="boy" class="aop005.Boy" />
    <bean id="girl" class="aop005.Girl" />
</beans>

어노테이션 기반 리펙토링 후

package aop006;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {
    @Pointcut("execution(* runSomething())")
    private void iampc() {
        //여긴 무엇을 작성해도 의미가 없어요.
    }

    @Before("iampc()")
    public void before(JoinPoint joinPoint) {
        System.out.println("얼굴 인식 확인: 문을 개방하라");
    }

    @After("iampc()")
    public void lockDoor(JoinPoint joinPoint) {
        System.out.println("주인님 나갔다: 어이 문 잠가!!!");
    }
}

0개의 댓글