首页 > 其他分享 >Spring Boot 依赖注入为 null 问题

Spring Boot 依赖注入为 null 问题

时间:2024-10-22 09:50:30浏览次数:3  
标签:enhancer return Spring beanName Boot bean null public

目录

问题

省流

代码复现

TestService

TestAspect

TestController

源码分析

AbstractAutoProxyCreator

CglibAopProxy

Enhancer


问题

工作中,在负责的模块里使用 @DubboService 注解注册了一个 dubbo 接口,给定时任务模块去调用。在自我调试阶段,需要在本地自己验证一下接口的功能实现,所以就在本地写了一个测试接口来调用 dubbo 接口,在 Controller 层使用 @DubboReference 注解注入依赖后,在 debug 时却发现注入的依赖为 null。第一时间怀疑是自己写法不对,时间比较赶,所以就想着先不研究这个问题,可以先通过其他方式来验证,可以使用 Spring 的 @Service 注解将 dubbo 接口实现注册成 Spring 容器的bean,然后通过 @Autowired 来注入依赖,同样可以验证 dubbo 接口的功能。但是最终在 debug 时,注入的依赖同样为 null。

省流

当接口使用了 AOP 时,Controller 层接口方法不能为 private !!!应该为 public!!!

正常来说都不应该遇到这种问题,写接口时一般也是直接复制现有的接口方法,然后修修改改,Controller 层的方法一般都是 public,所以当时出现问题了也没能快速反应到是方法修饰符写错了。项目中存在private修饰的接口方法,估计就是写代码时通过代码提示手滑才选中了private。

代码复现

这里使用简单的代码进行复现错误,以及排查过程。

TestService

使用 Spring 的 @Service 注解注册为 Spring 容器的 bean

@Service
public class TestService {

    public void doSomething() {
        System.out.println("doSomething");
    }
}

TestAspect

@Aspect
@Component
@Slf4j
public class TestAspect {

    @Pointcut("execution(* test*(..))")
    public void testPointCut() { }

    @Around("testPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        return point.proceed();
    }
}

TestController

@RestController
@RequestMapping ("test")
public class TestController {

    @Autowired
    private TestService testService;

    @RequestMapping("/test1")
    private ResponseEntity<String> test1(){

        testService.doSomething();

        return ResponseEntity.ok("success");
    }

    @RequestMapping("/test2")
    public ResponseEntity<String> test2(){

        testService.doSomething();

        return ResponseEntity.ok("success");
    }
}

当时项目是能够正常启动的,所以就排除了依赖不存在的问题,如果启动时发现找不到依赖,就会直接抛出异常,导致 Spring 容器启动了。

自己写的接口依赖注入为 null,但是试了下其他接口,却发现一切正常。

最终,在 debug 中发现了问题

依赖注入失败的接口依赖注入成功的接口,拿到的 Controller 实例不一样!

依赖注入失败的接口:

依赖注入成功的接口:

 可以看到两个接口最终拿到的是不一样的对象实例,public 方法获取到的就是有依赖注入的TestController实例,而private 方法获取到的是 TestController 的代理对象。代理对象显然是使用 AOP 动态生成的。

源码分析

直接从源码上看 AOP 创建切面代理对象的逻辑

AbstractAutoProxyCreator

在项目依赖中找到 AbstractAutoProxyCreator,可以看到它实现了 SmartInstantiationAwareBeanPostProcessor 接口,BeanPostProcessor 接口,就是用于 Spring 创建实例后做后置处理的,这里就是用来创建 AOP 代理对象。Spring 容器在实例化bean后,就会通过 postProcessAfterInitialization 来做 bean 的后置处理。

调用方法链如下:postProcessAfterInitialization -> wrapIfNecessary -> createProxy

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    //...... 省略无关代码

    /**
	 * Create a proxy with the configured interceptors if the bean is
	 * identified as one to proxy by the subclass.
	 * @see #getAdvicesAndAdvisorsForBean
	 */
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

    //...... 省略无关代码

	/**
	 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
	 * @param bean the raw bean instance
	 * @param beanName the name of the bean
	 * @param cacheKey the cache key for metadata access
	 * @return a proxy wrapping the bean, or the raw bean instance as-is
	 */
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}


    //...... 省略无关代码

	/**
	 * Create an AOP proxy for the given bean.
	 * @param beanClass the class of the bean
	 * @param beanName the name of the bean
	 * @param specificInterceptors the set of interceptors that is
	 * specific to this bean (may be empty, but not null)
	 * @param targetSource the TargetSource for the proxy,
	 * already pre-configured to access the bean
	 * @return the AOP proxy for the bean
	 * @see #buildAdvisors
	 */
	protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
			@Nullable Object[] specificInterceptors, TargetSource targetSource) {

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}

		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

		if (!proxyFactory.isProxyTargetClass()) {
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}

		return proxyFactory.getProxy(getProxyClassLoader());
	}
    
    //...... 省略无关代码

}

