摄影网站难做吗,关键词快速排名平台,wordpress外汇主题,wordpress 另类加速问题描述 初学 Spring 时#xff0c;我们往往不能快速转化思维。例如#xff0c;在程序开发过程中#xff0c;有时候#xff0c;一方面我们把一个类定义成 Bean#xff0c;同时又觉得这个 Bean 的定义除了加了一些 Spring 注解外#xff0c;并没有什么不同。所以在后续使…问题描述 初学 Spring 时我们往往不能快速转化思维。例如在程序开发过程中有时候一方面我们把一个类定义成 Bean同时又觉得这个 Bean 的定义除了加了一些 Spring 注解外并没有什么不同。所以在后续使用时有时候我们会不假思索地去随意定义它例如我们会写出下面这样的代码 Service
public class ServiceImpl {private String serviceName;public ServiceImpl(String serviceName){this.serviceName serviceName;}}ServiceImpl 因为标记为 Service 而成为一个 Bean。另外我们 ServiceImpl 显式定义了一个构造器。但是上面的代码不是永远都能正确运行的有时候会报下面这种错误 Parameter 0 of constructor in com.spring.puzzle.class1.example2.ServiceImpl required a bean of type ‘java.lang.String’ that could not be found.
案例分析 当创建一个 Bean 时调用的方法是 AbstractAutowireCapableBeanFactory#createBeanInstance。它主要包含两大基本步骤寻找构造器和通过反射调用构造器创建实例。对于这个案例最核心的代码执行你可以参考下面的代码片段 // Candidate constructors for autowiring?
Constructor?[] ctors determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors ! null || mbd.getResolvedAutowireMode() AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);
}Spring 会先执行 determineConstructorsFromBeanPostProcessors 方法来获取构造器然后通过 autowireConstructor 方法带着构造器去创建实例。很明显在本案例中只有一个构造器所以非常容易跟踪这个问题。 autowireConstructor 方法要创建实例不仅需要知道是哪个构造器还需要知道构造器对应的参数这点从最后创建实例的方法名也可以看出参考如下即 ConstructorResolver#instantiate private Object instantiate(String beanName, RootBeanDefinition mbd, Constructor? constructorToUse, Object[] argsToUse) 那么上述方法中存储构造参数的 argsToUse 如何获取呢换言之当我们已经知道构造器 ServiceImpl(String serviceName)要创建出 ServiceImpl 实例如何确定 serviceName 的值是多少 很明显这里是在使用 Spring我们不能直接显式使用 new 关键字来创建实例。Spring 只能是去寻找依赖来作为构造器调用参数。 那么这个参数如何获取呢可以参考下面的代码片段即 ConstructorResolver#autowireConstructor argsHolder createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,getUserDeclaredConstructor(candidate), autowiring, candidates.length 1);我们可以调用 createArgumentArray 方法来构建调用构造器的参数数组而这个方法的最终实现是从 BeanFactory 中获取 Bean可以参考下述调用 return this.beanFactory.resolveDependency(new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);如果用调试视图我们则可以看到更多的信息 如图所示上述的调用即是根据参数来寻找对应的 Bean在本案例中如果找不到对应的 Bean 就会抛出异常提示装配失败。
问题修正 从源码级别了解了错误的原因后现在反思为什么会出现这个错误。追根溯源正如开头所述因为不了解很多隐式的规则我们定义一个类为 Bean如果再显式定义了构造器那么这个 Bean 在构建时会自动根据构造器参数定义寻找对应的 Bean然后反射创建出这个 Bean。 了解了这个隐式规则后解决这个问题就简单多了。我们可以直接定义一个能让 Spring 装配给 ServiceImpl 构造器参数的 Bean例如定义如下 //这个bean装配给ServiceImpl的构造器参数“serviceName”
Bean
public String serviceName(){return MyServiceName;
}再次运行程序发现一切正常了。 所以我们在使用 Spring 时不要总想着定义的 Bean 也可以在非 Spring 场合直接用 new 关键字显式使用这种思路是不可取的。 另外类似的假设我们不了解 Spring 的隐式规则在修正问题后我们可能写出更多看似可以运行的程序代码如下 Service
public class ServiceImpl {private String serviceName;public ServiceImpl(String serviceName){this.serviceName serviceName;}public ServiceImpl(String serviceName, String otherStringParameter){this.serviceName serviceName;}
}如果我们仍用非 Spring 的思维去审阅这段代码可能不会觉得有什么问题毕竟 String 类型可以自动装配了无非就是增加了一个 String 类型的参数而已。 但是如果你了解 Spring 内部是用反射来构建 Bean 的话就不难发现问题所在存在两个构造器都可以调用时到底应该调用哪个呢最终 Spring 无从选择只能尝试去调用默认构造器而这个默认构造器又不存在所以测试这个程序它会出错。