JavaRush /Java Blog /Random-KO /AOP란 무엇입니까? 관점 지향 프로그래밍 기본 사항

AOP란 무엇입니까? 관점 지향 프로그래밍 기본 사항

Random-KO 그룹에 게시되었습니다
안녕하세요 여러분! 기본 개념을 이해하지 않고는 기능 구축에 대한 프레임워크와 접근 방식을 탐구하는 것이 매우 어렵습니다. 그래서 오늘 우리는 이러한 개념 중 하나인 AOP 또는 관점 지향 프로그래밍 에 대해 이야기하겠습니다 . AOP란 무엇입니까?  관점지향 프로그래밍의 기초 - 1이는 쉬운 주제가 아니며 직접적으로 자주 사용되지는 않지만 많은 프레임워크와 기술에서 이를 내부적으로 사용합니다. 물론 때로는 인터뷰 중에 이것이 어떤 종류의 동물이고 어디에 사용될 수 있는지 일반적인 용어로 알려 달라는 요청을 받을 수도 있습니다. 이제 Java에서 AOP의 기본 개념과 몇 가지 간단한 예를 살펴보겠습니다 . AOP란 무엇입니까?  관점지향 프로그래밍의 기초 - 2따라서 AOP ( Aspect 지향 프로그래밍 )는 교차 관심사를 분리하여 애플리케이션의 다양한 부분의 모듈성을 높이는 것을 목표로 하는 패러다임입니다. 이를 위해 원래 코드를 변경하지 않고 기존 코드에 추가 동작을 추가합니다. 즉, 수정된 코드를 수정하지 않고 메서드와 클래스 위에 추가 기능을 추가하는 것처럼 보입니다. 이것이 왜 필요한가요? 조만간 우리는 일반적인 객체 지향 접근 방식이 특정 문제를 항상 효과적으로 해결할 수는 없다는 결론에 도달합니다. 그러한 순간에 AOP가 구출되어 애플리케이션을 구축하는 데 필요한 추가 도구를 제공합니다. 그리고 추가 도구는 개발 유연성 향상을 의미하며, 덕분에 특정 문제를 해결하기 위한 더 많은 옵션이 제공됩니다.

AOP의 적용

관점 지향 프로그래밍은 다양한 방식으로 여러 번 반복되는 코드일 수 있고 별도의 모듈로 완전히 구조화될 수 없는 크로스커팅 문제를 해결하도록 설계되었습니다. 따라서 AOP를 사용하면 이를 기본 코드 외부에 두고 수직으로 정의할 수 있습니다. 한 가지 예는 애플리케이션에 보안 정책을 적용하는 것입니다. 일반적으로 보안은 애플리케이션의 여러 요소에 적용됩니다. 또한, 애플리케이션 보안 정책은 애플리케이션의 모든 기존 부분과 새로운 부분에 동일하게 적용되어야 합니다. 동시에 사용되는 보안 정책도 자체적으로 발전할 수 있습니다. 이것이 바로 AOP 의 사용이 유용할 수 있는 곳입니다 . 또 다른 예는 로깅 입니다 . 로깅을 수동으로 삽입하는 것에 비해 AOP 로깅 접근 방식을 사용하면 다음과 같은 몇 가지 이점이 있습니다.
  1. 로깅 코드는 구현 및 제거가 쉽습니다. 일부 측면의 몇 가지 구성을 추가하거나 제거하기만 하면 됩니다.
  2. 로깅을 위한 모든 소스코드는 한 곳에 저장되므로, 모든 사용처를 수동으로 찾을 필요가 없습니다.
  3. 로깅용 코드는 이미 작성된 메소드와 클래스 또는 새로운 기능 등 어디에나 추가할 수 있습니다. 이렇게 하면 개발자 오류 수가 줄어듭니다.
    또한 디자인 구성에서 측면을 제거하면 모든 추적 코드가 제거되고 누락된 것이 없음을 절대적으로 확신할 수 있습니다.
  4. Aspect는 계속해서 재사용하고 개선할 수 있는 독립 실행형 코드입니다.
