CodeGym/Java Blog/무작위의/AOP란? 관점 지향 프로그래밍의 원리
John Squirrels
레벨 41
San Francisco

AOP란? 관점 지향 프로그래밍의 원리

무작위의 그룹에 게시되었습니다
회원
안녕, 남자와 여자! 기본 개념을 이해하지 않고 기능을 구축하기 위한 프레임워크와 접근 방식을 탐구하는 것은 상당히 어렵습니다. 그래서 오늘 우리는 그러한 개념 중 하나인 AOP( Aspect-Oriented Programming) 에 대해 이야기할 것입니다 . AOP란?  관점 지향 프로그래밍의 원리 - 1이 주제는 쉽지 않고 직접적으로 거의 사용되지 않지만 많은 프레임워크와 기술에서 내부적으로 이를 사용합니다. 그리고 물론 가끔 인터뷰를 할 때 이것이 어떤 종류의 짐승이며 어디에 적용할 수 있는지 일반적인 용어로 설명하라는 요청을 받을 수도 있습니다. 이제 Java에서 AOP 의 기본 개념과 몇 가지 간단한 예를 살펴보겠습니다 . 이제 AOP는 Aspect-Oriented Programming 의 약자입니다., 교차 절단 문제를 분리하여 응용 프로그램의 다른 부분의 모듈성을 높이기 위한 패러다임입니다. 이를 달성하기 위해 원래 코드를 변경하지 않고 기존 코드에 추가 동작을 추가합니다. 즉, 수정된 코드를 변경하지 않고 메서드와 클래스 위에 추가 기능을 매달아 놓은 것으로 생각할 수 있습니다. 이것이 필요한 이유는 무엇입니까? 조만간 일반적인 객체 지향 접근 방식이 특정 문제를 항상 효과적으로 해결할 수 없다는 결론을 내립니다. 그리고 그 순간이 오면 AOP가 도움이 되어 애플리케이션 구축을 위한 추가 도구를 제공합니다. 그리고 추가 도구는 소프트웨어 개발의 유연성 증가를 의미하며 이는 특정 문제를 해결하기 위한 더 많은 옵션을 의미합니다.

AOP 적용

Aspect 지향 프로그래밍은 교차 절단 작업을 수행하도록 설계되었으며, 이는 서로 다른 방법으로 여러 번 반복될 수 있는 모든 코드가 될 수 있으며 별도의 모듈로 완전히 구조화될 수 없습니다. 따라서 AOP에서는 이를 메인 코드 외부에 유지하고 수직으로 선언할 수 있습니다. 예를 들면 애플리케이션에서 보안 정책을 사용하는 것입니다. 일반적으로 보안은 애플리케이션의 여러 요소를 통해 실행됩니다. 또한 애플리케이션의 보안 정책은 애플리케이션의 모든 기존 부분과 새로운 부분에 동일하게 적용되어야 합니다. 동시에 사용 중인 보안 정책 자체가 진화할 수 있습니다. 이것은 AOP를 사용하기에 완벽한 장소입니다 . 또한 또 다른 예는 로깅 입니다.. 로깅 기능을 수동으로 추가하는 대신 로깅에 대한 AOP 접근 방식을 사용하면 몇 가지 이점이 있습니다.
  1. 로깅을 위한 코드는 쉽게 추가하고 제거할 수 있습니다. 일부 측면의 몇 가지 구성을 추가하거나 제거하기만 하면 됩니다.

  2. 로깅을 위한 모든 소스 코드는 한 곳에 보관되므로 사용되는 모든 곳을 수동으로 추적할 필요가 없습니다.

  3. 로깅 코드는 이미 작성된 메서드 및 클래스 또는 새로운 기능에 상관없이 어디에나 추가할 수 있습니다. 이것은 코딩 오류의 수를 줄입니다.

    또한 디자인 구성에서 측면을 제거할 때 모든 추적 코드가 사라지고 아무 것도 누락되지 않았는지 확인할 수 있습니다.

  4. Aspect는 계속해서 개선하고 사용할 수 있는 별도의 코드입니다.
AOP란?  관점 지향 프로그래밍의 원리 - 2AOP는 또한 예외 처리, 캐싱 및 재사용이 가능하도록 특정 기능을 추출하는 데 사용됩니다.

AOP의 기본 원칙

