> Java > java지도 시간 > Spring AOP의 내부 작동 방식 공개

Spring AOP의 내부 작동 방식 공개

王林
풀어 주다: 2024-09-07 06:34:36
원래의
647명이 탐색했습니다.

Unveiling the Inner Workings of Spring AOP

이번 게시물에서는 Spring의 AOP(Aspect-Oriented 프로그래밍)의 내부 메커니즘에 대해 설명하겠습니다. AOP가 종종 "마법"의 형태로 간주되는 로깅과 같은 기능을 달성하는 방법을 이해하는 데 중점을 둘 것입니다. 핵심 Java 구현을 살펴봄으로써 우리는 이것이 진정한 마술이 아닌 Java의 반사, 프록시 패턴 및 주석에 관한 모든 것을 알게 될 것입니다.

전제 조건

  • Java 핵심 프록시 API
  • 리플렉션 API
  • 주석 API

이들은 모두 java.lang.reflect,java.lang.annotation 및 javassist.util.proxy 패키지의 일부입니다.

핵심 메커니즘

Spring AOP의 핵심에는 프록시 객체, 메소드 인터셉터 및 리플렉션 개념이 있습니다. 이 패턴의 핵심 플레이어는 MethodHandler(또는 호출 핸들러)입니다. 이 핸들러는 메소드 호출을 가로채서 프록시 객체의 동작을 제어합니다. 프록시에서 메서드가 호출되면 핸들러를 통해 전달되며, 여기서 주석은 리플렉션을 통해 검사될 수 있습니다. 적용된 주석을 기반으로 예외 전, 후 또는 발생 시 필요한 로직(예: 로깅)을 실행할 수 있습니다.

무너뜨리기

  1. 프록시 개체: 실제 비즈니스 개체를 대신하는 동적으로 생성된 개체로, 메서드 핸들러를 통해 메서드 호출을 라우팅합니다.
  2. 호출 핸들러: 여기에서 가로채기 마법이 발생합니다. 리플렉션을 사용하면 핸들러는 대상 메서드에 있는 주석을 검사하고 이에 따라 동작을 변경할 수 있습니다.
  3. 사용자 정의 주석: 로깅, 보안 검사 또는 트랜잭션 관리와 같은 추가 기능을 트리거하는 마커 역할을 하는 사용자 정의 주석을 정의할 수 있습니다.

예: 특정 메소드 실행 전후에 로깅을 추가한다고 가정해 보겠습니다. 모든 곳에서 로깅을 하드 코딩하는 대신 @BeforeMethod 및 @AfterMethod를 사용하여 메소드에 주석을 달 수 있습니다. 우리 핸들러는 이 주석에 대한 메소드를 검사하고 적절한 로깅 로직을 동적으로 추가합니다.

다음은 우리 예에서 컨트롤러와 서비스가 어떻게 보이는지 클래스입니다.

WorkerController.java

package edu.pk.poc.aop.controller;

import edu.pk.poc.aop.annotation.AfterMethod;
import edu.pk.poc.aop.annotation.All;
import edu.pk.poc.aop.annotation.BeforeMethod;
import edu.pk.poc.aop.helper.ProxyFactory;
import edu.pk.poc.aop.service.Worker;
import edu.pk.poc.aop.service.WorkerService;
import edu.pk.poc.aop.service.WorkerServiceImpl;

public class WorkerController {
    WorkerService workerService = ProxyFactory.createProxy(WorkerServiceImpl.class);
    /**
     * This Method 1s annotated with @BeforeMethod and @AfterMethod, So the log statements
     * will be generated before and after method call.
     */
    @BeforeMethod
    @AfterMethod
    public void engageFullTimeWorker() throws Exception {
        Worker fullTimeWorker = new Worker();
        fullTimeWorker.setName("FullTime-Worker");
        fullTimeWorker.setPartTime(false);
        fullTimeWorker.setDuration(9);
        workerService.doWork(fullTimeWorker);
    }
    /**
     * This Method is annotated with @All, So the log statements will be generated before and after method call
     * along with exception if raised.
     */
    @All
    public void engagePartTimeWorker() throws Exception {
        Worker partTimeWorker = new Worker();
        partTimeWorker.setName("PartTime-Worker");
        partTimeWorker.setPartTime(true);
        partTimeWorker.setDuration(4);
        workerService.doWork(partTimeWorker);
    }
}
로그인 후 복사

WorkerServiceImpl.java

package edu.pk.poc.aop.service;

import edu.pk.poc.aop.annotation.AfterMethod;

public class WorkerServiceImpl implements WorkerService {
    /**
     * Here this method is annotated with only @AfterMethod, So only log statement
     * will be generated after method call
     */
    @AfterMethod
    @Override
    public void doWork(Worker worker) throws Exception {
        if (worker.isPartTime()) {
            throw new Exception("Part time workers are not permitted to work.");
        }
        System.out.print("A full time worker is working for " + worker.getDuration() + " hours :: ");
        for (int i = 1; i < worker.getDuration(); i++) {
            System.out.print("* ");
        }
        System.out.println();
    }
}
로그인 후 복사

Main.java 테스트 클래스

package edu.pk.poc.aop.test;

import edu.pk.poc.aop.controller.WorkerController;
import edu.pk.poc.aop.helper.ProxyFactory;
import edu.pk.util.Logger;

public class Main {
    public static void main(String[] args) {
        WorkerController controller = ProxyFactory.createProxy(WorkerController.class);
        Logger logger = new Logger();
        try {
            System.out.println("Testing @BeforeMethod and @AfterMethod");
            System.out.println("-----------------------------------------");
            controller.engageFullTimeWorker();
            System.out.println("Testing @All");
            System.out.println("-----------------------------------------");
            controller.engagePartTimeWorker();
        } catch (Exception e) {
            logger.error("Exception caught in Main class");
        }
    }
}
로그인 후 복사