AOP란 무엇입니까?  관점지향 프로그래밍의 기초 - 3AOP는 재사용이 가능하도록 예외 처리, 캐싱 및 일부 기능 제거에도 사용됩니다.

AOP의 기본 개념

주제 분석을 더 진행하기 위해 먼저 AOP의 주요 개념에 대해 알아 보겠습니다. 조언은 연결 지점에서 호출되는 추가 논리, 코드입니다. 연결 지점 전, 후 또는 대신에 조언을 수행할 수 있습니다(자세한 내용은 아래 참조). 가능한 조언 유형 :
  1. 이전(Before) - 이 유형의 조언은 대상 메서드(연결 지점) 실행 전에 시작됩니다. 측면을 클래스로 사용할 때 @Before 주석을 사용하여 조언 유형이 앞에 오는 것으로 표시합니다. Aspect를 .aj 파일로 사용하는 경우 before() 메서드 가 됩니다 .
  2. 이후(After) - 메서드 실행이 완료된 후 실행되는 조언 - 일반적인 경우와 예외가 발생하는 경우 모두 연결 지점입니다.
    측면을 클래스로 사용할 때 @After 주석을 사용하여 이것이 뒤에 오는 팁임을 나타낼 수 있습니다.
    Aspect를 .aj 파일로 사용하는 경우 이는 after() 메서드 가 됩니다 .
  3. 반환 후 - 이 팁은 대상 메서드가 오류 없이 정상적으로 작동하는 경우에만 실행됩니다.
    측면이 클래스로 표시되면 @AfterReturning 주석을 사용하여 성공적인 완료 시 조언이 실행되는 것으로 표시할 수 있습니다.
    Aspect를 .aj 파일로 사용하는 경우 이는 (Object obj) 를 반환하는 after() 메서드가 됩니다 .
  4. 던진 후 - 이 유형의 조언은 메서드, 즉 연결 지점이 예외를 throw하는 경우를 위한 것입니다. 실패한 실행을 일부 처리하는 데 이 조언을 사용할 수 있습니다(예: 전체 트랜잭션 롤백 또는 필요한 추적 수준으로 로깅).
    관점 클래스의 경우 @AfterThrowing 주석은 예외가 발생한 후에 이 조언이 사용된다는 것을 나타내는 데 사용됩니다. .aj
    파일 형식의 측면을 사용할 때 이는 after() throw (Exception e) 메소드가 됩니다 .
  5. Around는 아마도 메소드, 즉 연결점을 둘러싸는 가장 중요한 유형의 조언 중 하나일 것입니다. 예를 들어 주어진 연결점 메소드를 실행할지 여부를 선택할 수 있습니다.
    조인 포인트 메서드 실행 전후에 실행되는 조언 코드를 작성할 수 있습니다. 어라운드 어드바이스의
    책임에는 조인 포인트 메소드 호출과 메소드가 무언가를 반환하는 경우 값을 반환하는 것이 포함됩니다. 즉, 이 팁에서는 대상 메서드를 호출하지 않고 간단히 대상 메서드의 작업을 모방하고 결과로 자신만의 것을 반환할 수 있습니다. 클래스 형태의 측면에 대해서는 @Around 주석을 사용하여 연결 지점을 래핑하는 팁을 만듭니다. .aj 파일 로 측면을 사용하는 경우 이는 around() 메서드 가 됩니다 .