이 주제에서 더 나아가기 위해 먼저 AOP의 주요 개념을 알아봅시다. 조언 — 조인 포인트에서 호출되는 추가 논리 또는 코드입니다. 어드바이스는 조인 포인트 이전, 이후 또는 대신에 수행될 수 있습니다(자세한 내용은 아래 참조). 가능한 조언 유형 :
  1. 이전 — 이 유형의 어드바이스는 대상 메서드, 즉 조인 포인트가 실행되기 전에 실행됩니다. aspect를 클래스로 사용할 때 @Before 어노테이션을 사용하여 어드바이스를 이전에 온 것으로 표시한다. aspect를 .aj 파일로 사용할 때 이것은 before() 메소드가 될 것이다 .

  2. After — 메서드(조인 포인트) 실행이 완료된 후 실행되는 어드바이스입니다. 정상 실행 시와 예외 발생 시 모두 마찬가지입니다.

    aspect를 클래스로 사용할 때 @After 어노테이션을 사용하여 이것이 뒤에 오는 어드바이스임을 나타낼 수 있다.

    .aj 파일 로 aspect를 사용할 때 이것은 after() 메소드입니다.

  3. After Returning — 이 조언은 대상 메서드가 오류 없이 정상적으로 완료될 때만 수행됩니다.

    관점이 클래스로 표현될 때 @AfterReturning 주석을 사용하여 어드바이스가 성공적인 완료 후 실행되는 것으로 표시할 수 있습니다.

    aspect를 .aj 파일로 사용할 때 이것은 (Object obj) 메서드를 반환하는 after() 가 됩니다 .

  4. After Throwing — 이 조언은 메서드, 즉 조인 포인트가 예외를 throw하는 인스턴스를 위한 것입니다. 이 조언을 사용하여 특정 종류의 실패한 실행을 처리할 수 있습니다(예: 필요한 추적 수준으로 전체 트랜잭션 또는 로그 롤백).

    클래스 측면에서 @AfterThrowing 어노테이션은 예외를 던진 후에 이 어드바이스가 사용되었음을 나타내기 위해 사용됩니다.

    aspect를 .aj 파일로 사용할 때 이것은 after() throw(예외 e) 메서드 가 됩니다 .

  5. 주변 — 아마도 가장 중요한 유형의 조언 중 하나일 것입니다. 즉, 예를 들어 주어진 조인 포인트 방법을 수행할지 여부를 선택하는 데 사용할 수 있는 조인 포인트를 둘러쌉니다.

    조인 포인트 메서드가 실행되기 전과 후에 실행되는 어드바이스 코드를 작성할 수 있습니다.

    around 어드바이스는 조인 포인트 메소드를 호출하고 메소드가 무언가를 리턴하는 경우 리턴 값을 담당합니다. 즉, 이 어드바이스에서는 호출하지 않고 대상 메소드의 작동을 간단하게 시뮬레이트하고 원하는 것을 리턴 결과로 리턴할 수 있습니다.

    측면을 클래스로 지정하면 조인 포인트를 래핑하는 어드바이스를 생성하기 위해 @Around 주석을 사용합니다 . .aj 파일 형식의 애스펙트를 사용할 때 이 메서드는 around() 메서드 가 됩니다 .

조인 포인트 — 어드바이스가 적용되어야 하는 실행 중인 프로그램의 포인트(즉, 메소드 호출, 객체 생성, 변수 액세스). 즉, 코드 인젝션을 할 곳(어드바이스를 적용해야 할 곳)을 찾기 위해 사용하는 일종의 정규표현식이다. Pointcut — 조인 포인트 세트 . pointcut은 주어진 조언이 주어진 조인 포인트에 적용 가능한지 여부를 결정합니다. Aspect — 크로스 커팅 기능을 구현하는 모듈 또는 클래스입니다. Aspect는 일부 pointcut 에 의해 정의된 조인 포인트조언을 적용하여 나머지 코드의 동작을 변경합니다 . 즉, Advice와 Join Point의 조합입니다. 소개— 외부 코드에 aspect의 기능을 추가하기 위해 클래스 구조 변경 및/또는 상속 계층 구조 변경. Target — 어드바이스가 적용될 객체. 위빙(Weaving) — 어드바이스 프록시 객체를 생성하기 위해 애스펙트를 다른 객체에 연결하는 프로세스. 이는 컴파일 시간, 로드 시간 또는 런타임에 수행할 수 있습니다. 직조에는 세 가지 유형이 있습니다.
  • Compile-time weaving — aspect의 소스 코드와 aspect를 사용하는 코드가 있다면 AspectJ 컴파일러를 사용하여 소스 코드와 aspect를 직접 컴파일할 수 있다.

  • 컴파일 후 위빙(바이너리 위빙) — 소스 코드 변환을 사용하여 aspect를 코드로 엮을 수 없거나 원하지 않는 경우 이전에 컴파일된 클래스 또는 jar 파일을 가져 와서 aspect를 주입할 수 있습니다.

  • Load-time weaving — 클래스 로더가 클래스 파일을 로드하고 JVM에 대한 클래스를 정의할 때까지 지연되는 바이너리 위빙입니다.

    이를 지원하려면 하나 이상의 위빙 클래스 로더가 필요합니다. 런타임에 의해 명시적으로 제공되거나 "위빙 에이전트"에 의해 활성화됩니다.