creatProxy 方法最后委托给了 proxyFactory 来创建代理对象

proxyFactory.getProxy(getProxyClassLoader());

从上面debug的截图中可以看出来,Spring 最后使用的是 cglib 动态代理方式。

所以这里直接找到对应 cglib 动态代理相关的类 CglibAopProxy

CglibAopProxy

class CglibAopProxy implements AopProxy, Serializable {
    //......省略无关代码

	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		if (logger.isTraceEnabled()) {
			logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
		}

		try {
			Class<?> rootClass = this.advised.getTargetClass();
			Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

			Class<?> proxySuperClass = rootClass;
			if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
				proxySuperClass = rootClass.getSuperclass();
				Class<?>[] additionalInterfaces = rootClass.getInterfaces();
				for (Class<?> additionalInterface : additionalInterfaces) {
					this.advised.addInterface(additionalInterface);
				}
			}

			// Validate the class, writing log messages as necessary.
			validateClassIfNecessary(proxySuperClass, classLoader);

			// Configure CGLIB Enhancer...
			Enhancer enhancer = createEnhancer();
			if (classLoader != null) {
				enhancer.setClassLoader(classLoader);
				if (classLoader instanceof SmartClassLoader &&
						((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
					enhancer.setUseCache(false);
				}
			}
			enhancer.setSuperclass(proxySuperClass);
			enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// Generate the proxy class and create a proxy instance.
			return createProxyClassAndInstance(enhancer, callbacks);
		}
		catch (CodeGenerationException | IllegalArgumentException ex) {
			throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
					": Common causes of this problem include using a final class or a non-visible class",
					ex);
		}
		catch (Throwable ex) {
			// TargetSource.getTarget() failed
			throw new AopConfigException("Unexpected AOP exception", ex);
		}
	}

	protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
		enhancer.setInterceptDuringConstruction(false);
		enhancer.setCallbacks(callbacks);
		return (this.constructorArgs != null && this.constructorArgTypes != null ?
				enhancer.create(this.constructorArgTypes, this.constructorArgs) :
				enhancer.create());
	}

    //......省略无关代码


}

可以看到,CglibAopProxy 最后将创建代理对象委托给了 Enhancer

Enhancer

在 Enhancer 中,可以看到其中的 getMethods 方法,其中的 CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));

public class Enhancer extends AbstractClassGenerator {

	private static void getMethods(Class superclass, Class[] interfaces, List methods, List interfaceMethods, Set forcePublic) {
		ReflectUtils.addAllMethods(superclass, methods);
		List target = (interfaceMethods != null) ? interfaceMethods : methods;
		if (interfaces != null) {
			for (int i = 0; i < interfaces.length; i++) {
				if (interfaces[i] != Factory.class) {
					ReflectUtils.addAllMethods(interfaces[i], target);
				}
			}
		}
		if (interfaceMethods != null) {
			if (forcePublic != null) {
				forcePublic.addAll(MethodWrapper.createSet(interfaceMethods));
			}
			methods.addAll(interfaceMethods);
		}
		CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC));
		CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));
		CollectionUtils.filter(methods, new DuplicatesPredicate());
		CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL));
	}

}

VisibilityPredicate 中,可以看到会取方法的修饰符做判断,Modifier.isPrivate(mode) 为true,则返回false。即将被代理对象的private方法过滤掉了,被代理对象中没有对应的方法,所以就只能执行代理对象中的方法了,而代理对象是由cglib实例化的,里面没有spring注入的对象,所以报空指针。

public class VisibilityPredicate implements Predicate {
    private boolean protectedOk;
    private String pkg;
    private boolean samePackageOk;

    public VisibilityPredicate(Class source, boolean protectedOk) {
        this.protectedOk = protectedOk;
        this.samePackageOk = source.getClassLoader() != null;
        this.pkg = TypeUtils.getPackageName(Type.getType(source));
    }

    public boolean evaluate(Object arg) {
        Member member = (Member)arg;
        int mod = member.getModifiers();
        if (Modifier.isPrivate(mod)) {
            return false;
        } else if (Modifier.isPublic(mod)) {
            return true;
        } else if (Modifier.isProtected(mod) && this.protectedOk) {
            return true;
        } else {
            return this.samePackageOk && this.pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass())));
        }
    }
}