조인 포인트 - 조언이 적용되어야 하는 실행 프로그램(메소드 호출, 객체 생성, 변수 액세스)의 지점입니다. 즉, 이것은 코드 도입 위치(팁 적용 위치)를 찾는 데 도움이 되는 일종의 정규식입니다. 포인트컷은 연결점들의 집합이다 . 컷은 주어진 연결점이 주어진 팁에 맞는지 여부를 결정합니다. Aspect 는 엔드투엔드 기능을 구현하는 모듈 또는 클래스입니다. 관점은 일부 슬라이스 에 의해 정의된 조인 포인트조언을 적용하여 나머지 코드의 동작을 수정합니다 . 즉, 팁과 연결점의 조합입니다. 소개 - 외부 코드에 측면 기능을 추가하기 위해 클래스 구조 변경 및/또는 상속 계층 구조 변경. Target은 조언이 적용될 개체입니다. Weaving은 권장되는 프록시 개체를 생성하기 위해 다른 개체와 측면을 연결하는 프로세스입니다. 이는 컴파일 타임, 로드 타임 또는 런타임에 수행될 수 있습니다. 직조에는 세 가지 유형이 있습니다.
  • 컴파일 타임 위빙 - 관점의 소스 코드와 관점을 사용하는 코드가 있는 경우 AspectJ 컴파일러를 사용하여 소스 코드와 관점을 직접 컴파일할 수 있습니다.
  • 컴파일 후 위빙 (바이너리 위빙) - 소스 코드 변환을 사용하여 코드에 측면을 위빙할 수 없거나 사용하고 싶지 않은 경우 이미 컴파일된 클래스나 jar를 가져와 측면을 삽입할 수 있습니다.
  • 로드 시간 위빙은 클래스 로더가 클래스 파일을 로드하고 JVM용 클래스를 정의할 때까지 지연되는 단순한 바이너리 위빙입니다.
    이를 지원하려면 하나 이상의 "weave 클래스 로더"가 필요합니다. 런타임에 의해 명시적으로 제공되거나 "직조 에이전트"에 의해 활성화됩니다.
AspectJ 는 교차 문제를 해결하는 기능을 구현하는 AOP 패러다임 의 특정 구현입니다 . 문서는 여기에서 찾을 수 있습니다 .

자바의 예

다음으로 AOP를 더 잘 이해하기 위해 Hello World 수준의 작은 예를 살펴보겠습니다. 우리 예제에서는 컴파일 타임 위빙을AOP란 무엇입니까?  관점지향 프로그래밍의 기초 - 4 사용할 것이라는 점을 바로 알아두겠습니다 . 먼저 pom.xml 에 다음 종속성을 추가해야 합니다 .
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
일반적으로 Ajs 컴파일러를 사용하여 관점을 사용합니다 . IntelliJ IDEA에는 기본적으로 이 기능이 없으므로 이를 애플리케이션 컴파일러로 선택할 때 AspectJ 배포 경로를 지정해야 합니다 . 이 페이지에서 Ajs를 컴파일러로 선택하는 방법에 대해 자세히 알아볼 수 있습니다. 이것이 첫 번째 방법이었고, 두 번째(제가 사용한) 방법은 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

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

  public static void main(String[] args) {
  printName("Толя");
  printName("Вова");
  printName("Sasha");
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
복잡한 것은 없습니다. 이름을 전달하고 콘솔에 표시했습니다. 지금 실행하면 콘솔에 다음이 표시됩니다.
톨리야 보바 사샤
자, 이제 AOP의 힘을 활용해야 할 때입니다. 이제 파일 측면을 생성해야 합니다 . 두 가지 유형이 있습니다. 첫 번째는 확장자가 .aj 인 파일이고, 두 번째는 주석을 사용하여 AOP 기능을 구현하는 일반 클래스입니다 . 먼저 확장자가 .aj 인 파일을 살펴보겠습니다 .
public aspect GreetingAspect {

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

  before() : greeting() {
     System.out.print("Привет ");
  }
}
이 파일은 클래스와 다소 유사합니다. 여기서 무슨 일이 일어나고 있는지 알아봅시다. pointcut - 연결 지점의 컷 또는 집합입니다. Greeting() — 이 슬라이스의 이름입니다. : 실행 - 실행할 때 * - 모두, 호출 - Main.printName(..) - 이 메소드. 그 다음에는 특정 조언인 before() 가 옵니다 . 이는 대상 메소드가 호출되기 전에 실행됩니다. Greeting() - 이 조언이 반응하는 슬라이스, 아래에서는 Java로 작성된 메소드 자체의 본문을 볼 수 있습니다. 우리가 이해하는 언어. 이 측면이 있는 상태에서 main을 실행하면 콘솔에 다음과 같은 출력이 표시됩니다.
안녕 톨야 안녕 보바 안녕 사샤
printName 메소드에 대한 모든 호출이 측면에 의해 수정되었음을 알 수 있습니다 . 이제 애스펙트가 어노테이션이 있는 Java 클래스로 어떻게 보이는지 살펴보겠습니다.
@Aspect
public class GreetingAspect{

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

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Привет ");
  }
}
.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) {
  makeSomeOperation("Толя");
  }

  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
