使用自定义 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