标签:enhancer,return,Spring,beanName,Boot,bean,null,public
From: https://blog.csdn.net/typeracer/article/details/143087198

相关文章

  • 网上订餐系统|基于springBoot的网上订餐系统设计与实现(附项目源码+论文+数据库)
    私信或留言即免费送开题报告和任务书(可指定任意题目)目录一、摘要二、相关技术三、系统设计四、数据库设计  五、核心代码  六、论文参考  七、源码获取  一、摘要随着我国经济的飞速发展,人们的生活速度明显加快,在餐厅吃饭排队的情况到处可见,近年来由于新兴......
  • 基于SpringBoot+Vue的宠物管理系统(源码+LW+调试文档+讲解)
    基于SpringBoot+Vue的宠物管理系统是一款为宠物主人和宠物相关机构设计的高效管理工具。SpringBoot作为强大的后端框架,为系统提供了稳定可靠的服务。它能够处理复杂的业务逻辑,如宠物信息的存储、查询、更新,宠物医疗记录管理,以及宠物服务预约等。通过与数据库的良好交......
  • PbootCMS填写授权码的地方不见了怎么办
    当你在填写授权码时,同时填写了“授权码手机”这一栏,系统会认为你填写了“万能授权码”,从而隐藏了授权码输入框。如果你没有万能授权码,就不应该填写“授权码手机”这一栏。解决步骤1.清除授权码和授权码手机登录后台:打开浏览器,输入你的PbootCMS后台地址,登录后台管理系统。......
  • PbootCMS出现database disk image is malformed的解决办法
    databasediskimageismalformed 错误通常是由于SQLite数据库文件损坏引起的。这种问题可能发生在写入数据库时突然中断操作,比如服务器突然重启或网络中断等情况。以下是一些解决方法,包括删除栏目模型重建和修复SQLite数据库。解决方法1.删除栏目模型,重建备份数据库......
  • PbootCMS系统管理员点击文章评论的状态按钮提示权限不足
    1.开启后台菜单登录后台:打开浏览器,输入你的PbootCMS后台地址,登录后台管理系统。进入系统设置:在后台管理界面,进入“系统设置”->“菜单管理”。开启后台菜单:如果你还没有开启后台菜单,可以参考这篇教程:如何开启PbootCMS后台菜单。2.修改会员中心的文章评论......
  • spring mybatis upgrade to mybatisplus 实战小记
    我司压箱底儿的灵工服务商系统,系统框架是spring,持久层是mybatis。最近,将Mybatisplus集成到系统中,以提高开发效率。升级版本:mybatis版本3.2.2,升级到3.5.16Mybatisplus版本:3.5.3mybatis-spring版本1.2.0,升级到3.0.0pagehelper版本:5.3.1【注】mybatis官方提供了Myba......
  • 210基于java ssm springboot垃圾分类回收预约管理系统垃圾站点(源码+文档+运行视频+讲
     文章目录系列文章目录前言一、详细视频演示二、项目部分实现截图三、技术栈后端框架springboot后端框架springboot持久层框架MyBaitsPlus系统测试四、代码参考源码获取前言......
  • PbootCMS会话路径选择站内还是系统?
    PbootCMS的会话路径选择是一个需要仔细考虑的问题。不同的选择会有不同的优缺点,以下是对“站内”和“系统”两种选择的详细分析,帮助你做出合适的选择:会话路径选择1.选择站内优点:集中管理:所有缓存和会话文件都存储在网站目录下的runtime文件夹中,便于管理和清理。独立性:不......
  • SpringBoot启动报错java.nio.charset.MalformedInputException: Input length =1
    启动springboot项目时,出现了以下报错:defaultPattern_IS_UNDEFINEDdefaultPattern_IS_UNDEFINEDdefaultPattern_IS_UNDEFINEDjava.lang.IllegalStateException:Failedtoloadpropertysourcefromlocation'classpath:/application-local.yaml' atorg.springframework......
  • springboot新闻管理系统-毕业设计源码12529
    目 录摘要1绪论1.1研究背景1.2 研究意义1.3论文结构与章节安排2系统分析2.1可行性分析2.2系统流程分析2.2.1用户登录流程2.2.2数据删除流程2.3 系统功能分析2.4系统用例分析2.5本章小结3 系统总体设计3.1系统架构设计3.2系统功......