使用自定义 Autowired 时出现 NullPointerException

Posted

技术标签:

【中文标题】使用自定义 Autowired 时出现 NullPointerException【英文标题】:NullPointerException when using customized Autowired 【发布时间】:2022-01-21 07:29:05 【问题描述】:

我使用 BeanPostProcessor (InjectBeanPostProcessor.java) 自定义了一个类似 @Autowired 的 Annotation @CustomizedAutowired,但是在使用 AOP 时我得到了 NullPointerException。

    为什么使用 AOP 时为 null? 为什么在使用 AOP 时 DemoController 似乎被代理了两次? 我应该怎么做才能让@CustomizedAutowired 像@Autowired 一样工作?
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
public @interface CustomizedAutowired 
@RestController
@RequestMapping("/hello")
public class DemoController 

    @CustomizedAutowired
    private InjectBean injectBean;

    @GetMapping("/world")
    public LocalDateTime hello() 
        injectBean.hello(); // injectBean is null
        return LocalDateTime.now();
    

@Aspect
@Component
public class AopDemo 

    @Pointcut("execution(public java.time.LocalDateTime *(..))")
    public void pointcut() 

    @AfterReturning(pointcut = "pointcut()")
    public void round() 
        System.out.println("after returning");
    

@Component
public class InjectBeanPostProcessor implements BeanPostProcessor 

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
        Class<?> targetClass = bean.getClass();
        while (targetClass != null) 
            Field[] fields = targetClass.getDeclaredFields();
            for (Field field : fields) 
                if (field.isAnnotationPresent(CustomizedAutowired.class)) 
                    field.setAccessible(true);
                    try 
                        field.set(bean, new InjectBean());
                     catch (IllegalAccessException e) 
                        e.printStackTrace();
                    
                
            
            targetClass = targetClass.getSuperclass();
        
        return bean;
    

@SpringBootApplication
public class DemoApplication implements CommandLineRunner 

    public static void main(String[] args) 
        SpringApplication.run(DemoApplication.class, args);
    

    @CustomizedAutowired
    private InjectBean injectBean;

    @Override
    public void run(String... args) throws Exception 
        System.out.println("instance -> " + this);
        injectBean.hello(); // works fine here
    

结果如下:

【问题讨论】:

这个问题仍然被列为未回答,尽管它有两个答案。我希望你能接受并支持我的回答。即使您的问题已经解决,也请不要让问题悬而未决。谢谢。 【参考方案1】:

从google搜索后发现发生NPE的原因是我们得到了错误的targetObject,BeanPostProcessor.postProcessAfterInitialization(Object bean, String beanName)给了我们一个被代理的bean,如果我们使用AOP会被代理两次,把postProcessAfterInitialization替换为postProcessBeforeInitialization 可以解决这个问题,或者其他可以在被AOP代理之前进行注入操作的解决方案。

【讨论】:

【参考方案2】:

您不能在代理 bean 上执行field.set(bean, new InjectBean()),因为它不继承任何私有字段。您需要解开代理并在原始对象上设置字段。

我不会评论使用所有丑陋的反射来实现您的自定义注入想法的想法,只是帮助您使其工作。您可以使用 AopTestUtils.getTargetObject(bean) 而不是您的 while 循环来获取原始对象,然后轻松获取其类。

这个怎么样?

package de.scrum_master.spring.q70408968;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.test.util.AopTestUtils;

import java.lang.reflect.Field;

@Component
public class InjectBeanPostProcessor implements BeanPostProcessor 

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
    // Unwrap bean, in case it is a proxy
    Object targetObject = AopTestUtils.getTargetObject(bean);
    Class<?> targetClass = targetObject.getClass();
    Field[] fields = targetClass.getDeclaredFields();
    for (Field field : fields) 
      if (field.isAnnotationPresent(CustomizedAutowired.class)) 
        field.setAccessible(true);
        try 
          field.set(targetObject, new InjectBean());
        
        catch (IllegalAccessException e) 
          e.printStackTrace();
        
      
    
    return bean;
  

这将在有和没有 AOP 代理的情况下工作。请注意我是如何将字段注入targetObject,但返回原始bean 实例(即AOP 案例中的代理)。

然后,去掉应用类中的注解成员,因为应用不是普通的Spring组件。

package de.scrum_master.spring.q70408968;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class DemoApplication 
  public static void main(String[] args) 
    try (ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args)) 
      context.getBean(DemoController.class).hello();
    
  

现在应用程序运行良好。

o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
d.s.spring.q70408968.DemoApplication     : Started DemoApplication in 7.457 seconds (JVM running for 10.318)
Hello from InjectBean
after returning

或者您可能更喜欢 Java 流方法,但这只是装饰:

package de.scrum_master.spring.q70408968;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.test.util.AopTestUtils;

import java.util.Arrays;

@Component
public class InjectBeanPostProcessor implements BeanPostProcessor 
  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
    // Unwrap bean, in case it is a proxy
    Object targetObject = AopTestUtils.getTargetObject(bean);
    Arrays.stream(targetObject.getClass().getDeclaredFields())
      .filter(field -> field.isAnnotationPresent(CustomizedAutowired.class))
      .forEach(field -> 
        field.setAccessible(true);
        try 
          field.set(targetObject, new InjectBean());
        
        catch (IllegalAccessException e)  e.printStackTrace(); 
      );
    return bean;
  

【讨论】:

使用 AopTestUtils。 getTargetObject(bean) 是个好主意,对我来说效果很好,谢谢。 通过检查instanceof Advised,然后强制转换,然后调用getTargetSource().getTarget()getTargetSource().getTargetClass(),自行解包代理也不难。但我想保持简单,看看对测试工具的依赖是否适合你。 顺便说一句,感谢我的最佳方式是接受并支持我的回答。您自己的答案使用了您没有要求的不同方法,并且我已经告诉您postProcessAfterInitialization 的目标对象是需要解包的代理。

以上是关于使用自定义 Autowired 时出现 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

在spring security dofilter身份验证方法中使用@Autowired注释时出现Nullpointer异常[关闭]

使用 Angular 材质定义自定义主题时出现 SassError

使用自定义操作栏布局时出现 Robolectric InflateException

使用 addCommand (webdriverio) 添加自定义命令时出现 ts 错误

使用 Hbase 自定义过滤器时出现异常

在类中使用自定义排序时出现编译错误 [重复]