做网站代理拉不到人,中国佛山手机网站建设,郑州网站建设优化,wordpress出境游前言
最近做项目#xff0c;发现了springboot2.7.x在参数initiate的时候可以反复初始化#xff0c;而且首次异常后#xff0c;第二次成功居然也可以启动#xff0c;通过查看源代码发现了问题根源#xff0c;且在springboot高版本3.x#xff0c;就出现了了Configuration的…前言
最近做项目发现了springboot2.7.x在参数initiate的时候可以反复初始化而且首次异常后第二次成功居然也可以启动通过查看源代码发现了问题根源且在springboot高版本3.x就出现了了Configuration的
enforceUniqueMethods
的加载配置项应该是springboot发现并修复了这个问题。
Spring Boot 2.7.14
使用oracle jdk8和springboot 2.7.14按照如下方式初始化bean
Configuration
EnableConfigurationProperties(DemoProperties.class)
public class DemoConfig {Autowiredprivate DemoProperties demoProperties;Beanpublic FirstBean initFirstBean(NeedDemoBean needDemoBean) {FirstBean firstBean new FirstBean();firstBean.setNeedDemoBean(needDemoBean);return firstBean;}BeanConditionalOnMissingBean(DemoBean.class)public DemoBean initDemoBean(DemoProperties demoProperties) {DemoBean demoBean new DemoBean();demoBean.setDemoProperties(demoProperties);return demoBean;}BeanConditionalOnBean(DemoBean.class)public NeedDemoBean initNeedDemoBean(DemoBean demoBean){NeedDemoBean needDemoBean new NeedDemoBean();needDemoBean.setDemoBean(demoBean);return needDemoBean;}BeanConditionalOnMissingBean(DemoBean.class)public NeedDemoBean initNeedDemoBean(){return new NeedDemoBean();}
}
其中DemoBean首次初始化抛出异常
public class DemoBean {private DemoProperties demoProperties;private static boolean isFirstInit true;public DemoProperties getDemoProperties() {return demoProperties;}public void setDemoProperties(DemoProperties demoProperties) {this.demoProperties demoProperties;}PostConstructpublic void init(){haha();}public void haha(){if (isFirstInit) {System.out.println(1111111111111111111111);isFirstInit false;throw new RuntimeException(first init error.........................);}System.out.println(22222222222222222222222222);}
}springboot启动完成后可以正常启动日志也不会出现异常 这里就很诡异了为什么初始化2次为什么抛的异常不见了
原理分析
首先分析bean的加载顺序 步骤就是如图
但是为什么DemoBean初始化2次原因在于参数依赖初始化bean依赖注入与相同的beanId的beanFactory的创建过程
源代码分析
从源码上验证我们上面的分析说明分析的是对的 Spring默认安装beanNames的顺序创建bean但是我们可以控制顺序这里就存在控制的情况同时这个官方注解也说明了non-lazy的单例
org.springframework.beans.factory.support.DefaultListableBeanFactory的
preInstantiateSingletons
证明Configuration的bean先创建然后创建FirstBean根据程序栈确实如此 但是这个方法有参数根据依赖注入原则需要对参数bean进行create
org.springframework.beans.factory.support.ConstructorResolver 问题就在这里在参数依赖注入创建bean的过程抛出异常会被吞掉在后面的逻辑处理只有多次创建bean都失败才会抛出最后一次的异常 问题就出在这里实际上多个BeanId相同的的时候如果有条件控制会不受条件控制那么会初始化就可能发生多次其中一次成功即可且没有注解限制笔者明明注解了
ConditionalOnBean
但是因为依赖注入没有Bean就会创建Bean关键是也没提示抛的异常也看不到 /*** Instantiate the bean using a named factory method. The method may be static, if the* bean definition parameter specifies a class, rather than a factory-bean, or* an instance variable on a factory object itself configured using Dependency Injection.* pImplementation requires iterating over the static or instance methods with the* name specified in the RootBeanDefinition (the method may be overloaded) and trying* to match with the parameters. We dont have the types attached to constructor args,* so trial and error is the only way to go here. The explicitArgs array may contain* argument values passed in programmatically via the corresponding getBean method.* param beanName the name of the bean* param mbd the merged bean definition for the bean* param explicitArgs argument values passed in programmatically via the getBean* method, or {code null} if none (- use constructor argument values from bean definition)* return a BeanWrapper for the new instance*/public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, Nullable Object[] explicitArgs) {BeanWrapperImpl bw new BeanWrapperImpl();this.beanFactory.initBeanWrapper(bw);Object factoryBean;Class? factoryClass;boolean isStatic;String factoryBeanName mbd.getFactoryBeanName();if (factoryBeanName ! null) {if (factoryBeanName.equals(beanName)) {throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,factory-bean reference points back to the same bean definition);}factoryBean this.beanFactory.getBean(factoryBeanName);if (mbd.isSingleton() this.beanFactory.containsSingleton(beanName)) {throw new ImplicitlyAppearedSingletonException();}this.beanFactory.registerDependentBean(factoryBeanName, beanName);factoryClass factoryBean.getClass();isStatic false;}else {// Its a static factory method on the bean class.if (!mbd.hasBeanClass()) {throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,bean definition declares neither a bean class nor a factory-bean reference);}factoryBean null;factoryClass mbd.getBeanClass();isStatic true;}Method factoryMethodToUse null;ArgumentsHolder argsHolderToUse null;Object[] argsToUse null;if (explicitArgs ! null) {argsToUse explicitArgs;}else {Object[] argsToResolve null;synchronized (mbd.constructorArgumentLock) {factoryMethodToUse (Method) mbd.resolvedConstructorOrFactoryMethod;if (factoryMethodToUse ! null mbd.constructorArgumentsResolved) {// Found a cached factory method...argsToUse mbd.resolvedConstructorArguments;if (argsToUse null) {argsToResolve mbd.preparedConstructorArguments;}}}if (argsToResolve ! null) {argsToUse resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);}}//刚刚启动这些是没有的if (factoryMethodToUse null || argsToUse null) {// Need to determine the factory method...// Try all methods with this name to see if they match the given arguments.factoryClass ClassUtils.getUserClass(factoryClass);ListMethod candidates null;//bean id唯一这也是解决办法之一说明Spring设计的时候是考虑的if (mbd.isFactoryMethodUnique) {if (factoryMethodToUse null) {factoryMethodToUse mbd.getResolvedFactoryMethod();}if (factoryMethodToUse ! null) {candidates Collections.singletonList(factoryMethodToUse);}}//读取method就是Bean的方法通过class鉴别if (candidates null) {candidates new ArrayList();Method[] rawCandidates getCandidateMethods(factoryClass, mbd);for (Method candidate : rawCandidates) {if (Modifier.isStatic(candidate.getModifiers()) isStatic mbd.isFactoryMethod(candidate)) {candidates.add(candidate);}}}//一个method的情况显式参数为null没有构造函数if (candidates.size() 1 explicitArgs null !mbd.hasConstructorArgumentValues()) {Method uniqueCandidate candidates.get(0);if (uniqueCandidate.getParameterCount() 0) {mbd.factoryMethodToIntrospect uniqueCandidate;synchronized (mbd.constructorArgumentLock) {mbd.resolvedConstructorOrFactoryMethod uniqueCandidate;mbd.constructorArgumentsResolved true;mbd.resolvedConstructorArguments EMPTY_ARGS;}bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, uniqueCandidate, EMPTY_ARGS));return bw;}}//多个的时候考虑顺序排序if (candidates.size() 1) { // explicitly skip immutable singletonListcandidates.sort(AutowireUtils.EXECUTABLE_COMPARATOR);}ConstructorArgumentValues resolvedValues null;boolean autowiring (mbd.getResolvedAutowireMode() AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);int minTypeDiffWeight Integer.MAX_VALUE;SetMethod ambiguousFactoryMethods null;int minNrOfArgs;if (explicitArgs ! null) {minNrOfArgs explicitArgs.length;}else {// We dont have arguments passed in programmatically, so we need to resolve the// arguments specified in the constructor arguments held in the bean definition.if (mbd.hasConstructorArgumentValues()) {ConstructorArgumentValues cargs mbd.getConstructorArgumentValues();resolvedValues new ConstructorArgumentValues();minNrOfArgs resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);}else {minNrOfArgs 0;}}DequeUnsatisfiedDependencyException causes null;// 多个method实际上只有一个成功就行for (Method candidate : candidates) {int parameterCount candidate.getParameterCount();if (parameterCount minNrOfArgs) {ArgumentsHolder argsHolder;Class?[] paramTypes candidate.getParameterTypes();//显式参数是外部传入的参数比如配置文件等不是依赖注入Bean参数if (explicitArgs ! null) {// Explicit arguments given - arguments length must match exactly. // 有显式参数的情况if (paramTypes.length ! explicitArgs.length) {continue;}argsHolder new ArgumentsHolder(explicitArgs);}else { //一般情况都不是显式参数// Resolved constructor arguments: type conversion and/or autowiring necessary.try {String[] paramNames null;ParameterNameDiscoverer pnd this.beanFactory.getParameterNameDiscoverer();if (pnd ! null) {paramNames pnd.getParameterNames(candidate);}//这个很关键只要一次成功即可argsHolder createArgumentArray(beanName, mbd, resolvedValues, bw,paramTypes, paramNames, candidate, autowiring, candidates.size() 1);}catch (UnsatisfiedDependencyException ex) {if (logger.isTraceEnabled()) {logger.trace(Ignoring factory method [ candidate ] of bean beanName : ex);}// Swallow and try next overloaded factory method.if (causes null) {causes new ArrayDeque(1);}causes.add(ex);//异常虽然加了但是for循环一下次如果创建成功就不会失败continue; // 异常就Contine}}int typeDiffWeight (mbd.isLenientConstructorResolution() ?argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));// Choose this factory method if it represents the closest match.if (typeDiffWeight minTypeDiffWeight) {factoryMethodToUse candidate;argsHolderToUse argsHolder; //这个决定异常是否抛出argsToUse argsHolder.arguments; //这个是参数同一个对象minTypeDiffWeight typeDiffWeight;ambiguousFactoryMethods null;}// Find out about ambiguity: In case of the same type difference weight// for methods with the same number of parameters, collect such candidates// and eventually raise an ambiguity exception.// However, only perform that check in non-lenient constructor resolution mode,// and explicitly ignore overridden methods (with the same parameter signature).else if (factoryMethodToUse ! null typeDiffWeight minTypeDiffWeight !mbd.isLenientConstructorResolution() paramTypes.length factoryMethodToUse.getParameterCount() !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {if (ambiguousFactoryMethods null) {ambiguousFactoryMethods new LinkedHashSet();ambiguousFactoryMethods.add(factoryMethodToUse);}ambiguousFactoryMethods.add(candidate);}}} // 这里就是多个method的情况结束一个method就不存多次创建一次成功就好的情况异常直接抛了if (factoryMethodToUse null || argsToUse null) {//跟刚刚的赋值对应if (causes ! null) { //只有创建bean失败才会抛出异常UnsatisfiedDependencyException ex causes.removeLast();for (Exception cause : causes) {this.beanFactory.onSuppressedException(cause);}//创建bean的异常就是这里抛出的throw ex; //如果有异常就抛}ListString argTypes new ArrayList(minNrOfArgs);if (explicitArgs ! null) {for (Object arg : explicitArgs) {argTypes.add(arg ! null ? arg.getClass().getSimpleName() : null);}}else if (resolvedValues ! null) {SetValueHolder valueHolders new LinkedHashSet(resolvedValues.getArgumentCount());valueHolders.addAll(resolvedValues.getIndexedArgumentValues().values());valueHolders.addAll(resolvedValues.getGenericArgumentValues());for (ValueHolder value : valueHolders) {String argType (value.getType() ! null ? ClassUtils.getShortName(value.getType()) :(value.getValue() ! null ? value.getValue().getClass().getSimpleName() : null));argTypes.add(argType);}}String argDesc StringUtils.collectionToCommaDelimitedString(argTypes);throw new BeanCreationException(mbd.getResourceDescription(), beanName,No matching factory method found on class [ factoryClass.getName() ]: (mbd.getFactoryBeanName() ! null ?factory bean mbd.getFactoryBeanName() ; : ) factory method mbd.getFactoryMethodName() ( argDesc ). Check that a method with the specified name (minNrOfArgs 0 ? and arguments : ) exists and that it is (isStatic ? static : non-static) .);}else if (void.class factoryMethodToUse.getReturnType()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,Invalid factory method mbd.getFactoryMethodName() on class [ factoryClass.getName() ]: needs to have a non-void return type!);}else if (ambiguousFactoryMethods ! null) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,Ambiguous factory method matches found on class [ factoryClass.getName() ] (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): ambiguousFactoryMethods);}if (explicitArgs null argsHolderToUse ! null) {mbd.factoryMethodToIntrospect factoryMethodToUse;argsHolderToUse.storeCache(mbd, factoryMethodToUse);}}bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, factoryMethodToUse, argsToUse));return bw;}
就会出现明明异常了而且因为相同的Bean ID多个其中一个创建的bean创建成功 但是确把依赖Bean创建成功了明显不符合ConditionOnBean的实际情况 实际Spring执行顺序如下 Spring Boot 3.1.x
需要JDK 17LTS因为JDK8已经不支持了
Spring增加了
enforceUniqueMethods
如果不加默认情况会报错
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Configuration class DemoConfig contains overloaded Bean methods with name initNeedDemoBean. Use unique method names for separate bean definitions (with individual conditions etc) or switch Configuration.enforceUniqueMethods to false.
Offending resource: class path resource [org/example/boot/demo/config/DemoConfig.class]at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)at org.springframework.context.annotation.ConfigurationClass.validate(ConfigurationClass.java:233)at org.springframework.context.annotation.ConfigurationClassParser.validate(ConfigurationClassParser.java:205)at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:416)at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:287)at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344)at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115)at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:771)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:589)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:436)at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295)at org.example.boot.demo.BootMain.main(BootMain.java:9)
如果我们主动设置为false呢
Use unique method names for separate bean definitions (with individual conditions etc) or switch Configuration.enforceUniqueMethods to false.
结果也会一样 因为org.springframework.beans.factory.support.ConstructorResolver代码没变仅仅是增加了扫描检查而已默认就是要检查的相当于源头规避问题让你充分知道但是解决这个问题由你自己解决 总结
这个问题实际上出现不是很频繁但是如果不经意就会出现我们不可预知的问题尤其是初始化的情况不同条件初始化绝对不一样出现这种问题我们很难知道根源因为异常被吞了启动也OK。当然解决问题是不使用id相同的Bean创建方式因为如果异常只要其中一个Bean创建成功即可成功没异常我们发现不了问题如果没有异常相同的Bean id会被后创建的Bean替代但是在相同id的时候是都会尝试创建ConditionOnXxx就不会执行这个会跟我们需要的情况相违背。当然如果升级SpringBoot 3.x启动就会提示问题也是一个解决之道类似Spring5.3收拢循环依赖一样循环依赖虽然Spring也能解决其中一些情况比如Set注入构造函数不行但是终有隐患这个问题同理所以Spring也是直接把开关设置默认true启动直接保错解决思路同理。