松下mbdln25se参数怎么(Spring三级缓存和循环依赖的理解(面试))
Posted
篇首语:非淡泊无以明志,非宁静无以致远。本文由小常识网(cha138.com)小编为大家整理,主要介绍了松下mbdln25se参数怎么(Spring三级缓存和循环依赖的理解(面试))相关的知识,希望对你有一定的参考价值。
松下mbdln25se参数怎么(Spring三级缓存和循环依赖的理解(面试))
1. Spring创建bean的过程
Spring创建bean的过程其实就是创建对象的过程,创建对象我们一般手工先new对象分配内存地址,然后再给对象的属性赋值,从而完成一个对象整体流程的创建。
Spring创建对象也遵循此流程,只不过是Spring容器通过反射等完成的。
2. 什么是循环依赖?
- 循环依赖分为三种,自身依赖于自身、互相循环依赖、多组循环依赖。
- 但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
- 所以 Spring 提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。那么我们也可以先来尝试下这样的依赖,如果是我们自己处理的话该怎么解决。
3. Spring循环依赖场景
4. Spring解决方案-三级缓存
三级缓存实际上就是三个Map对象,从Spring创建bean存放对象的顺序开始。
- 三级缓存singletonFactories存放ObjectFactory,传入的是匿名内部类,ObjectFactory.getObject() 方法最终会调用getEarlyBeanReference()进行处理,返回创建bean实例化的lambda表达式。
- 二级缓存earlySingletonObjects存放bean,保存半成品bean实例,当对象需要被AOP切面代时,保存代理bean的实例beanProxy
- 一级缓存(单例池)singletonObjects存放完整的bean实例
/** Cache of singleton objects: bean name to bean instance. */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of early singleton objects: bean name to bean instance. */private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);/** Cache of singleton factories: bean name to ObjectFactory. */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)
5. 三级缓存如何解决循环依赖?
Spring解决循环依赖的核心思想在于提前曝光,首先创建实例化Student,并在三级缓存singletonFactories中保存实例化Student的lambda表达式以便获取Student实例,当我没有循环依赖和AOP时,这个三级缓存singletonFactories是没用在后续用到的。
但是当我Student对象需要注入ClassRoom对象,发现缓存里还没有ClassRoom对象,创建ClassRoom对象并又上述所说添加进三级缓存singletonFactories,ClassRoom对象需要注入Student对象,这时从半成品缓存里取到半成品对象Student,通过缓存的lambda表达式创建Student实例对象,并放到二级缓存earlySingletonObjects中。
此时ClassRoom对象可以注入Student对象实例和初始化自己,之后将完成品B对象放入完成品缓存singletonObjects。但是当有aop时,ClassRoom对象还没有把完成品ClassRoom对象放入完成品缓存singletonObjects中,ClassRoom对象初始化后需要进行代理对象的创建,此时需要从singletonFactories获取bean实例对象,进行createProxy创建代理类操作,这是会把proxy&B放入二级缓存earlySingletonObjects中。这时候才会把完整的ClassRoom对象放入完成品一级缓存也叫单例池singletonObjects中,返回给Student对象。
Student对象继续注入其他属性和初始化,之后将完成品Student对象放入完成品缓存。bean注入的入口在DefaultListableBeanFactory#preInstantiateSingletons()
案例代码如下:
public class CircularReferencesDemo public static void main(String[] args) AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 注册 Configuration Class context.register(CircularReferencesDemo.class); // 如果设置为 false,则抛出异常信息如:currently in creation: Is there an unresolvable circular reference? // 默认值为 true context.setAllowCircularReferences(true); // 启动 Spring 应用上下文 context.refresh(); System.out.println("Student : " + context.getBean(Student.class)); System.out.println("ClassRoom : " + context.getBean(ClassRoom.class)); // 关闭 Spring 应用上下文 context.close(); @Bean public static Student student() Student student = new Student(); student.setId(1L); student.setName("张三"); return student; @Bean public static ClassRoom classRoom() ClassRoom classRoom = new ClassRoom(); classRoom.setName("C122"); return classRoom; public class ClassRoom private String name; @Autowired private Collection<Student> students; public String getName() return name; public void setName(String name) this.name = name; public Collection<Student> getStudents() return students; public void setStudents(Collection<Student> students) this.students = students; @Override public String toString() return "ClassRoom" + "name='" + name + '\\'' + ", students=" + students + ''; public class Student private Long id; private String name; @Autowired private ClassRoom classRoom; public Long getId() return id; public void setId(Long id) this.id = id; public String getName() return name; public void setName(String name) this.name = name; public ClassRoom getClassRoom() return classRoom; public void setClassRoom(ClassRoom classRoom) this.classRoom = classRoom; @Override public String toString() return "Student" + "id=" + id + ", name='" + name + '\\'' + ", classRoom.name=" + classRoom.getName() + '';
6. 源码讲解
对于Spring处理循环依赖问题的方式,我们这里通过上面的流程图其实很容易就可以理解
需要注意的一个点,Spring是如何标记开始生成的A对象是一个半成品,并且是如何保存A对象的。
这里的标记工作Spring是使用属性singletonsCurrentlyInCreation来保存的,而半成品的A对象则是通过singletonFactories来保存的
protected void beforeSingletonCreation(String beanName) if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) throw new BeanCurrentlyInCreationException(beanName);
这里的ObjectFactory是一个工厂对象,可通过调用其getObject()方法来获取目标对象。在AbstractBeanFactory.doGetBean()方法中获取对象的方法如下:
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException String beanName = transformedBeanName(name); Object beanInstance; // Eagerly check singleton cache for manually registered singletons. // Spring首先从singletonObjects(一级缓存)中尝试获取 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) if (logger.isTraceEnabled()) if (isSingletonCurrentlyInCreation(beanName)) logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); else logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); else // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) throw new BeanCurrentlyInCreationException(beanName); // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); else if (args != null) // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); else if (requiredType != null) // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); else return (T) parentBeanFactory.getBean(nameToLookup); if (!typeCheckOnly) markBeanAsCreated(beanName); StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate") .tag("beanName", name); try if (requiredType != null) beanCreation.tag("beanType", requiredType::toString); RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) for (String dep : dependsOn) if (isDependent(beanName, dep)) throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); registerDependentBean(dep, beanName); try getBean(dep); catch (NoSuchBeanDefinitionException ex) throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); // 单利bean if (mbd.isSingleton()) // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建 // 目标对象,确保ObjectFactory类型只调用一次 sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() @Override public Object getObject() throws BeansException try //具体对象的创建 return createBean(beanName, mbd, args); catch (BeansException ex) // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; ); beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); else if (mbd.isPrototype()) // It's a prototype -> create a new instance. Object prototypeInstance = null; try beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); finally afterPrototypeCreation(beanName); beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); else String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) throw new IllegalStateException("No scope name defined for bean '" + beanName + "'"); Scope scope = this.scopes.get(scopeName); if (scope == null) throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); try Object scopedInstance = scope.get(beanName, () -> beforePrototypeCreation(beanName); try return createBean(beanName, mbd, args); finally afterPrototypeCreation(beanName); ); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); catch (IllegalStateException ex) throw new ScopeNotActiveException(beanName, scopeName, ex); catch (BeansException ex) beanCreation.tag("exception", ex.getClass().toString()); beanCreation.tag("message", String.valueOf(ex.getMessage())); cleanupAfterBeanCreationFailure(beanName); throw ex; finally beanCreation.end(); return adaptBeanInstance(name, beanInstance, requiredType);
这里的doGetBean()方法是非常关键的一个方法(中间省略了其他代码),上面也主要有两个步骤
第一个步骤的getSingleton()方法的作用是尝试从缓存中获取目标对象,如果没有获取到,则尝试获取半成品的目标对象;如果第一个步骤没有获取到目标对象的实例,那么就进入第二个步骤
第二个步骤的getSingleton()方法的作用是尝试创建目标对象,并且为该对象注入其所依赖的属性。
这里其实就是主干逻辑,我们前面图中已经标明,在整个过程中会调用三次doGetBean()方法
第一次调用的时候会尝试获取Student对象实例,此时走的是第一个getSingleton()方法,由于没有已经创建的Student对象的成品或半成品,因而这里得到的是null
然后就会调用第二个getSingleton()方法,创建Student对象的实例,然后递归的调用doGetBean()方法,尝试获取ClassRoom对象的实例以注入到Student对象中
此时由于Spring容器中也没有ClassRoom对象的成品或半成品,因而还是会走到第二个getSingleton()方法,在该方法中创建ClassRoom对象的实例
创建完成之后,尝试获取其所依赖的Student的实例作为其属性,因而还是会递归的调用doGetBean()方法
此时需要注意的是,在前面由于已经有了一个半成品的Student对象的实例,因而这个时候,再尝试获取Student对象的实例的时候,会走第一个getSingleton()方法
在该方法中会得到一个半成品的Student对象的实例,然后将该实例返回,并且将其注入到ClassRoom对象的属性a中,此时ClassRoom对象实例化完成。
然后,将实例化完成的ClassRoom对象递归的返回,此时就会将该实例注入到Student对象中,这样就得到了一个成品的Student对象。
我们这里可以阅读上面的第一个getSingleton()方法:
@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回 Object singletonObject = this.singletonObjects.get(beanName); // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象 // 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) synchronized (this.singletonObjects) singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的 // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品 ObjectFactory singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) // 获取目标对象的实例 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); return singletonObject;
这里我们会存在一个问题就是A的半成品实例是如何实例化的,然后是如何将其封装为一个ObjectFactory类型的对象,并且将其放到上面的singletonFactories属性中的。
这主要是在前面的第二个getSingleton()方法中,其最终会通过其传入的第二个参数,从而调用createBean()方法,该方法的最终调用是委托给了另一个doCreateBean()方法进行的
这里面有如下一段代码:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException // 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的 BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); if (instanceWrapper == null) instanceWrapper = createBeanInstance(beanName, mbd, args); // 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) // 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是 // 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是 // 目标bean直接返回 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); try // 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了, // 就会递归的调用getBean()方法尝试获取目标bean populateBean(beanName, mbd, instanceWrapper); catch (Throwable ex) // 省略... return exposedObject;
到这里,Spring整个解决循环依赖问题的实现思路已经比较清楚了。对于整体过程,读者朋友只要理解两点:
- Spring是通过递归的方式获取目标bean及其所依赖的bean的;
- Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。
结合这两点,也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。
7. 三级缓存分别不同的处理过程
7.1 一级缓存能解决吗?
其实只有一级缓存并不是不能解决循环依赖,就像我们自己做的例子一样。但是在 Spring 中如果像我们例子里那么处理,就会变得非常麻烦,而且也可能会出现 NPE 问题。所以如图按照 Spring 中代码处理的流程,我们去分析一级缓存这样存放成品 Bean 的流程中,是不能解决循环依赖的问题的。因为 A 的成品创建依赖于 B,B的成品创建又依赖于 A,当需要补全B的属性时 A 还是没有创建完,所以会出现死循环。
public class CircleTest private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); public static void main(String[] args) throws Exception System.out.println(getBean(B.class).getA()); System.out.println(getBean(A.class).getB()); private static <T> T getBean(Class<T> beanClass) throws Exception String beanName = beanClass.getSimpleName().toLowerCase(); if (singletonObjects.containsKey(beanName)) return (T) singletonObjects.get(beanName); // 实例化对象入缓存 Object obj = beanClass.newInstance(); singletonObjects.put(beanName, obj); // 属性填充补全对象 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) field.setAccessible(true); Class<?> fieldClass = field.getType(); String fieldBeanName = fieldClass.getSimpleName().toLowerCase(); field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass)); field.setAccessible(false); return (T) obj; class A private B b; // ...get/setclass B private A a; // ...get/set
7.2 使用二级缓存能不能解决循环依赖?
一定是不行,我们只保留二级缓存有两个可能性保留一二singletonObjects和earlySingletonObjects,或者一三singletonObjects和singletonFactories
【只保留一二singletonObjects和earlySingletonObjects】
流程可以这样走:实例化A ->将半成品的A放入earlySingletonObjects中 ->填充A的属性时发现取不到B->实例化B->将半成品的B放入earlySingletonObjects中->从earlySingletonObjects中取出A填充B的属性->将成品B放入singletonObjects,并从earlySingletonObjects中删除B->将B填充到A的属性中->将成品A放入singletonObjects并删除earlySingletonObjects。
这样的流程是线程安全的,不过如果A上加个切面(AOP),这种做法就没法满足需求了,因为earlySingletonObjects中存放的都是原始对象,而我们需要注入的其实是A的代理对象。
【只保留一三singletonObjects和singletonFactories】
流程是这样的:实例化A ->创建A的对象工厂并放入singletonFactories中 ->填充A的属性时发现取不到B->实例化B->创建B的对象工厂并放入singletonFactories中->从singletonFactories中获取A的对象工厂并获取A填充到B中->将成品B放入singletonObjects,并从singletonFactories中删除B的对象工厂->将B填充到A的属性中->将成品A放入singletonObjects并删除A的对象工厂。
同样,这样的流程也适用于普通的IOC已经有并发的场景,但如果A上加个切面(AOP)的话,这种情况也无法满足需求。
因为拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,getEarlyBeanReference这个方法每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象
所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。
7. 参考文章
- https://www.zhihu.com/tardis/sogou/art/84267654
- https://bbs.huaweicloud.com/blogs/268055?utm_source=zhihu&utm_medium=bbs-ex&utm_campaign=other&utm_content=content
相关参考
01西门子品牌6SE70书本型变频器:设定密码打不开时,将P358和P359中数据改为相同即可。02ABB品牌ACS600变频器:在16.03参数中输入密码“23032”,102.01参数设置为false,可以进入设定所有主控板参数。03三菱品牌740系列变频器:设定...
松下变频器参数设置怎么解锁(变频器被加密了不知道怎么办?(强烈建议收藏)-民熔)
为了防止其参数被随意修改,很多人都会设定一个密码,但是问题来了,由于变频器的参数设置成功以后一般不会经常修改,时间长了,密码就忘记了,或者原来的技术人员离职了,工厂又来了新的电气技术人员接手。一旦工厂...
来源:电工电力论坛西门子品牌6SE70书本型变频器:设定密码打不开时,将P358和P359中数据改为相同即可。ABB品牌ACS600变频器:在16.03参数中输入密码“23032”,102.01参数设置为false,可以进入设定所有主控板参数。三菱品牌740系...
松下门机变频器不开门(变频器被加密了怎么办?(强烈建议收藏))
西门子品牌6SE70书本型变频器:设定密码打不开时,将P358和P359中数据改为相同即可。ABB品牌ACS600变频器:在16.03参数中输入密码“23032”,102.01参数设置为false,可以进入设定所有主控板参数。三菱品牌740系列变频器:设定密码...
松下变频器控制接线图(变频器被加密了不知道怎么办?(强烈建议收藏)-民熔)
为了防止其参数被随意修改,很多人都会设定一个密码,但是问题来了,由于变频器的参数设置成功以后一般不会经常修改,时间长了,密码就忘记了,或者原来的技术人员离职了,工厂又来了新的电气技术人员接手。一旦工厂...
在我们日常使用变频器的时候为了防止其参数被随意修改,很多人都会设定一个密码,但是问题来了,由于变频器的参数设置成功以后一般不会经常修改,时间长了,密码就忘记了,或者原来的技术人员离职了,工厂又来了新的...
松下全热交换机(松下空调15匹R1新一级和R3新三级能效对比拆机)
松下空调一级能效和三级能效区别有多大,这次拆下松下R系列空调对比看下。R系列是松下最便宜的入门款变频空调,R1是新一级能效,R3是新三级能效,这次拆的都是1.5匹挂机。松下空调命名规则极为混乱,松下空调在线下渠道...
数控液压机调压力视频(13代酷睿i7-13790F评测:更大缓存网游吃香,中国独享整机拍档)
...款“奇怪”的产品i5-12490F。它拥有比i5-12400F略高的频率和三级缓存,但价格却相差无几。包装盒还一改往日的蓝色,用上了定位更高的黑盒。最特别的是,它是中国大陆地区专供的型号。在过去一年里,i5-12490F一直活跃在各大整...
松下A5,A6伺服电机参数设置伺服电机有很多参数可以设置,下面我们来说说其中几个简单的参数,Pr0.08,Pr0.09,Pr0.10这三个参数句具体含义如下图所示特闷之间的关系看下图从上图我们可以看到,如果我们想用10000个脉冲让电机旋...
松下a4驱动器方向参数(轻松了解松下伺服驱动器操作及参数写入保存和报警代码的含义)
随着自动化程度的不断提高,松下伺服驱动器作为重要的动力元件,在自动化运用中的地位也不断提高。今天我们就来详细了解一款松下伺服驱动器的操作方法。面板按键的操作1.M键:模式转换,共有4种模式选择。1.监视器模式...