출력

Testing @BeforeMethod and @AfterMethod
-----------------------------------------
>>> Entering into edu.pk.poc.aop.controller.WorkerController.engageFullTimeWorker()
A full time worker is working for 9 hours :: * * * * * * * * 
>>> Exiting from edu.pk.poc.aop.service.WorkerServiceImpl.doWork()
>>> Exiting from edu.pk.poc.aop.controller.WorkerController.engageFullTimeWorker()
Testing @All
-----------------------------------------
>>> Entering into edu.pk.poc.aop.controller.WorkerController.engagePartTimeWorker()
>>> Exception in edu.pk.poc.aop.controller.WorkerController.engagePartTimeWorker()
Exception caught in Main class
로그인 후 복사

작동 방식

프록시 객체에서 메서드가 호출되면 리플렉션을 사용하여 대상 메서드의 모든 주석을 검사하는 핸들러가 호출을 가로챕니다. 이러한 주석을 기반으로 핸들러는 메소드 시작/종료를 기록할지, 예외를 기록할지, 아니면 로깅을 모두 건너뛸지를 결정합니다.

시각화하는 방법은 다음과 같습니다.

  • 실행 전: 로그 메소드를 입력합니다.
  • 실행 후: 메소드 종료 또는 성공을 기록합니다.
  • 모두: 메소드 항목, 메소드 항목 및 예외 발생 시 로그를 기록합니다. 이러한 동적 동작은 Spring AOP가 일부 마술을 사용하기보다는 핵심 Java API를 활용한다는 것을 보여줍니다.

주석 정의

package edu.pk.poc.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterMethod {

}
로그인 후 복사
package edu.pk.poc.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BeforeMethod {

}
로그인 후 복사
package edu.pk.poc.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface All {

}
로그인 후 복사

프록시 팩토리 정의

package edu.pk.poc.aop.helper;

/**
 * The {@code ProxyFactory} class is responsible for creating proxy objects using the Javassist library.
 * It allows for dynamic generation of proxies for classes or interfaces, with support for method interception.
 */
public class ProxyFactory {

    /**
     * A Javassist ProxyFactory instance used to generate proxy classes.
     */
    private static final javassist.util.proxy.ProxyFactory factory = new javassist.util.proxy.ProxyFactory();

    /**
     * Creates a proxy object for the given class or interface.
     * If the class is an interface, the proxy implements the interface.
     * If it's a concrete class, the proxy extends the class.
     *
     * @param <T>   the type of the class or interface for which the proxy is to be created
     * @param klass the {@code Class} object representing the class or interface to proxy
     * @return a proxy instance of the specified class or interface, or {@code null} if proxy creation fails
     */
    public static <T> T createProxy(Class<T> klass) {
        if (klass.isInterface())
            factory.setInterfaces(new Class[]{klass});
        else
            factory.setSuperclass(klass);
        try {
            return (T) factory.create(new Class<?>[0], new Object[0], new AOPLoggingMethodHandler());
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        return null;
    }
}
로그인 후 복사

MethodHandler 정의

package edu.pk.poc.aop.helper;

import edu.pk.poc.aop.annotation.AfterMethod;
import edu.pk.poc.aop.annotation.All;
import edu.pk.poc.aop.annotation.BeforeMethod;
import edu.pk.poc.aop.annotation.OnException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import edu.pk.util.Logger;
import javassist.util.proxy.MethodHandler;

public class AOPLoggingMethodHandler implements MethodHandler {

    private static final Logger logger = new Logger();

    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        if (proceed != null) { // Concrete Method
            Object result = null;
            String className = resolveClassName(self);
            try {
                if (isAnnotationPresent(thisMethod, BeforeMethod.class) || isAnnotationPresent(thisMethod, All.class)) {
                    logger.info(">>> Entering into " + className + "." + thisMethod.getName() + "()");
                }
                result = proceed.invoke(self, args);
                if (isAnnotationPresent(thisMethod, AfterMethod.class) || isAnnotationPresent(thisMethod, All.class)) {
                    logger.info(">>> Exiting from " + className + "." + thisMethod.getName() + "()");
                }
            } catch (Throwable t) {
                if (isAnnotationPresent(thisMethod, OnException.class) || isAnnotationPresent(thisMethod, All.class)) {
                    logger.error(">>> Exception in " + className + "." + thisMethod.getName() + "()");
                }
                throw t;
            }
            return result;
        }
        throw new RuntimeException("Method is Abstract");
    }

    private boolean isAnnotationPresent(Method method, Class klass) {
        Annotation[] declaredAnnotationsByType = method.getAnnotationsByType(klass);
        return declaredAnnotationsByType != null && declaredAnnotationsByType.length > 0;
    }

    private String resolveClassName(Object self) {
        String className = self.getClass().getName();
        if (className.contains("_$$")) {
            className = className.substring(0, className.indexOf("_$$"));
        }
        return className;
    }
}
로그인 후 복사

결론

Spring AOP는 다양한 문제를 해결하는 강력한 도구이지만 혁신적인 작업을 수행하지는 않습니다. 이는 언어 자체에서 사용할 수 있는 리플렉션 및 프록시와 같은 핵심 Java 개념을 기반으로 구축되었습니다. 이를 이해하면 Spring이 개발자 편의를 위해 이러한 하위 수준 메커니즘을 어떻게 단순화하는지 더 잘 이해할 수 있습니다.

위 내용은 Spring AOP의 내부 작동 방식 공개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