AspectJ — 크로스 커팅 작업을 수행하는 기능을 구현하는 AOP 패러다임 의 특정 구현입니다 . 문서는 여기에서 찾을 수 있습니다 .

자바의 예

다음으로 AOP를 더 잘 이해하기 위해 작은 "Hello World" 스타일 예제를 살펴보겠습니다. 방망이 오른쪽에서 우리의 예제는 컴파일 타임 위빙을 사용한다는 점에 주목하겠습니다 . 먼저 pom.xml 파일 에 다음 종속성을 추가해야 합니다 .
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
일반적으로 특별한 ajc 컴파일러는 aspect를 사용하는 방법이다. IntelliJ IDEA 는 기본적으로 포함하지 않으므로 애플리케이션 컴파일러로 선택할 때 5168 75 AspectJ 배포에 대한 경로를 지정해야 합니다. 이것이 첫 번째 방법이었습니다. 내가 사용한 두 번째는 pom.xml 파일에 다음 플러그인을 등록하는 것입니다.
<build>
  <plugins>
     <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
           <complianceLevel>1.8</complianceLevel>
           <source>1.8</source>
           <target>1.8</target>
           <showWeaveInfo>true</showWeaveInfo>
           <<verbose>true<verbose>
           <Xlint>ignore</Xlint>
           <encoding>UTF-8</encoding>
        </configuration>
        <executions>
           <execution>
              <goals>
                 <goal>compile</goal>
                 <goal>test-compile</goal>
              </goals>
           </execution>
        </executions>
     </plugin>
  </plugins>
</build>
그런 다음 Maven 에서 다시 가져오고 mvn clean compile 을 실행하는 것이 좋습니다 . 이제 예제로 직접 진행하겠습니다.

예 1

메인 클래스를 만들어 봅시다 . 여기에는 콘솔에 전달된 이름을 인쇄하는 진입점과 메서드가 있습니다.
public class Main {

  public static void main(String[] args) {
  printName("Tanner");
  printName("Victor");
  printName("Sasha");
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
여기에는 복잡한 것이 없습니다. 이름을 전달하고 콘솔에 표시했습니다. 지금 프로그램을 실행하면 콘솔에 다음이 표시됩니다.
태너 빅터 사샤
이제 AOP의 장점을 활용할 때입니다. 이제 aspect 파일을 만들어야 합니다 . 두 가지 종류가 있습니다. 첫 번째는 .aj 파일 확장자를 가집니다. 두 번째는 주석을 사용하여 AOP 기능을 구현하는 일반 클래스입니다 . 먼저 확장자가 .aj 인 파일을 살펴보겠습니다 .
public aspect GreetingAspect {

  pointcut greeting() : execution(* Main.printName(..));

  before() : greeting() {
     System.out.print("Hi, ");
  }
}
이 파일은 클래스와 비슷합니다. 여기서 무슨 일이 일어나는지 봅시다. pointcut 은 조인 포인트 집합입니다. greeting()은 이 포인트컷의 이름입니다; : 실행은 Main.printName(...) 메서드의 모든( * ) 호출을 실행하는 동안 적용하도록 나타냅니다 . 다음은 대상 메서드가 호출되기 전에 실행되는 특정 어드바이스( before()) 입니다. : greeting()은 이 어드바이스가 응답하는 컷포인트입니다. 음, 아래에서 우리가 이해하는 Java 언어로 작성된 메서드 자체의 본문을 볼 수 있습니다. 이 측면이 있는 상태에서 main을 실행하면 다음 콘솔 출력이 표시됩니다.
안녕, 태너 안녕, 빅터 안녕, 사샤
printName 메서드에 대한 모든 호출이 aspect 덕분에 수정되었음을 알 수 있습니다 . 이제 애스펙트가 주석이 있는 Java 클래스로 어떻게 보이는지 살펴보겠습니다.
@Aspect
public class GreetingAspect{

  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Hi, ");
  }
}
.aj 애스펙트 파일 이후에는 여기에서 모든 것이 더 명확해집니다.
  • @Aspect는 이 클래스가 관점임을 나타냅니다.
  • @Pointcut("execution(* Main.printName(String))") 은 유형이 String 인 입력 인수를 사용하여 Main.printName 에 대한 모든 호출에 대해 트리거되는 컷포인트입니다 .
  • @Before("greeting()")은 greeting() 컷포인트 에 지정된 코드를 호출하기 전에 적용되는 조언입니다 .
이 측면으로 main을 실행해도 콘솔 출력이 변경되지 않습니다.
안녕, 태너 안녕, 빅터 안녕, 사샤

예 2

클라이언트를 위해 몇 가지 작업을 수행하는 메서드가 있고 이 메서드를 main 에서 호출한다고 가정합니다 .
public class Main {