@Around 주석을 사용하여 "의사 트랜잭션"과 같은 작업을 수행해 보겠습니다.
@Aspect
public class TransactionAspect{

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

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Открытие транзакции...");
     try {
        joinPoint.proceed();
        System.out.println("Закрытие транзакции....");
     }
     catch (Throwable throwable) {
        System.out.println("Операция не удалась, откат транзакции...");
     }
  }
  }
ProceedingJoinPoint 객체 의 Proceed 메소드를 사용하여 래퍼의 메소드를 호출하여 보드에서의 위치를 ​​결정하고 그에 따라 JoinPoint.proceed() 위 메소드의 코드를 결정합니다. - 이것이 Before 이고 아래가 - After 입니다. main을 실행하면 콘솔에 표시됩니다.
트랜잭션 열기... 클라이언트에 대한 일부 작업 수행 - Tolya 트랜잭션 닫기....
메소드에 예외 발생을 추가하면(갑자기 작업이 실패함):
public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  throw new Exception();
}
그런 다음 콘솔에 출력이 표시됩니다.
트랜잭션 열기... 클라이언트에 대한 일부 작업 수행 중 - Tolya 작업이 실패하여 트랜잭션이 롤백되었습니다...
그것은 실패의 의사 처리로 밝혀졌습니다.

예 3

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

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<некоторое meaning>");
     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("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Успешно выполнен метод - %s, класса- %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
여기서 무슨 일이 일어나고 있는 걸까요? @Pointcut("execution(* *(..))") - 모든 메서드에 대한 모든 호출에 연결됩니다. @AfterReturning(value = "methodExecuting()", return = "returningValue") - 대상 메서드가 성공적으로 완료된 후에 실행될 조언입니다. 여기에는 두 가지 경우가 있습니다.
  1. 메소드에 반환 값이 있는 경우 if (returningValue != null) {
  2. 반환 값이 없는 경우 else {
@AfterThrowing(value = "methodExecuting()", Throwing = "Exception") - 오류가 발생한 경우, 즉 메서드에서 예외가 발생한 경우 트리거되는 조언입니다. 따라서 main 을 실행하면 콘솔에 일종의 로깅이 표시됩니다.
Main 클래스의 setValue 메소드가 성공적으로 실행되었습니다. Main 클래스의 getValue 메소드가 성공적으로 실행되었으며, 실행 결과는 <some value> Main 클래스의 checkValue 메소드입니다. 예외로 인해 비정상적으로 종료되었습니다 - java.lang.Exception 메소드 - main, class-Main, 예외로 인해 충돌함 - java.lang.Exception
글쎄, 우리는 예외를 처리하지 않았으므로 스택 추적도 얻을 것입니다. 예외 및 해당 처리에 대한 내용은 Java의 예외예외 및 해당 처리AOP란 무엇입니까?  관점지향 프로그래밍의 기초 - 5 문서에서 읽을 수 있습니다 . 오늘은 그게 전부입니다. 오늘 우리는 AOP 에 대해 알게 되었는데 , 이 짐승이 그림처럼 무섭지 않다는 것을 알 수 있었습니다. 모두들 안녕!AOP란 무엇입니까?  관점지향 프로그래밍의 기초 - 6
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION