Spring-17 依赖项空指针异常原因

67 阅读2分钟

spring-logo-0.jpg

Spring-17 依赖项空指针异常原因

Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。

使用 spring 的时候有时会遇到明明依赖项都注入进去了,但是在方法调用的时候却会报空指针异常,本章会说明这种现象产生的原因是什么。

直接说结论吧,少一点废话多一点有价值的内容。产生 NPE 的原因是因为调用了代理对象的 private 方法,而 private 方法中使用带了依赖项,这时候就会报空指针异常。

原因

image.png

在 spring 的 AOP 中由代理对象和目标对象共同组成,代理对象和目标对象是两个完全不同的对象. cglib 代理方式是代理类继承了目标类. 最终得到的代理类中的依赖项属性是 null , 因为代理类是目标类的子类, 也继承了目标类中的属性,但是却不会给这些属性再赋值。

debug 可以看到 targetSource 中的目标对象中 bean2 属性是有值的. 代理对象实列中 bean2 是 null 。

image.png

再强调一遍产生 NPE 的原因, 调用了代理对象中的 private 方法,在 private 方法使用了依赖注入项。 这种情况较多的出现在 controller 层,因为 SpringMVC 是通过反射调用目标方法的,所以即使申明为 private 也无所谓。但是如果 controller 是代理对象,这样就会出现 NPE

SpringMVC 对目标控制器方法的反射调用:

image.png

测试代码

public class NPEExp {

    public static void main(String[] args) throws Exception {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        Logger logger = loggerContext.getLogger(AnnotationAwareAspectJAutoProxyCreator.class);
        logger.setLevel(Level.TRACE);

        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

        AnnotationAwareAspectJAutoProxyCreator proxyCreator = new AnnotationAwareAspectJAutoProxyCreator();
        proxyCreator.setBeanFactory(factory);
        factory.addBeanPostProcessor(proxyCreator);

        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(factory);
        factory.addBeanPostProcessor(processor);

        factory.registerBeanDefinition("myAspect" , BeanDefinitionBuilder
                .genericBeanDefinition(MyAspect.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean1" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean1.class)
                .getBeanDefinition());

        factory.registerBeanDefinition("bean2" , BeanDefinitionBuilder
                .genericBeanDefinition(Bean2.class)
                .getBeanDefinition());

        Bean1 bean1 = factory.getBean(Bean1.class);
        bean1.foo();
        bean1.fooPrivate();
        Method method = Bean1.class.getDeclaredMethod("fooPrivate");
        method.setAccessible(true);
        method.invoke(bean1);
    }


    @Aspect
    static class MyAspect {

        @Before("execution(* foo())")
        public void before() {
            System.out.println("@Aspect @Before ------------------------------");
        }

    }


    static class Bean1 {

        @Autowired
        private Bean2 bean2;

        public Bean1() {
            System.out.println("======================== bean1 实例化 ");
        }

        public void foo() {
            System.out.println("======================== Bean1 foo ");
            bean2.foo();
        }

        private void fooPrivate() {
            System.out.println("======================== Bean1 fooPrivate ");
            bean2.foo();
        }
    }

    static class Bean2 {


        public Bean2() {
            System.out.println("======================== Bean2 实例化 ");
        }

        public void foo() {
            System.out.println("======================== Bean2 foo ");
        }
    }
}

DevX 会持续分享 Java 技术干货,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。

DevX 不止于技术