  public static void main(String[] args) {
  performSomeOperation("Tanner");
  }

  public static void performSomeOperation(String clientName) {
     System.out.println("Performing some operations for Client " + clientName);
  }
}
@Around 주석을 사용하여 "의사 거래"를 생성해 보겠습니다.
@Aspect
public class TransactionAspect{

  @Pointcut("execution(* Main.performSomeOperation(String))")
  public void executeOperation() {
  }

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Opening a transaction...");
     try {
        joinPoint.proceed();
        System.out.println("Closing a transaction...");
     }
     catch (Throwable throwable) {
        System.out.println("The operation failed. Rolling back the transaction...");
     }
  }
  }
ProceedingJoinPoint 개체의 진행 메서드를 사용하여 어드바이스에서 위치를 결정하기 위해 래핑 메서드를 호출합니다. 따라서 위 메소드의 코드는 joinPoint.proceed(); Before 이고 그 아래의 코드는 After 입니다 . main 을 실행하면 콘솔에 다음과 같이 표시됩니다.
트랜잭션 열기... 클라이언트 Tanner에 대한 일부 작업 수행 트랜잭션 닫기...
그러나 실패한 작업을 시뮬레이트하기 위해 메서드에서 예외를 던지고 예외를 적용하면 다음과 같습니다.
public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
그런 다음 다음 콘솔 출력을 얻습니다.
트랜잭션 열기... Client Tanner에 대한 일부 작업 수행 중 작업이 실패했습니다. 트랜잭션 롤백 중...
그래서 우리가 여기서 끝낸 것은 일종의 오류 처리 기능입니다.

예 3

다음 예에서는 콘솔에 로그인하는 것과 같은 작업을 수행해 보겠습니다. 먼저 유사 비즈니스 로직을 추가한 Main 을 살펴보세요 .
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<some value>");
     String valueForCheck = main.getValue();
     main.checkValue(valueForCheck);
  }

  public void setValue(String value) {
     this.value = value;
  }

  public String getValue() {
     return this.value;
  }

  public void checkValue(String value) throws Exception {
     if (value.length() > 10) {
        throw new Exception();
     }
  }
}
main 에서 setValue 를 사용하여 value 인스턴스 변수 에 값을 할당합니다 . 그런 다음 getValue를 사용하여 값을 가져온 다음 checkValue를 호출하여 10자보다 긴지 확인합니다. 그렇다면 예외가 발생합니다. 이제 메서드 작업을 기록하는 데 사용할 측면을 살펴보겠습니다.
@Aspect
public class LogAspect {

  @Pointcut("execution(* *(..))")
  public void methodExecuting() {
  }

  @AfterReturning(value = "methodExecuting()", returning = "returningValue")
  public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
     if (returningValue != null) {
        System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Successful execution: method — %s, class — %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
무슨 일이야? @Pointcut("execution(* *(..))") 은 모든 메서드의 모든 호출에 참여합니다. @AfterReturning(value = "methodExecuting()", Returning = "returningValue")는 대상 메서드가 성공적으로 실행된 후 실행될 어드바이스입니다. 여기에는 두 가지 경우가 있습니다.
  1. 메서드에 반환 값이 있는 경우 — if (returningValue! = Null) {
  2. 반환 값이 없을 때 — else {
@AfterThrowing(value = "methodExecuting()", Throwing = "exception")은 오류가 발생한 경우, 즉 메서드가 예외를 throw했을 때 트리거되는 어드바이스입니다. 따라서 main 을 실행하면 일종의 콘솔 기반 로깅을 얻을 수 있습니다.
성공적인 실행: method — setValue, class — Main 성공적인 실행: method — getValue, class — Main, 반환 값 — <some value> 예외 발생: method — checkValue, class — 주요 예외 — java.lang.Exception 예외 발생: method — 메인, 클래스 — 메인, 예외 — java.lang.Exception
예외를 처리하지 않았기 때문에 스택 추적을 계속 얻을 수 있습니다. 예외 및 예외 처리에 대한 내용은 Java의 예외예외: catch 및 처리AOP란?  관점 지향 프로그래밍의 원리 - 3 문서에서 읽을 수 있습니다 . 오늘은 그게 전부입니다. 오늘 우리는 AOP 에 대해 알게 되었고 , 이 짐승이 일부 사람들이 생각하는 것처럼 무섭지 않다는 것을 알 수 있었습니다. 모두 안녕!
코멘트
  • 인기
  • 신규
  • 이전
코멘트를 남기려면 로그인 해야 합니다
이 페이지에는 아직 코멘트가 없습니다