복붙노트

[SPRING] Spring AOP CGLIB 프록시의 필드가 null입니다.

SPRING

Spring AOP CGLIB 프록시의 필드가 null입니다.

vlcj 구성 요소를 사용하면 사용자 정의 구성 요소가 AOP 프록시 객체 null의 결과로 나타납니다.

public class MediaList {
    private libvlc_media_list_t mediaListInstance;
    public MediaList(LibVlc libvlc, libvlc_instance_t instance, libvlc_media_list_t mediaListInstance) {
        this.libvlc = libvlc;
        this.instance = instance;
        createInstance(mediaListInstance);
    }
    private void createInstance(libvlc_media_list_t mediaListInstance) {
        logger.debug("createInstance()");
        if(mediaListInstance == null) {
            mediaListInstance = libvlc.libvlc_media_list_new(instance);
        }
        else {
            libvlc.libvlc_media_list_retain(mediaListInstance);
        }

        this.mediaListInstance = mediaListInstance; // <- assignment
        logger.debug("mediaListInstance={}", mediaListInstance);

        mediaListEventManager = libvlc.libvlc_media_list_event_manager(mediaListInstance);
        logger.debug("mediaListEventManager={}", mediaListEventManager);

        registerEventListener();
    }
    public final libvlc_media_list_t mediaListInstance() {
        return mediaListInstance; // <- proxy object return null, if use aop
    }
}
public class TestMediaList extends MediaList {

    public TestMediaList(LibVlc libvlc, libvlc_instance_t instance) {
        super(libvlc, instance);
    }

    public void xTest(String test){
        System.out.println(test);
    }
}
@Configuration
public class PlayerBeanConfig {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Resource
    public TestMediaList testMediaList(LibVlc libvlc, libvlc_instance_t instance) {
        return new TestMediaList(libvlc, instance);
    }
}
@Aspect
public class MediaListAspect {
    @Pointcut("execution(* TestMediaList.xTest(..))")
    private void anyMethod() {
    }

    @Around("anyMethod()")
    public Object lockAndUnlock(ProceedingJoinPoint joinPoint) throws Throwable {
        Object object = joinPoint.proceed();
        return object;
    }
}
public static void main(String[] args) {
    boolean b = new NativeDiscovery().discover();

    if (b) {
        springContext = new AnnotationConfigApplicationContext(PlayerBeanConfig.class);

        String[] kkk = new String[]{};
        TestMediaList list = springContext.
                getBean(TestMediaList.class, LibVlc.INSTANCE, LibVlc.INSTANCE.libvlc_new(kkk.length, kkk));

        System.out.println(list.mediaListInstance()); // <- proxy object return null
    } else {
        logger.error("Cannot find vlc lib, exit application");
    }
}

TestMediaList 빌드가 완료되면 단일 단계 추적을 시도합니다. MediaListInstance () 메서드가 정상 값으로 돌아가지만 스프링이 프록시 객체로 반환되면 null이 반환됩니다. 동시에 AOP를 사용하지 않으면 값을 올바르게 반환하려고합니다. 따라서 AOP 동적 프록시의 기본 문제를 결정하지만 그 이유는 모르지만 이전에는 그러한 상황이 발생하지 않았습니다.

패키지의 모든 클래스 : vod.demo

public class TargetClass {
    private String returnValue;

    public TargetClass() {
        this.returnValue = "Hello World";
    }

    public final String test() {
        System.out.println("TargetClass.test();");
        return returnValue;
    }
}
@Aspect
public class AspectClass {
    @Pointcut("execution(* vod.demo.TargetClass.*(..))")
    private void targetMethod() {
    }

    @Around("targetMethod()")
    public Object aroundTarget(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AspectClass.aroundTarget();");
        return joinPoint.proceed();
    }
}
@Configuration
@EnableAspectJAutoProxy
@Import(AspectClass.class)
public class SpringConfig {
    @Bean
    public TargetClass target() {
        return new TargetClass();
    }
}
public class Client {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        TargetClass target = context.getBean(TargetClass.class);
        System.out.println("Client invoke:" + target.test()); // <- output null
    }
}

해결법

  1. ==============================

    1.이것은 예기치 않은 예기치 못한 행동의 조합입니다. 먼저, Spring은 CGLIB를 사용하여 AOP를 위해 빈을 프록시 처리한다. CGLIB 프록시는 모든 메소드 호출을 클래스의 실제 인스턴스에 위임하는 클래스의 동적 하위 유형의 인스턴스입니다. 그러나 프록시가 하위 유형 인 경우에도 해당 필드는 초기화되지 않습니다 (즉, TargetClass 슈퍼 생성자가 호출되지 않음). 더 긴 설명은 여기에서 찾을 수 있습니다.

    이것은 예기치 않은 예기치 못한 행동의 조합입니다. 먼저, Spring은 CGLIB를 사용하여 AOP를 위해 빈을 프록시 처리한다. CGLIB 프록시는 모든 메소드 호출을 클래스의 실제 인스턴스에 위임하는 클래스의 동적 하위 유형의 인스턴스입니다. 그러나 프록시가 하위 유형 인 경우에도 해당 필드는 초기화되지 않습니다 (즉, TargetClass 슈퍼 생성자가 호출되지 않음). 더 긴 설명은 여기에서 찾을 수 있습니다.

    또한 메소드

    public final libvlc_media_list_t mediaListInstance() {
        return mediaListInstance; // <- proxy object return null, if use aop
    }
    

    또는

    public final String test() {
        System.out.println("TargetClass.test();");
        return returnValue;
    }
    

    최종입니다. 따라서 CGLIB는 실제 인스턴스에 위임하기 위해 CGLIB를 재정의 할 수 없습니다. 이것은 스프링 로그에서 암시 될 것입니다. 예를 들어,

    22:35:31.773 [main] INFO  o.s.aop.framework.CglibAopProxy - Unable to proxy method [public final java.lang.String com.example.root.TargetClass.test()] because it is final: All calls to this method via a proxy will NOT be routed to the target instance.
    

    위의 모든 것을 조합하면 필드가 null이고 프록시가 실제 인스턴스의 메서드에 위임 할 수없는 프록시 인스턴스가 생성됩니다. 따라서 코드가 실제로 호출됩니다.

    public final String test() {
        System.out.println("TargetClass.test();");
        return returnValue;
    }
    

    returnValue 필드가 null 인 인스턴스의 경우.

    방법을 변경할 수 있다면 최종 수정자를 제거하십시오. 할 수 없다면 디자인을 재고해야합니다.

  2. from https://stackoverflow.com/questions/34563058/spring-aop-cglib-proxys-field-is-null by cc-by-sa and MIT license